diff --git a/conf/config.php b/conf/config.php index 6cdda70..59c560c 100644 --- a/conf/config.php +++ b/conf/config.php @@ -6,6 +6,9 @@ define('DEBUG', false); // Debug file path define('DEBUG_FILE', __DIR__.'/data/debug.log'); +// Plugins directory +define('PLUGINS_DIR', 'data/plugins'); + // Folder for uploaded files, don't forget the trailing slash define('FILES_DIR', 'data/files/'); @@ -65,20 +68,20 @@ define('LDAP_SERVER', ''); // LDAP server port (389 by default) define('LDAP_PORT', 389); -// By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification. +// By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification define('LDAP_SSL_VERIFY', true); // Enable LDAP START_TLS define('LDAP_START_TLS', false); -// LDAP bind type: "anonymous", "user" (use the given user/password from the form) and "proxy" (a specific user to browse the LDAP directory) +// LDAP bind type: "anonymous", "user" or "proxy" define('LDAP_BIND_TYPE', 'anonymous'); -// LDAP username to connect with. null for anonymous bind (by default). -// Or for user bind type, you can use a pattern: %s@kanboard.local +// LDAP username to use with proxy mode +// LDAP username pattern to use with user mode define('LDAP_USERNAME', null); -// LDAP password to connect with. null for anonymous bind (by default). +// LDAP password to use for proxy mode define('LDAP_PASSWORD', null); // LDAP account base, i.e. root of all user account @@ -90,16 +93,27 @@ define('LDAP_ACCOUNT_BASE', ''); // Example for OpenLDAP: 'uid=%s' define('LDAP_USER_PATTERN', ''); -// Name of an attribute of the user account object which should be used as the full name of the user. +// Name of an attribute of the user account object which should be used as the full name of the user define('LDAP_ACCOUNT_FULLNAME', 'displayname'); -// Name of an attribute of the user account object which should be used as the email of the user. +// 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 @@ -174,6 +188,9 @@ define('ENABLE_HSTS', true); // Enable or disable "X-Frame-Options: DENY" HTTP header define('ENABLE_XFRAME', true); +// Enable syslog logging +define('ENABLE_SYSLOG', true); + // Escape html inside markdown text define('MARKDOWN_ESCAPE_HTML', true); @@ -198,3 +215,10 @@ define('BRUTEFORCE_LOCKDOWN_DURATION', 15); // Session duration in second (0 = until the browser is closed) // See http://php.net/manual/en/session.configuration.php#ini.session.cookie-lifetime define('SESSION_DURATION', 1); + +// HTTP client proxy +define('HTTP_PROXY_HOSTNAME', ''); +define('HTTP_PROXY_PORT', '3128'); +define('HTTP_PROXY_USERNAME', ''); +define('HTTP_PROXY_PASSWORD', ''); + diff --git a/sources/.htaccess b/sources/.htaccess index 0d873f5..45123a9 100644 --- a/sources/.htaccess +++ b/sources/.htaccess @@ -7,3 +7,20 @@ RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [QSA,L] + + + + = 2.3> + Require all denied + + + Order allow,deny + Deny from all + + + + + Order allow,deny + Deny from all + + diff --git a/sources/ChangeLog b/sources/ChangeLog index ddfb2f7..6b9f551 100644 --- a/sources/ChangeLog +++ b/sources/ChangeLog @@ -1,3 +1,67 @@ +Version 1.0.19 +-------------- + +New features: + +* Added web notifications +* Added LDAP group sync +* Added swimlane description +* New plugin system (alpha) +* Added Bahasa Indonesia translation +* Added API procedures: getMyOverdueTasks, getOverdueTasksByProject and GetMyProjects +* Added user API access for procedure getProjectActivity() +* Added config parameter to enable/disable Syslog +* Added custom filters +* Added http client proxy support + +Core functionalities moved to plugins: + +* Budget planning: https://github.com/kanboard/plugin-budget +* SubtaskForecast: https://github.com/kanboard/plugin-subtask-forecast +* Timetable: https://github.com/kanboard/plugin-timetable + +Improvements: + +* When duplicating a task redirect to the new task +* Include more shortcut links into the view "My projects" +* Duplicate a project with tasks will copy the new tasks in the same columns +* Offer alternative method to create Mysql and Postgres databases (import sql dump) +* Make sure there is always a trailing slash for application_url +* Do not show the checkbox "Show default swimlane" when there is no active swimlanes +* Append filters instead of replacing value for users and categories dropdowns +* Do not show empty swimlanes in public view +* Change swimlane layout to save space on the screen +* Add the possibility to set/unset max column height (column scrolling) +* Show "Open this task" in dropdown menu for closed tasks +* Show assignee on card only when someone is assigned (hide nobody text) +* 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 +* Add abstract storage layer +* Add abstract cache layer +* Add Docker tag for stable version + +Others: + +* Data directory permissions are not checked anymore +* Data directory is not mandatory anymore for people that use a remote database and remote object storage + +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 +* Fix Mysql error about gitlab_id when creating remote user +* Fix subtask timer bug (event called recursively) +* Fix Postgres issue "Cardinality violation" when there is multiple "is_milestone_of" links +* Fix issue with due date greater than year 2038 + Version 1.0.18 -------------- diff --git a/sources/app/.htaccess b/sources/app/.htaccess index 14249c5..c47998c 100644 --- a/sources/app/.htaccess +++ b/sources/app/.htaccess @@ -1 +1,7 @@ -Deny from all \ No newline at end of file += 2.3> + Require all denied + + + Order allow,deny + Deny from all + diff --git a/sources/app/Action/Base.php b/sources/app/Action/Base.php index d0c81d8..c8ff02a 100644 --- a/sources/app/Action/Base.php +++ b/sources/app/Action/Base.php @@ -126,6 +126,17 @@ abstract class Base extends \Core\Base return get_called_class(); } + /** + * Get project id + * + * @access public + * @return integer + */ + public function getProjectId() + { + return $this->project_id; + } + /** * Set an user defined parameter * diff --git a/sources/app/Action/TaskDuplicateAnotherProject.php b/sources/app/Action/TaskDuplicateAnotherProject.php index 55ebc76..7b7c6bf 100644 --- a/sources/app/Action/TaskDuplicateAnotherProject.php +++ b/sources/app/Action/TaskDuplicateAnotherProject.php @@ -64,7 +64,9 @@ class TaskDuplicateAnotherProject extends Base */ public function doAction(array $data) { - return (bool) $this->taskDuplication->duplicateToProject($data['task_id'], $this->getParam('project_id')); + $destination_column_id = $this->board->getFirstColumn($this->getParam('project_id')); + + return (bool) $this->taskDuplication->duplicateToProject($data['task_id'], $this->getParam('project_id'), null, $destination_column_id); } /** diff --git a/sources/app/Api/Base.php b/sources/app/Api/Base.php index 17c7f79..0287e0e 100644 --- a/sources/app/Api/Base.php +++ b/sources/app/Api/Base.php @@ -19,6 +19,8 @@ abstract class Base extends \Core\Base 'getMyActivityStream', 'createMyPrivateProject', 'getMyProjectsList', + 'getMyProjects', + 'getMyOverdueTasks', ); private $both_allowed_procedures = array( @@ -37,6 +39,8 @@ abstract class Base extends \Core\Base 'createTask', 'updateTask', 'getBoard', + 'getProjectActivity', + 'getOverdueTasksByProject', ); public function checkProcedurePermission($is_user, $procedure) @@ -50,6 +54,8 @@ abstract class Base extends \Core\Base else if (! $is_user && ! $is_both_procedure && $is_user_procedure) { throw new AccessDeniedException('Permission denied'); } + + $this->logger->debug('API call: '.$procedure); } public function checkProjectPermission($project_id) diff --git a/sources/app/Api/File.php b/sources/app/Api/File.php index 97aa9d8..ad736ad 100644 --- a/sources/app/Api/File.php +++ b/sources/app/Api/File.php @@ -2,6 +2,8 @@ namespace Api; +use Core\ObjectStorage\ObjectStorageException; + /** * File API controller * @@ -22,16 +24,17 @@ class File extends \Core\Base public function downloadFile($file_id) { - $file = $this->file->getById($file_id); + try { - if (! empty($file)) { + $file = $this->file->getById($file_id); - $filename = FILES_DIR.$file['path']; - - if (file_exists($filename)) { - return base64_encode(file_get_contents($filename)); + if (! empty($file)) { + return base64_encode($this->objectStorage->get($file['path'])); } } + catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } return ''; } diff --git a/sources/app/Api/Me.php b/sources/app/Api/Me.php index 29a8052..e761155 100644 --- a/sources/app/Api/Me.php +++ b/sources/app/Api/Me.php @@ -33,7 +33,8 @@ class Me extends Base public function getMyActivityStream() { - return $this->projectActivity->getProjects($this->projectPermission->getActiveMemberProjectIds($this->userSession->getId()), 100); + $project_ids = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId()); + return $this->projectActivity->getProjects($project_ids, 100); } public function createMyPrivateProject($name, $description = null) @@ -52,4 +53,17 @@ class Me extends Base { return $this->projectPermission->getMemberProjects($this->userSession->getId()); } + + public function getMyOverdueTasks() + { + return $this->taskFinder->getOverdueTasksByUser($this->userSession->getId()); + } + + public function getMyProjects() + { + $project_ids = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId()); + $projects = $this->project->getAllByIds($project_ids); + + return $this->formatProjects($projects); + } } diff --git a/sources/app/Api/Project.php b/sources/app/Api/Project.php index c3ae503..8ed382c 100644 --- a/sources/app/Api/Project.php +++ b/sources/app/Api/Project.php @@ -58,6 +58,7 @@ class Project extends Base public function getProjectActivity($project_id) { + $this->checkProjectPermission($project_id); return $this->projectActivity->getProject($project_id); } diff --git a/sources/app/Api/Swimlane.php b/sources/app/Api/Swimlane.php index fb40841..13838d7 100644 --- a/sources/app/Api/Swimlane.php +++ b/sources/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/sources/app/Api/Task.php b/sources/app/Api/Task.php index 946a9e8..23a8c5b 100644 --- a/sources/app/Api/Task.php +++ b/sources/app/Api/Task.php @@ -34,6 +34,12 @@ class Task extends Base { return $this->taskFinder->getOverdueTasks(); } + + public function getOverdueTasksByProject($project_id) + { + $this->checkProjectPermission($project_id); + return $this->taskFinder->getOverdueTasksByProject($project_id); + } public function openTask($task_id) { diff --git a/sources/app/Auth/Ldap.php b/sources/app/Auth/Ldap.php index c1459b4..3a48c40 100644 --- a/sources/app/Auth/Ldap.php +++ b/sources/app/Auth/Ldap.php @@ -19,6 +19,190 @@ class Ldap extends Base */ const AUTH_NAME = 'LDAP'; + /** + * Get LDAP server name + * + * @access public + * @return string + */ + public function getLdapServer() + { + return LDAP_SERVER; + } + + /** + * Get LDAP bind type + * + * @access public + * @return integer + */ + public function getLdapBindType() + { + return LDAP_BIND_TYPE; + } + + /** + * 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 + * @param string $username + * @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 * @@ -29,7 +213,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 +230,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 { @@ -64,28 +248,6 @@ class Ldap extends Base return false; } - /** - * 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 * @@ -98,8 +260,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 +270,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('LDAP: The PHP LDAP extension is required'); + return false; } // Skip SSL certificate verification @@ -122,10 +285,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('LDAP: Unable to connect to the LDAP server'); + return false; } ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); @@ -134,30 +298,31 @@ 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('LDAP: 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 * @return boolean */ - private function bind($ldap, $username, $password) + public function bind($ldap, $username, $password) { - if (LDAP_BIND_TYPE === 'user') { - $ldap_username = sprintf(LDAP_USERNAME, $username); + if ($this->getLdapBindType() === 'user') { + $ldap_username = sprintf($this->getLdapUsername(), $username); $ldap_password = $password; } - else if (LDAP_BIND_TYPE === 'proxy') { - $ldap_username = LDAP_USERNAME; - $ldap_password = LDAP_PASSWORD; + else if ($this->getLdapBindType() === 'proxy') { + $ldap_username = $this->getLdapUsername(); + $ldap_password = $this->getLdapPassword(); } else { $ldap_username = null; @@ -165,6 +330,8 @@ class Ldap extends Base } if (! @ldap_bind($ldap, $ldap_username, $ldap_password)) { + $this->logger->error('LDAP: Unable to bind to server with: '.$ldap_username); + $this->logger->error('LDAP: bind type='.$this->getLdapBindType()); return false; } @@ -172,118 +339,189 @@ 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)); + $user_pattern = $this->getLdapUserPattern($username); + $entries = $this->executeQuery($ldap, $user_pattern); - if ($sr === false) { + if ($entries === false) { + $this->logger->error('LDAP: Unable to get user profile: '.$user_pattern); return false; } - $info = ldap_get_entries($ldap, $sr); - - // User not found - if (count($info) == 0 || $info['count'] == 0) { - return false; + if (@ldap_bind($ldap, $entries[0]['dn'], $password)) { + return $this->prepareProfile($ldap, $entries, $username); } - // We got our user - if (@ldap_bind($ldap, $info[0]['dn'], $password)) { - - return array( - 'username' => $username, - 'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME), - 'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL), - ); + if (DEBUG) { + $this->logger->debug('LDAP: wrong password for '.$entries[0]['dn']); } return false; } /** - * Retrieve info on LDAP user + * Build user profile from LDAP information * - * @param string $username Username - * @param string $email Email address + * @access public + * @param resource $ldap + * @param array $entries + * @param string $username + * @return boolean|array */ - public function lookup($username = null, $email = null) + public function prepareProfile($ldap, array $entries, $username) { - $query = $this->getQuery($username, $email); - if ($query === false) { + if ($this->getLdapAccountId() !== '') { + $username = $this->getEntry($entries, $this->getLdapAccountId(), $username); + } + + 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, + ); + } + + /** + * 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; } - // Connect and attempt anonymous bind + for ($i = 0; $i < $group_entries['count']; $i++) { + if ($group_entries[$i] === $group_dn) { + return true; + } + } + + return false; + } + + /** + * Retrieve info on LDAP user by username or email + * + * @access public + * @param string $username + * @param string $email + * @return boolean|array + */ + public function lookup($username = null, $email = null) + { + $query = $this->getLookupQuery($username, $email); + if ($query === '') { + return false; + } + + // 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) { - return false; - } - - $info = ldap_get_entries($ldap, $sr); - - // User not found - if (count($info) == 0 || $info['count'] == 0) { + $entries = $this->executeQuery($ldap, $query); + if ($entries === false) { return false; } // User id not retrieved: LDAP_ACCOUNT_ID not properly configured - if (empty($username) && ! isset($info[0][LDAP_ACCOUNT_ID][0])) { + if (empty($username) && ! isset($entries[0][$this->getLdapAccountId()][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 $this->prepareProfile($ldap, $entries, $username); + } + + /** + * 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; + } + + $entries = ldap_get_entries($ldap, $sr); + if ($entries === false || count($entries) === 0 || $entries['count'] == 0) { + return false; + } + + 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($info[0][$key][0]) ? $info[0][$key][0] : $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($entries[0][$key]) ? $entries[0][$key] : $default; } } diff --git a/sources/app/Auth/ReverseProxy.php b/sources/app/Auth/ReverseProxy.php index c8fd5ee..7818254 100644 --- a/sources/app/Auth/ReverseProxy.php +++ b/sources/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/sources/app/Console/TaskOverdueNotification.php b/sources/app/Console/TaskOverdueNotification.php index 3d254ae..2428ad3 100644 --- a/sources/app/Console/TaskOverdueNotification.php +++ b/sources/app/Console/TaskOverdueNotification.php @@ -19,7 +19,7 @@ class TaskOverdueNotification extends Base protected function execute(InputInterface $input, OutputInterface $output) { - $tasks = $this->notification->sendOverdueTaskNotifications(); + $tasks = $this->overdueNotification->sendOverdueTaskNotifications(); if ($input->getOption('show')) { $this->showTable($output, $tasks); diff --git a/sources/app/Controller/App.php b/sources/app/Controller/App.php index fe1e648..8a1a6e2 100644 --- a/sources/app/Controller/App.php +++ b/sources/app/Controller/App.php @@ -3,7 +3,6 @@ namespace Controller; use Model\Subtask as SubtaskModel; -use Model\Task as TaskModel; /** * Application controller @@ -188,6 +187,22 @@ class App extends Base ))); } + /** + * My notifications + * + * @access public + */ + public function notifications() + { + $user = $this->getUser(); + + $this->response->html($this->layout('app/notifications', array( + 'title' => t('My notifications'), + 'notifications' => $this->webNotification->getAll($user['id']), + 'user' => $user, + ))); + } + /** * Render Markdown text and reply with the HTML Code * @@ -213,7 +228,7 @@ class App extends Base { $search = $this->request->getStringParam('term'); - $filter = $this->taskFilter + $filter = $this->taskFilterAutoCompleteFormatter ->create() ->filterByProjects($this->projectPermission->getActiveMemberProjectIds($this->userSession->getId())) ->excludeTasks(array($this->request->getIntegerParam('exclude_task_id'))); @@ -226,6 +241,6 @@ class App extends Base $filter->filterByTitle($search); } - $this->response->json($filter->toAutoCompletion()); + $this->response->json($filter->format()); } } diff --git a/sources/app/Controller/Base.php b/sources/app/Controller/Base.php index 480976b..e0fd59c 100644 --- a/sources/app/Controller/Base.php +++ b/sources/app/Controller/Base.php @@ -80,7 +80,7 @@ abstract class Base extends \Core\Base private function sendHeaders($action) { // HTTP secure headers - $this->response->csp(array('style-src' => "'self' 'unsafe-inline'", 'img-src' => '* data:')); + $this->response->csp($this->container['cspRules']); $this->response->nosniff(); $this->response->xss(); diff --git a/sources/app/Controller/Board.php b/sources/app/Controller/Board.php index 179c6b3..840db05 100644 --- a/sources/app/Controller/Board.php +++ b/sources/app/Controller/Board.php @@ -27,7 +27,7 @@ class Board extends Base } // Display the board with a specific layout - $this->response->html($this->template->layout('board/public_view', array( + $this->response->html($this->template->layout('board/view_public', array( 'project' => $project, 'swimlanes' => $this->board->getBoard($project['id']), 'title' => $project['name'], @@ -49,9 +49,10 @@ class Board extends Base { $params = $this->getProjectFilters('board', 'show'); - $this->response->html($this->template->layout('board/private_view', array( + $this->response->html($this->template->layout('board/view_private', array( 'categories_list' => $this->category->getList($params['project']['id'], false), 'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false), + 'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()), 'swimlanes' => $this->taskFilter->search($params['filters']['search'])->getBoard($params['project']['id']), 'description' => $params['project']['description'], 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'), @@ -136,7 +137,7 @@ class Board extends Base } $values = $this->request->getJson(); - $this->userSession->setFilters($project_id, $values['search']); + $this->userSession->setFilters($project_id, empty($values['search']) ? '' : $values['search']); $this->response->html($this->renderBoard($project_id)); } @@ -320,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 * diff --git a/sources/app/Controller/Budget.php b/sources/app/Controller/Budget.php deleted file mode 100644 index a2f7e0d..0000000 --- a/sources/app/Controller/Budget.php +++ /dev/null @@ -1,135 +0,0 @@ -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/sources/app/Controller/Calendar.php b/sources/app/Controller/Calendar.php index 8a24d70..7050e54 100644 --- a/sources/app/Controller/Calendar.php +++ b/sources/app/Controller/Calendar.php @@ -37,20 +37,26 @@ class Calendar extends Base $end = $this->request->getStringParam('end'); // Common filter - $filter = $this->taskFilter + $filter = $this->taskFilterCalendarFormatter ->search($this->userSession->getFilters($project_id)) ->filterByProject($project_id); // Tasks if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') { - $events = $filter->copy()->filterByCreationDateRange($start, $end)->toDateTimeCalendarEvents('date_creation', 'date_completed'); + $events = $filter->copy()->filterByCreationDateRange($start, $end)->setColumns('date_creation', 'date_completed')->format(); } else { - $events = $filter->copy()->filterByStartDateRange($start, $end)->toDateTimeCalendarEvents('date_started', 'date_completed'); + $events = $filter->copy()->filterByStartDateRange($start, $end)->setColumns('date_started', 'date_completed')->format(); } // Tasks with due date - $events = array_merge($events, $filter->copy()->filterByDueDateRange($start, $end)->toAllDayCalendarEvents()); + $events = array_merge($events, $filter->copy()->filterByDueDateRange($start, $end)->setColumns('date_due')->setFullDay()->format()); + + $events = $this->hook->merge('controller:calendar:project:events', $events, array( + 'project_id' => $project_id, + 'start' => $start, + 'end' => $end, + )); $this->response->json($events); } @@ -65,17 +71,17 @@ class Calendar extends Base $user_id = $this->request->getIntegerParam('user_id'); $start = $this->request->getStringParam('start'); $end = $this->request->getStringParam('end'); - $filter = $this->taskFilter->create()->filterByOwner($user_id)->filterByStatus(TaskModel::STATUS_OPEN); + $filter = $this->taskFilterCalendarFormatter->create()->filterByOwner($user_id)->filterByStatus(TaskModel::STATUS_OPEN); // Task with due date - $events = $filter->copy()->filterByDueDateRange($start, $end)->toAllDayCalendarEvents(); + $events = $filter->copy()->filterByDueDateRange($start, $end)->setColumns('date_due')->setFullDay()->format(); // Tasks if ($this->config->get('calendar_user_tasks', 'date_started') === 'date_creation') { - $events = array_merge($events, $filter->copy()->filterByCreationDateRange($start, $end)->toDateTimeCalendarEvents('date_creation', 'date_completed')); + $events = array_merge($events, $filter->copy()->filterByCreationDateRange($start, $end)->setColumns('date_creation', 'date_completed')->format()); } else { - $events = array_merge($events, $filter->copy()->filterByStartDateRange($start, $end)->toDateTimeCalendarEvents('date_started', 'date_completed')); + $events = array_merge($events, $filter->copy()->filterByStartDateRange($start, $end)->setColumns('date_started', 'date_completed')->format()); } // Subtasks time tracking @@ -83,10 +89,11 @@ class Calendar extends Base $events = array_merge($events, $this->subtaskTimeTracking->getUserCalendarEvents($user_id, $start, $end)); } - // Subtask estimates - if ($this->config->get('calendar_user_subtasks_forecast') == 1) { - $events = array_merge($events, $this->subtaskForecast->getCalendarEvents($user_id, $end)); - } + $events = $this->hook->merge('controller:calendar:user:events', $events, array( + 'user_id' => $user_id, + 'start' => $start, + 'end' => $end, + )); $this->response->json($events); } diff --git a/sources/app/Controller/Config.php b/sources/app/Controller/Config.php index 6f14cc3..1ae390c 100644 --- a/sources/app/Controller/Config.php +++ b/sources/app/Controller/Config.php @@ -48,7 +48,7 @@ class Config extends Base $values += array('integration_slack_webhook' => 0, 'integration_hipchat' => 0, 'integration_gravatar' => 0, 'integration_jabber' => 0); break; case 'calendar': - $values += array('calendar_user_subtasks_forecast' => 0, 'calendar_user_subtasks_time_tracking' => 0); + $values += array('calendar_user_subtasks_time_tracking' => 0); break; } @@ -77,6 +77,19 @@ class Config extends Base ))); } + /** + * Display the plugin page + * + * @access public + */ + public function plugins() + { + $this->response->html($this->layout('config/plugins', array( + 'plugins' => $this->pluginLoader->plugins, + 'title' => t('Settings').' > '.t('Plugins'), + ))); + } + /** * Display the application settings page * diff --git a/sources/app/Controller/Customfilter.php b/sources/app/Controller/Customfilter.php new file mode 100644 index 0000000..c403cb4 --- /dev/null +++ b/sources/app/Controller/Customfilter.php @@ -0,0 +1,142 @@ +getProject(); + + $this->response->html($this->projectLayout('custom_filter/index', array( + 'values' => $values + array('project_id' => $project['id']), + 'errors' => $errors, + 'project' => $project, + 'custom_filters' => $this->customFilter->getAll($project['id'], $this->userSession->getId()), + 'title' => t('Custom filters'), + ))); + } + + /** + * Save a new custom filter + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + + $values = $this->request->getValues(); + $values['user_id'] = $this->userSession->getId(); + + list($valid, $errors) = $this->customFilter->validateCreation($values); + + if ($valid) { + if ($this->customFilter->create($values)) { + $this->session->flash(t('Your custom filter have been created successfully.')); + $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); + } + else { + $this->session->flashError(t('Unable to create your custom filter.')); + } + } + + $this->index($values, $errors); + } + + /** + * Remove a custom filter + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $filter = $this->customFilter->getById($this->request->getIntegerParam('filter_id')); + + $this->checkPermission($project, $filter); + + if ($this->customFilter->remove($filter['id'])) { + $this->session->flash(t('Custom filter removed successfully.')); + } else { + $this->session->flashError(t('Unable to remove this custom filter.')); + } + + $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); + } + + /** + * Edit a custom filter (display the form) + * + * @access public + */ + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $filter = $this->customFilter->getById($this->request->getIntegerParam('filter_id')); + + $this->checkPermission($project, $filter); + + $this->response->html($this->projectLayout('custom_filter/edit', array( + 'values' => empty($values) ? $filter : $values, + 'errors' => $errors, + 'project' => $project, + 'filter' => $filter, + 'title' => t('Edit custom filter') + ))); + } + + /** + * Edit a custom filter (validate the form and update the database) + * + * @access public + */ + public function update() + { + $project = $this->getProject(); + $filter = $this->customFilter->getById($this->request->getIntegerParam('filter_id')); + + $this->checkPermission($project, $filter); + + $values = $this->request->getValues(); + + if (! isset($values['is_shared'])) { + $values += array('is_shared' => 0); + } + + list($valid, $errors) = $this->customFilter->validateModification($values); + + if ($valid) { + if ($this->customFilter->update($values)) { + $this->session->flash(t('Your custom filter have been updated successfully.')); + $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); + } + else { + $this->session->flashError(t('Unable to update custom filter.')); + } + } + + $this->edit($values, $errors); + } + + private function checkPermission(array $project, array $filter) + { + $user_id = $this->userSession->getId(); + + if ($filter['user_id'] != $user_id && (! $this->projectPermission->isManager($project['id'], $user_id) || ! $this->userSession->isAdmin())) { + $this->forbidden(); + } + } +} diff --git a/sources/app/Controller/Doc.php b/sources/app/Controller/Doc.php index 19644b8..f9f0a67 100644 --- a/sources/app/Controller/Doc.php +++ b/sources/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/sources/app/Controller/File.php b/sources/app/Controller/File.php index f73a9de..ef90c55 100644 --- a/sources/app/Controller/File.php +++ b/sources/app/Controller/File.php @@ -2,6 +2,8 @@ namespace Controller; +use Core\ObjectStorage\ObjectStorageException; + /** * File controller * @@ -60,7 +62,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.')); } @@ -74,16 +76,21 @@ class File extends Base */ public function download() { - $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); - $filename = FILES_DIR.$file['path']; + try { + + $task = $this->getTask(); + $file = $this->file->getById($this->request->getIntegerParam('file_id')); + + if ($file['task_id'] != $task['id']) { + $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + } - if ($file['task_id'] == $task['id'] && file_exists($filename)) { $this->response->forceDownload($file['name']); - $this->response->binary(file_get_contents($filename)); + $this->objectStorage->output($file['path']); + } + catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); } - - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); } /** @@ -111,17 +118,20 @@ class File extends Base */ public function image() { - $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); - $filename = FILES_DIR.$file['path']; + try { - if ($file['task_id'] == $task['id'] && file_exists($filename)) { - $metadata = getimagesize($filename); + $task = $this->getTask(); + $file = $this->file->getById($this->request->getIntegerParam('file_id')); - 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->output($file['path']); + } + catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); } } @@ -132,18 +142,20 @@ class File extends Base */ public function thumbnail() { - $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); - $filename = FILES_DIR.$file['path']; + try { - if ($file['task_id'] == $task['id'] && file_exists($filename)) { + $task = $this->getTask(); + $file = $this->file->getById($this->request->getIntegerParam('file_id')); + + if ($file['task_id'] != $task['id']) { + $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + } $this->response->contentType('image/jpeg'); - $this->file->generateThumbnail( - $filename, - $this->request->getIntegerParam('width'), - $this->request->getIntegerParam('height') - ); + $this->objectStorage->output($this->file->getThumbnailPath($file['path'])); + } + catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); } } diff --git a/sources/app/Controller/Gantt.php b/sources/app/Controller/Gantt.php index a2d3f36..879ab37 100644 --- a/sources/app/Controller/Gantt.php +++ b/sources/app/Controller/Gantt.php @@ -25,7 +25,7 @@ class Gantt extends Base } $this->response->html($this->template->layout('gantt/projects', array( - 'projects' => $this->project->getGanttBars($project_ids), + 'projects' => $this->projectGanttFormatter->filter($project_ids)->format(), 'title' => t('Gantt chart for all projects'), 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()), ))); @@ -57,7 +57,7 @@ class Gantt extends Base public function project() { $params = $this->getProjectFilters('gantt', 'project'); - $filter = $this->taskFilter->search($params['filters']['search'])->filterByProject($params['project']['id']); + $filter = $this->taskFilterGanttFormatter->search($params['filters']['search'])->filterByProject($params['project']['id']); $sorting = $this->request->getStringParam('sorting', 'board'); if ($sorting === 'date') { @@ -70,7 +70,7 @@ class Gantt extends Base $this->response->html($this->template->layout('gantt/project', $params + array( 'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false), 'sorting' => $sorting, - 'tasks' => $filter->toGanttBars(), + 'tasks' => $filter->format(), ))); } diff --git a/sources/app/Controller/Hourlyrate.php b/sources/app/Controller/Hourlyrate.php deleted file mode 100644 index 19650ed..0000000 --- a/sources/app/Controller/Hourlyrate.php +++ /dev/null @@ -1,89 +0,0 @@ -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/sources/app/Controller/Ical.php b/sources/app/Controller/Ical.php index 0129915..e89b7e3 100644 --- a/sources/app/Controller/Ical.php +++ b/sources/app/Controller/Ical.php @@ -29,7 +29,7 @@ class Ical extends Base } // Common filter - $filter = $this->taskFilter + $filter = $this->taskFilterICalendarFormatter ->create() ->filterByOwner($user['id']); @@ -58,7 +58,7 @@ class Ical extends Base } // Common filter - $filter = $this->taskFilter + $filter = $this->taskFilterICalendarFormatter ->create() ->filterByProject($project['id']); @@ -83,16 +83,31 @@ class Ical extends Base // Tasks if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') { - $filter->copy()->filterByCreationDateRange($start, $end)->addDateTimeIcalEvents('date_creation', 'date_completed', $calendar); + $filter + ->copy() + ->filterByCreationDateRange($start, $end) + ->setColumns('date_creation', 'date_completed') + ->setCalendar($calendar) + ->addDateTimeEvents(); } else { - $filter->copy()->filterByStartDateRange($start, $end)->addDateTimeIcalEvents('date_started', 'date_completed', $calendar); + $filter + ->copy() + ->filterByStartDateRange($start, $end) + ->setColumns('date_started', 'date_completed') + ->setCalendar($calendar) + ->addDateTimeEvents($calendar); } // Tasks with due date - $filter->copy()->filterByDueDateRange($start, $end)->addAllDayIcalEvents('date_due', $calendar); + $filter + ->copy() + ->filterByDueDateRange($start, $end) + ->setColumns('date_due') + ->setCalendar($calendar) + ->addFullDayEvents($calendar); $this->response->contentType('text/calendar; charset=utf-8'); - echo $calendar->render(); + echo $filter->setCalendar($calendar)->format(); } } diff --git a/sources/app/Controller/Swimlane.php b/sources/app/Controller/Swimlane.php index 054fa4b..f92e9fe 100644 --- a/sources/app/Controller/Swimlane.php +++ b/sources/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/sources/app/Controller/Taskcreation.php b/sources/app/Controller/Taskcreation.php index ff25c5d..b9e9a33 100644 --- a/sources/app/Controller/Taskcreation.php +++ b/sources/app/Controller/Taskcreation.php @@ -59,25 +59,29 @@ class Taskcreation extends Base list($valid, $errors) = $this->taskValidator->validateCreation($values); - if ($valid) { - - if ($this->taskCreation->create($values)) { - $this->session->flash(t('Task created successfully.')); - - 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.')); - } + 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.')); } $this->create($values, $errors); } + + 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)); + } + } + else { + $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id']))); + } + } } diff --git a/sources/app/Controller/Taskduplication.php b/sources/app/Controller/Taskduplication.php index aebbcfc..8d329ca 100644 --- a/sources/app/Controller/Taskduplication.php +++ b/sources/app/Controller/Taskduplication.php @@ -26,7 +26,7 @@ class Taskduplication extends Base if ($task_id > 0) { $this->session->flash(t('Task created successfully.')); - $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task_id))); } else { $this->session->flashError(t('Unable to create this task.')); $this->response->redirect($this->helper->url->to('taskduplication', 'duplicate', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); @@ -83,15 +83,16 @@ class Taskduplication extends Base $values = $this->request->getValues(); list($valid,) = $this->taskValidator->validateProjectModification($values); - if ($valid && $this->taskDuplication->duplicateToProject($task['id'], - $values['project_id'], - $values['swimlane_id'], - $values['column_id'], - $values['category_id'], - $values['owner_id'])) { + if ($valid) { + $task_id = $this->taskDuplication->duplicateToProject( + $task['id'], $values['project_id'], $values['swimlane_id'], + $values['column_id'], $values['category_id'], $values['owner_id'] + ); - $this->session->flash(t('Task created successfully.')); - $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); + if ($task_id > 0) { + $this->session->flash(t('Task created successfully.')); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $values['project_id'], 'task_id' => $task_id))); + } } $this->session->flashError(t('Unable to create your task.')); diff --git a/sources/app/Controller/Taskmodification.php b/sources/app/Controller/Taskmodification.php index 56d2b9f..638af59 100644 --- a/sources/app/Controller/Taskmodification.php +++ b/sources/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 ($valid && $this->taskModification->update($values)) { + $this->session->flash(t('Task updated successfully.')); - if ($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/sources/app/Controller/Taskstatus.php b/sources/app/Controller/Taskstatus.php index a47d9da..1768b77 100644 --- a/sources/app/Controller/Taskstatus.php +++ b/sources/app/Controller/Taskstatus.php @@ -18,36 +18,8 @@ class Taskstatus extends Base public function close() { $task = $this->getTask(); - $redirect = $this->request->getStringParam('redirect'); - - if ($this->request->getStringParam('confirmation') === 'yes') { - - $this->checkCSRFParam(); - - if ($this->taskStatus->close($task['id'])) { - $this->session->flash(t('Task closed successfully.')); - } else { - $this->session->flashError(t('Unable to close this task.')); - } - - if ($redirect === 'board') { - $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); - } - - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); - } - - if ($this->request->isAjax()) { - $this->response->html($this->template->render('task_status/close', array( - 'task' => $task, - 'redirect' => $redirect, - ))); - } - - $this->response->html($this->taskLayout('task_status/close', array( - 'task' => $task, - 'redirect' => $redirect, - ))); + $this->changeStatus($task, 'close', t('Task closed successfully.'), t('Unable to close this task.')); + $this->renderTemplate($task, 'task_status/close'); } /** @@ -58,22 +30,44 @@ class Taskstatus extends Base public function open() { $task = $this->getTask(); + $this->changeStatus($task, 'open', t('Task opened successfully.'), t('Unable to open this task.')); + $this->renderTemplate($task, 'task_status/open'); + } + private function changeStatus(array $task, $method, $success_message, $failure_message) + { if ($this->request->getStringParam('confirmation') === 'yes') { $this->checkCSRFParam(); - if ($this->taskStatus->open($task['id'])) { - $this->session->flash(t('Task opened successfully.')); + if ($this->taskStatus->$method($task['id'])) { + $this->session->flash($success_message); } else { - $this->session->flashError(t('Unable to open this task.')); + $this->session->flashError($failure_message); } - $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); + if ($this->request->getStringParam('redirect') === 'board') { + $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); + } + + $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + } + } + + private function renderTemplate(array $task, $template) + { + $redirect = $this->request->getStringParam('redirect'); + + if ($this->request->isAjax()) { + $this->response->html($this->template->render($template, array( + 'task' => $task, + 'redirect' => $redirect, + ))); } - $this->response->html($this->taskLayout('task_status/open', array( + $this->response->html($this->taskLayout($template, array( 'task' => $task, + 'redirect' => $redirect, ))); } } diff --git a/sources/app/Controller/Timetable.php b/sources/app/Controller/Timetable.php deleted file mode 100644 index 65edb44..0000000 --- a/sources/app/Controller/Timetable.php +++ /dev/null @@ -1,39 +0,0 @@ -getUser(); - $from = $this->request->getStringParam('from', date('Y-m-d')); - $to = $this->request->getStringParam('to', date('Y-m-d', strtotime('next week'))); - $timetable = $this->timetable->calculate($user['id'], new DateTime($from), new DateTime($to)); - - $this->response->html($this->layout('timetable/index', array( - 'user' => $user, - 'timetable' => $timetable, - 'values' => array( - 'from' => $from, - 'to' => $to, - 'controller' => 'timetable', - 'action' => 'index', - 'user_id' => $user['id'], - ), - ))); - } -} diff --git a/sources/app/Controller/Timetableday.php b/sources/app/Controller/Timetableday.php deleted file mode 100644 index c8f7ac8..0000000 --- a/sources/app/Controller/Timetableday.php +++ /dev/null @@ -1,88 +0,0 @@ -getUser(); - - $this->response->html($this->layout('timetable_day/index', array( - 'timetable' => $this->timetableDay->getByUser($user['id']), - 'values' => $values + array('user_id' => $user['id']), - 'errors' => $errors, - 'user' => $user, - ))); - } - - /** - * Validate and save - * - * @access public - */ - public function save() - { - $values = $this->request->getValues(); - list($valid, $errors) = $this->timetableDay->validateCreation($values); - - if ($valid) { - - if ($this->timetableDay->create($values['user_id'], $values['start'], $values['end'])) { - $this->session->flash(t('Time slot created successfully.')); - $this->response->redirect($this->helper->url->to('timetableday', 'index', array('user_id' => $values['user_id']))); - } - else { - $this->session->flashError(t('Unable to save this time slot.')); - } - } - - $this->index($values, $errors); - } - - /** - * Confirmation dialag box to remove a row - * - * @access public - */ - public function confirm() - { - $user = $this->getUser(); - - $this->response->html($this->layout('timetable_day/remove', array( - 'slot_id' => $this->request->getIntegerParam('slot_id'), - 'user' => $user, - ))); - } - - /** - * Remove a row - * - * @access public - */ - public function remove() - { - $this->checkCSRFParam(); - $user = $this->getUser(); - - if ($this->timetableDay->remove($this->request->getIntegerParam('slot_id'))) { - $this->session->flash(t('Time slot removed successfully.')); - } - else { - $this->session->flash(t('Unable to remove this time slot.')); - } - - $this->response->redirect($this->helper->url->to('timetableday', 'index', array('user_id' => $user['id']))); - } -} diff --git a/sources/app/Controller/Timetableextra.php b/sources/app/Controller/Timetableextra.php deleted file mode 100644 index 7c6fe26..0000000 --- a/sources/app/Controller/Timetableextra.php +++ /dev/null @@ -1,16 +0,0 @@ -getUser(); - - $paginator = $this->paginator - ->setUrl($this->controller_url, 'index', array('user_id' => $user['id'])) - ->setMax(10) - ->setOrder('date') - ->setDirection('desc') - ->setQuery($this->{$this->model}->getUserQuery($user['id'])) - ->calculate(); - - $this->response->html($this->layout($this->template_dir.'/index', array( - 'values' => $values + array('user_id' => $user['id']), - 'errors' => $errors, - 'paginator' => $paginator, - 'user' => $user, - ))); - } - - /** - * Validate and save - * - * @access public - */ - public function save() - { - $values = $this->request->getValues(); - list($valid, $errors) = $this->{$this->model}->validateCreation($values); - - if ($valid) { - - if ($this->{$this->model}->create( - $values['user_id'], - $values['date'], - isset($values['all_day']) && $values['all_day'] == 1, - $values['start'], - $values['end'], - $values['comment'])) { - - $this->session->flash(t('Time slot created successfully.')); - $this->response->redirect($this->helper->url->to($this->controller_url, 'index', array('user_id' => $values['user_id']))); - } - else { - $this->session->flashError(t('Unable to save this time slot.')); - } - } - - $this->index($values, $errors); - } - - /** - * Confirmation dialag box to remove a row - * - * @access public - */ - public function confirm() - { - $user = $this->getUser(); - - $this->response->html($this->layout($this->template_dir.'/remove', array( - 'slot_id' => $this->request->getIntegerParam('slot_id'), - 'user' => $user, - ))); - } - - /** - * Remove a row - * - * @access public - */ - public function remove() - { - $this->checkCSRFParam(); - $user = $this->getUser(); - - if ($this->{$this->model}->remove($this->request->getIntegerParam('slot_id'))) { - $this->session->flash(t('Time slot removed successfully.')); - } - else { - $this->session->flash(t('Unable to remove this time slot.')); - } - - $this->response->redirect($this->helper->url->to($this->controller_url, 'index', array('user_id' => $user['id']))); - } -} diff --git a/sources/app/Controller/Timetableweek.php b/sources/app/Controller/Timetableweek.php deleted file mode 100644 index b8ce00e..0000000 --- a/sources/app/Controller/Timetableweek.php +++ /dev/null @@ -1,99 +0,0 @@ -getUser(); - - if (empty($values)) { - - $day = $this->timetableDay->getByUser($user['id']); - - $values = array( - 'user_id' => $user['id'], - 'start' => isset($day[0]['start']) ? $day[0]['start'] : null, - 'end' => isset($day[0]['end']) ? $day[0]['end'] : null, - ); - } - - $this->response->html($this->layout('timetable_week/index', array( - 'timetable' => $this->timetableWeek->getByUser($user['id']), - 'values' => $values, - 'errors' => $errors, - 'user' => $user, - ))); - } - - /** - * Validate and save - * - * @access public - */ - public function save() - { - $values = $this->request->getValues(); - list($valid, $errors) = $this->timetableWeek->validateCreation($values); - - if ($valid) { - - if ($this->timetableWeek->create($values['user_id'], $values['day'], $values['start'], $values['end'])) { - $this->session->flash(t('Time slot created successfully.')); - $this->response->redirect($this->helper->url->to('timetableweek', 'index', array('user_id' => $values['user_id']))); - } - else { - $this->session->flashError(t('Unable to save this time slot.')); - } - } - - $this->index($values, $errors); - } - - /** - * Confirmation dialag box to remove a row - * - * @access public - */ - public function confirm() - { - $user = $this->getUser(); - - $this->response->html($this->layout('timetable_week/remove', array( - 'slot_id' => $this->request->getIntegerParam('slot_id'), - 'user' => $user, - ))); - } - - /** - * Remove a row - * - * @access public - */ - public function remove() - { - $this->checkCSRFParam(); - $user = $this->getUser(); - - if ($this->timetableWeek->remove($this->request->getIntegerParam('slot_id'))) { - $this->session->flash(t('Time slot removed successfully.')); - } - else { - $this->session->flash(t('Unable to remove this time slot.')); - } - - $this->response->redirect($this->helper->url->to('timetableweek', 'index', array('user_id' => $user['id']))); - } -} diff --git a/sources/app/Controller/User.php b/sources/app/Controller/User.php index 04e5741..0b39619 100644 --- a/sources/app/Controller/User.php +++ b/sources/app/Controller/User.php @@ -2,6 +2,8 @@ namespace Controller; +use Model\NotificationType; + /** * User controller * @@ -92,6 +94,11 @@ class User extends Base if ($user_id !== false) { $this->projectPermission->addMember($project_id, $user_id); + + if (! empty($values['notifications_enabled'])) { + $this->notificationType->saveUserSelectedTypes($user_id, array(NotificationType::TYPE_EMAIL)); + } + $this->session->flash(t('User created successfully.')); $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user_id))); } @@ -202,6 +209,8 @@ class User extends Base $this->response->html($this->layout('user/notifications', array( 'projects' => $this->projectPermission->getMemberProjects($user['id']), 'notifications' => $this->notification->readSettings($user['id']), + 'types' => $this->notificationType->getTypes(), + 'filters' => $this->notificationFilter->getFilters(), 'user' => $user, ))); } diff --git a/sources/app/Controller/Webnotification.php b/sources/app/Controller/Webnotification.php new file mode 100644 index 0000000..a481e9b --- /dev/null +++ b/sources/app/Controller/Webnotification.php @@ -0,0 +1,39 @@ +userSession->getId(); + + $this->webNotification->markAllAsRead($user_id); + $this->response->redirect($this->helper->url->to('app', 'notifications', array('user_id' => $user_id))); + } + + /** + * Mark a notification as read + * + * @access public + */ + public function remove() + { + $user_id = $this->userSession->getId(); + $notification_id = $this->request->getIntegerParam('notification_id'); + + $this->webNotification->markAsRead($user_id, $notification_id); + $this->response->redirect($this->helper->url->to('app', 'notifications', array('user_id' => $user_id))); + } +} diff --git a/sources/app/Core/Base.php b/sources/app/Core/Base.php index 3db0cf7..7503e84 100644 --- a/sources/app/Core/Base.php +++ b/sources/app/Core/Base.php @@ -10,78 +10,86 @@ use Pimple\Container; * @package core * @author Frederic Guillot * - * @property \Core\Helper $helper - * @property \Core\EmailClient $emailClient - * @property \Core\HttpClient $httpClient - * @property \Core\Paginator $paginator - * @property \Core\Request $request - * @property \Core\Session $session - * @property \Core\Template $template - * @property \Core\MemoryCache $memoryCache - * @property \Core\OAuth2 $oauth - * @property \Core\Router $router - * @property \Core\Lexer $lexer - * @property \Integration\BitbucketWebhook $bitbucketWebhook - * @property \Integration\GithubWebhook $githubWebhook - * @property \Integration\GitlabWebhook $gitlabWebhook - * @property \Integration\HipchatWebhook $hipchatWebhook - * @property \Integration\Jabber $jabber - * @property \Integration\Mailgun $mailgun - * @property \Integration\Postmark $postmark - * @property \Integration\Sendgrid $sendgrid - * @property \Integration\SlackWebhook $slackWebhook - * @property \Integration\Smtp $smtp - * @property \Model\Acl $acl - * @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 - * @property \Model\Config $config - * @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 - * @property \Model\Project $project - * @property \Model\ProjectActivity $projectActivity - * @property \Model\ProjectAnalytic $projectAnalytic - * @property \Model\ProjectDuplication $projectDuplication - * @property \Model\ProjectDailyColumnStats $projectDailyColumnStats - * @property \Model\ProjectDailyStats $projectDailyStats - * @property \Model\ProjectIntegration $projectIntegration - * @property \Model\ProjectPermission $projectPermission - * @property \Model\Subtask $subtask - * @property \Model\SubtaskExport $subtaskExport - * @property \Model\SubtaskForecast $subtaskForecast - * @property \Model\SubtaskTimeTracking $subtaskTimeTracking - * @property \Model\Swimlane $swimlane - * @property \Model\Task $task - * @property \Model\TaskAnalytic $taskAnalytic - * @property \Model\TaskCreation $taskCreation - * @property \Model\TaskDuplication $taskDuplication - * @property \Model\TaskExport $taskExport - * @property \Model\TaskFinder $taskFinder - * @property \Model\TaskFilter $taskFilter - * @property \Model\TaskLink $taskLink - * @property \Model\TaskModification $taskModification - * @property \Model\TaskPermission $taskPermission - * @property \Model\TaskPosition $taskPosition - * @property \Model\TaskStatus $taskStatus - * @property \Model\TaskValidator $taskValidator - * @property \Model\Timetable $timetable - * @property \Model\TimetableDay $timetableDay - * @property \Model\TimetableExtra $timetableExtra - * @property \Model\TimetableOff $timetableOff - * @property \Model\TimetableWeek $timetableWeek - * @property \Model\Transition $transition - * @property \Model\User $user - * @property \Model\UserSession $userSession - * @property \Model\Webhook $webhook + * @property \Core\Helper $helper + * @property \Core\EmailClient $emailClient + * @property \Core\HttpClient $httpClient + * @property \Core\Paginator $paginator + * @property \Core\Request $request + * @property \Core\Session $session + * @property \Core\Template $template + * @property \Core\OAuth2 $oauth + * @property \Core\Router $router + * @property \Core\Lexer $lexer + * @property \Core\ObjectStorage\ObjectStorageInterface $objectStorage + * @property \Core\Cache\Cache $memoryCache + * @property \Core\Plugin\Hook $hook + * @property \Core\Plugin\Loader $pluginLoader + * @property \Integration\BitbucketWebhook $bitbucketWebhook + * @property \Integration\GithubWebhook $githubWebhook + * @property \Integration\GitlabWebhook $gitlabWebhook + * @property \Integration\HipchatWebhook $hipchatWebhook + * @property \Integration\Jabber $jabber + * @property \Integration\Mailgun $mailgun + * @property \Integration\Postmark $postmark + * @property \Integration\Sendgrid $sendgrid + * @property \Integration\SlackWebhook $slackWebhook + * @property \Integration\Smtp $smtp + * @property \Formatter\ProjectGanttFormatter $projectGanttFormatter + * @property \Formatter\TaskFilterGanttFormatter $taskFilterGanttFormatter + * @property \Formatter\TaskFilterAutoCompleteFormatter $taskFilterAutoCompleteFormatter + * @property \Formatter\TaskFilterCalendarFormatter $taskFilterCalendarFormatter + * @property \Formatter\TaskFilterICalendarFormatter $taskFilterICalendarFormatter + * @property \Model\Acl $acl + * @property \Model\Action $action + * @property \Model\Authentication $authentication + * @property \Model\Board $board + * @property \Model\Category $category + * @property \Model\Color $color + * @property \Model\Comment $comment + * @property \Model\Config $config + * @property \Model\Currency $currency + * @property \Model\CustomFilter $customFilter + * @property \Model\DateParser $dateParser + * @property \Model\File $file + * @property \Model\LastLogin $lastLogin + * @property \Model\Link $link + * @property \Model\Notification $notification + * @property \Model\NotificationType $notificationType + * @property \Model\NotificationFilter $notificationFilter + * @property \Model\OverdueNotification $overdueNotification + * @property \Model\WebNotification $webNotification + * @property \Model\Project $project + * @property \Model\ProjectActivity $projectActivity + * @property \Model\ProjectAnalytic $projectAnalytic + * @property \Model\ProjectDuplication $projectDuplication + * @property \Model\ProjectDailyColumnStats $projectDailyColumnStats + * @property \Model\ProjectDailyStats $projectDailyStats + * @property \Model\ProjectIntegration $projectIntegration + * @property \Model\ProjectPermission $projectPermission + * @property \Model\Subtask $subtask + * @property \Model\SubtaskExport $subtaskExport + * @property \Model\SubtaskTimeTracking $subtaskTimeTracking + * @property \Model\Swimlane $swimlane + * @property \Model\Task $task + * @property \Model\TaskAnalytic $taskAnalytic + * @property \Model\TaskCreation $taskCreation + * @property \Model\TaskDuplication $taskDuplication + * @property \Model\TaskExport $taskExport + * @property \Model\TaskFinder $taskFinder + * @property \Model\TaskFilter $taskFilter + * @property \Model\TaskLink $taskLink + * @property \Model\TaskModification $taskModification + * @property \Model\TaskPermission $taskPermission + * @property \Model\TaskPosition $taskPosition + * @property \Model\TaskStatus $taskStatus + * @property \Model\TaskValidator $taskValidator + * @property \Model\Transition $transition + * @property \Model\User $user + * @property \Model\UserSession $userSession + * @property \Model\Webhook $webhook + * @property \Psr\Log\LoggerInterface $logger + * @property \League\HTMLToMarkdown\HtmlConverter $htmlConverter + * @property \PicoDb\Database $db */ abstract class Base { diff --git a/sources/app/Core/Cache.php b/sources/app/Core/Cache.php deleted file mode 100644 index 670a76e..0000000 --- a/sources/app/Core/Cache.php +++ /dev/null @@ -1,58 +0,0 @@ -container = $container; - $this->init(); - } - - /** - * Proxy cache - * - * Note: Arguments must be scalar types - * - * @access public - * @param string $container Container name - * @param string $method Container method - * @return mixed - */ - public function proxy($container, $method) - { - $args = func_get_args(); - $key = 'proxy_'.implode('_', $args); - $result = $this->get($key); - - if ($result === null) { - $result = call_user_func_array(array($this->container[$container], $method), array_splice($args, 2)); - $this->set($key, $result); - } - - return $result; - } -} diff --git a/sources/app/Core/Cache/Base.php b/sources/app/Core/Cache/Base.php new file mode 100644 index 0000000..a16c12f --- /dev/null +++ b/sources/app/Core/Cache/Base.php @@ -0,0 +1,38 @@ +get($key); + + if ($result === null) { + $result = call_user_func_array(array($class, $method), array_splice($args, 1)); + $this->set($key, $result); + } + + return $result; + } +} diff --git a/sources/app/Core/Cache/CacheInterface.php b/sources/app/Core/Cache/CacheInterface.php new file mode 100644 index 0000000..8675ef8 --- /dev/null +++ b/sources/app/Core/Cache/CacheInterface.php @@ -0,0 +1,45 @@ +storage[$key] = $value; + } + + /** + * Fetch value from cache + * + * @access public + * @param string $key + * @return mixed Null when not found, cached value otherwise + */ + public function get($key) + { + return isset($this->storage[$key]) ? $this->storage[$key] : null; + } + + /** + * Clear all cache + * + * @access public + */ + public function flush() + { + $this->storage = array(); + } + + /** + * Remove cached value + * + * @access public + * @param string $key + */ + public function remove($key) + { + unset($this->storage[$key]); + } +} diff --git a/sources/app/Core/HttpClient.php b/sources/app/Core/HttpClient.php index b808f75..99534cf 100644 --- a/sources/app/Core/HttpClient.php +++ b/sources/app/Core/HttpClient.php @@ -99,9 +99,18 @@ class HttpClient extends Base return ''; } - $headers = array_merge(array('User-Agent: '.self::HTTP_USER_AGENT, 'Connection: close'), $headers); + $default_headers = array( + 'User-Agent: '.self::HTTP_USER_AGENT, + 'Connection: close', + ); - $context = stream_context_create(array( + if (HTTP_PROXY_USERNAME) { + $default_headers[] = 'Proxy-Authorization: Basic '.base64_encode(HTTP_PROXY_USERNAME.':'.HTTP_PROXY_PASSWORD); + } + + $headers = array_merge($default_headers, $headers); + + $context = array( 'http' => array( 'method' => $method, 'protocol_version' => 1.1, @@ -110,9 +119,14 @@ class HttpClient extends Base 'header' => implode("\r\n", $headers), 'content' => $content ) - )); + ); - $stream = @fopen(trim($url), 'r', false, $context); + if (HTTP_PROXY_HOSTNAME) { + $context['http']['proxy'] = 'tcp://'.HTTP_PROXY_HOSTNAME.':'.HTTP_PROXY_PORT; + $context['http']['request_fulluri'] = true; + } + + $stream = @fopen(trim($url), 'r', false, stream_context_create($context)); $response = ''; if (is_resource($stream)) { diff --git a/sources/app/Core/MemoryCache.php b/sources/app/Core/MemoryCache.php deleted file mode 100644 index f80a66e..0000000 --- a/sources/app/Core/MemoryCache.php +++ /dev/null @@ -1,32 +0,0 @@ -storage[$key] = $value; - } - - public function get($key) - { - return isset($this->storage[$key]) ? $this->storage[$key] : null; - } - - public function flush() - { - $this->storage = array(); - } - - public function remove($key) - { - unset($this->storage[$key]); - } -} diff --git a/sources/app/Core/NotificationInterface.php b/sources/app/Core/NotificationInterface.php new file mode 100644 index 0000000..5dca74e --- /dev/null +++ b/sources/app/Core/NotificationInterface.php @@ -0,0 +1,22 @@ +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 + */ + 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 output($key) + { + $filename = $this->path.DIRECTORY_SEPARATOR.$key; + + if (! file_exists($filename)) { + throw new ObjectStorageException('File not found: '.$filename); + } + + readfile($filename); + } + + /** + * Move local file to object storage + * + * @access public + * @param string $src_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 = strpos($key, '/') !== false ? $this->path.DIRECTORY_SEPARATOR.dirname($key) : $this->path; + + if (! is_dir($folder) && ! mkdir($folder, 0755, true)) { + throw new ObjectStorageException('Unable to create folder: '.$folder); + } + } +} diff --git a/sources/app/Core/ObjectStorage/ObjectStorageException.php b/sources/app/Core/ObjectStorage/ObjectStorageException.php new file mode 100644 index 0000000..e89aeb2 --- /dev/null +++ b/sources/app/Core/ObjectStorage/ObjectStorageException.php @@ -0,0 +1,9 @@ +container['cspRules'] = $rules; + } + + /** + * 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); + }); + } + + /** + * Get plugin name + * + * This method should be overrided by your Plugin class + * + * @access public + * @return string + */ + public function getPluginName() + { + return ucfirst(substr(get_called_class(), 7, -7)); + } + + /** + * Get plugin description + * + * This method should be overrided by your Plugin class + * + * @access public + * @return string + */ + public function getPluginDescription() + { + return ''; + } + + /** + * Get plugin author + * + * This method should be overrided by your Plugin class + * + * @access public + * @return string + */ + public function getPluginAuthor() + { + return '?'; + } + + /** + * Get plugin version + * + * This method should be overrided by your Plugin class + * + * @access public + * @return string + */ + public function getPluginVersion() + { + return '?'; + } + + /** + * Get plugin homepage + * + * This method should be overrided by your Plugin class + * + * @access public + * @return string + */ + public function getPluginHomepage() + { + return ''; + } +} diff --git a/sources/app/Core/Plugin/Hook.php b/sources/app/Core/Plugin/Hook.php new file mode 100644 index 0000000..fa14af1 --- /dev/null +++ b/sources/app/Core/Plugin/Hook.php @@ -0,0 +1,99 @@ +hooks[$hook])) { + $this->hooks[$hook] = array(); + } + + $this->hooks[$hook][] = $value; + } + + /** + * Get all bindings for a hook + * + * @access public + * @param string $hook + * @return array + */ + public function getListeners($hook) + { + return isset($this->hooks[$hook]) ? $this->hooks[$hook] : array(); + } + + /** + * Return true if the hook is used + * + * @access public + * @param string $hook + * @return boolean + */ + public function exists($hook) + { + return isset($this->hooks[$hook]); + } + + /** + * Merge listener results with input array + * + * @access public + * @param string $hook + * @param array $values + * @param array $params + * @return array + */ + public function merge($hook, array &$values, array $params = array()) + { + foreach ($this->getListeners($hook) as $listener) { + $result = call_user_func_array($listener, $params); + + if (is_array($result) && ! empty($result)) { + $values = array_merge($values, $result); + } + } + + return $values; + } + + /** + * Execute only first listener + * + * @access public + * @param string $hook + * @param array $params + * @return mixed + */ + public function first($hook, array $params = array()) + { + foreach ($this->getListeners($hook) as $listener) { + return call_user_func_array($listener, $params); + } + + return null; + } +} diff --git a/sources/app/Core/Plugin/Loader.php b/sources/app/Core/Plugin/Loader.php new file mode 100644 index 0000000..9a884da --- /dev/null +++ b/sources/app/Core/Plugin/Loader.php @@ -0,0 +1,148 @@ +isDot() && $fileinfo->isDir()) { + $plugin = $fileinfo->getFilename(); + $this->loadSchema($plugin); + $this->load($plugin); + } + } + } + } + + /** + * Load plugin + * + * @access public + * @param string $plugin + */ + public function load($plugin) + { + $class = '\Plugin\\'.$plugin.'\\Plugin'; + $instance = new $class($this->container); + + Tool::buildDic($this->container, $instance->getClasses()); + + $instance->initialize(); + $this->plugins[] = $instance; + } + + /** + * Load plugin schema + * + * @access public + * @param string $plugin + */ + public function loadSchema($plugin) + { + $filename = PLUGINS_DIR.'/'.$plugin.'/Schema/'.ucfirst(DB_DRIVER).'.php'; + + if (file_exists($filename)) { + require_once($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/sources/app/Core/Response.php b/sources/app/Core/Response.php index d42a8f1..f8ca015 100644 --- a/sources/app/Core/Response.php +++ b/sources/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/sources/app/Core/Router.php b/sources/app/Core/Router.php index 6e7576d..93d266b 100644 --- a/sources/app/Core/Router.php +++ b/sources/app/Core/Router.php @@ -80,7 +80,7 @@ class Router extends Base $path = substr($path, 0, - strlen($query_string) - 1); } - if ($path{0} === '/') { + if (! empty($path) && $path{0} === '/') { $path = substr($path, 1); } @@ -206,56 +206,22 @@ class Router extends Base * @access public * @param string $uri * @param string $query_string - * @return boolean */ public function dispatch($uri, $query_string = '') { if (! empty($_GET['controller']) && ! empty($_GET['action'])) { - $controller = $this->sanitize($_GET['controller'], 'app'); - $action = $this->sanitize($_GET['action'], 'index'); + $this->controller = $this->sanitize($_GET['controller'], 'app'); + $this->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($this->controller, $this->action) = $this->findRoute($this->getPath($uri, $query_string)); // TODO: add plugin for routes + $plugin = ''; } + $class = empty($plugin) ? '\Controller\\'.ucfirst($this->controller) : '\Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($this->controller); - 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; - } - - return false; + $instance = new $class($this->container); + $instance->beforeAction($this->controller, $this->action); + $instance->{$this->action}(); } } diff --git a/sources/app/Core/Session.php b/sources/app/Core/Session.php index df0ec5f..4f5fde7 100644 --- a/sources/app/Core/Session.php +++ b/sources/app/Core/Session.php @@ -45,7 +45,9 @@ class Session implements ArrayAccess ini_set('session.use_only_cookies', '1'); // Enable strict mode - ini_set('session.use_strict_mode', '1'); + if (version_compare(PHP_VERSION, '7.0.0') < 0) { + ini_set('session.use_strict_mode', '1'); + } // Ensure session ID integrity ini_set('session.entropy_file', '/dev/urandom'); diff --git a/sources/app/Core/Template.php b/sources/app/Core/Template.php index ba869ee..9ca904d 100644 --- a/sources/app/Core/Template.php +++ b/sources/app/Core/Template.php @@ -2,8 +2,6 @@ namespace Core; -use LogicException; - /** * Template class * @@ -13,11 +11,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 +32,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 +55,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/sources/app/Core/Tool.php b/sources/app/Core/Tool.php index 84e42ba..887c8fb 100644 --- a/sources/app/Core/Tool.php +++ b/sources/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/sources/app/Core/Translator.php b/sources/app/Core/Translator.php index e3d1969..e9aa1f3 100644 --- a/sources/app/Core/Translator.php +++ b/sources/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/sources/app/Formatter/FormatterInterface.php b/sources/app/Formatter/FormatterInterface.php new file mode 100644 index 0000000..4193bd4 --- /dev/null +++ b/sources/app/Formatter/FormatterInterface.php @@ -0,0 +1,14 @@ +projects = array(); + } + else { + + $this->projects = $this->db + ->table(self::TABLE) + ->asc('start_date') + ->in('id', $project_ids) + ->eq('is_active', self::ACTIVE) + ->eq('is_private', 0) + ->findAll(); + } + + return $this; + } + + /** + * Format projects to be displayed in the Gantt chart + * + * @access public + * @return array + */ + public function format() + { + $colors = $this->color->getDefaultColors(); + $bars = array(); + + foreach ($this->projects as $project) { + $start = empty($project['start_date']) ? time() : strtotime($project['start_date']); + $end = empty($project['end_date']) ? $start : strtotime($project['end_date']); + $color = next($colors) ?: reset($colors); + + $bars[] = array( + 'type' => 'project', + 'id' => $project['id'], + 'title' => $project['name'], + 'start' => array( + (int) date('Y', $start), + (int) date('n', $start), + (int) date('j', $start), + ), + 'end' => array( + (int) date('Y', $end), + (int) date('n', $end), + (int) date('j', $end), + ), + 'link' => $this->helper->url->href('project', 'show', array('project_id' => $project['id'])), + 'board_link' => $this->helper->url->href('board', 'show', array('project_id' => $project['id'])), + 'gantt_link' => $this->helper->url->href('gantt', 'project', array('project_id' => $project['id'])), + 'color' => $color, + 'not_defined' => empty($project['start_date']) || empty($project['end_date']), + 'users' => $this->projectPermission->getProjectUsers($project['id']), + ); + } + + return $bars; + } +} diff --git a/sources/app/Formatter/TaskFilterAutoCompleteFormatter.php b/sources/app/Formatter/TaskFilterAutoCompleteFormatter.php new file mode 100644 index 0000000..999a894 --- /dev/null +++ b/sources/app/Formatter/TaskFilterAutoCompleteFormatter.php @@ -0,0 +1,33 @@ +query->columns(Task::TABLE.'.id', Task::TABLE.'.title')->findAll(); + + foreach ($tasks as &$task) { + $task['value'] = $task['title']; + $task['label'] = '#'.$task['id'].' - '.$task['title']; + } + + return $tasks; + } +} diff --git a/sources/app/Formatter/TaskFilterCalendarEvent.php b/sources/app/Formatter/TaskFilterCalendarEvent.php new file mode 100644 index 0000000..8733ee8 --- /dev/null +++ b/sources/app/Formatter/TaskFilterCalendarEvent.php @@ -0,0 +1,76 @@ +startColumn = $start_column; + $this->endColumn = $end_column ?: $start_column; + return $this; + } + + /** + * When called calendar events will be full day + * + * @access public + * @return TaskFilterCalendarEvent + */ + public function setFullDay() + { + $this->fullDay = true; + return $this; + } + + /** + * Return true if the events are full day + * + * @access public + * @return boolean + */ + public function isFullDay() + { + return $this->fullDay; + } +} diff --git a/sources/app/Formatter/TaskFilterCalendarFormatter.php b/sources/app/Formatter/TaskFilterCalendarFormatter.php new file mode 100644 index 0000000..f3f42b9 --- /dev/null +++ b/sources/app/Formatter/TaskFilterCalendarFormatter.php @@ -0,0 +1,52 @@ +query->findAll() as $task) { + $events[] = array( + 'timezoneParam' => $this->config->getCurrentTimezone(), + 'id' => $task['id'], + 'title' => t('#%d', $task['id']).' '.$task['title'], + 'backgroundColor' => $this->color->getBackgroundColor($task['color_id']), + 'borderColor' => $this->color->getBorderColor($task['color_id']), + 'textColor' => 'black', + 'url' => $this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), + 'start' => date($this->getDateTimeFormat(), $task[$this->startColumn]), + 'end' => date($this->getDateTimeFormat(), $task[$this->endColumn] ?: time()), + 'editable' => $this->isFullDay(), + 'allday' => $this->isFullDay(), + ); + } + + return $events; + } + + /** + * Get DateTime format for event + * + * @access private + * @return string + */ + private function getDateTimeFormat() + { + return $this->isFullDay() ? 'Y-m-d' : 'Y-m-d\TH:i:s'; + } +} diff --git a/sources/app/Formatter/TaskFilterGanttFormatter.php b/sources/app/Formatter/TaskFilterGanttFormatter.php new file mode 100644 index 0000000..069daa3 --- /dev/null +++ b/sources/app/Formatter/TaskFilterGanttFormatter.php @@ -0,0 +1,78 @@ +query->findAll() as $task) { + $bars[] = $this->formatTask($task); + } + + return $bars; + } + + /** + * Format a single task + * + * @access private + * @param array $task + * @return array + */ + private function formatTask(array $task) + { + if (! isset($this->columns[$task['project_id']])) { + $this->columns[$task['project_id']] = $this->board->getColumnsList($task['project_id']); + } + + $start = $task['date_started'] ?: time(); + $end = $task['date_due'] ?: $start; + + return array( + 'type' => 'task', + 'id' => $task['id'], + 'title' => $task['title'], + 'start' => array( + (int) date('Y', $start), + (int) date('n', $start), + (int) date('j', $start), + ), + 'end' => array( + (int) date('Y', $end), + (int) date('n', $end), + (int) date('j', $end), + ), + 'column_title' => $task['column_name'], + 'assignee' => $task['assignee_name'] ?: $task['assignee_username'], + 'progress' => $this->task->getProgress($task, $this->columns[$task['project_id']]).'%', + 'link' => $this->helper->url->href('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), + 'color' => $this->color->getColorProperties($task['color_id']), + 'not_defined' => empty($task['date_due']) || empty($task['date_started']), + ); + } +} diff --git a/sources/app/Formatter/TaskFilterICalendarFormatter.php b/sources/app/Formatter/TaskFilterICalendarFormatter.php new file mode 100644 index 0000000..8aed1e2 --- /dev/null +++ b/sources/app/Formatter/TaskFilterICalendarFormatter.php @@ -0,0 +1,135 @@ +vCalendar->render(); + } + + /** + * Set calendar object + * + * @access public + * @param \Eluceo\iCal\Component\Calendar $vCalendar + * @return TaskFilterICalendarFormatter + */ + public function setCalendar(Calendar $vCalendar) + { + $this->vCalendar = $vCalendar; + return $this; + } + + /** + * Transform results to ical events + * + * @access public + * @return TaskFilterICalendarFormatter + */ + public function addDateTimeEvents() + { + foreach ($this->query->findAll() as $task) { + + $start = new DateTime; + $start->setTimestamp($task[$this->startColumn]); + + $end = new DateTime; + $end->setTimestamp($task[$this->endColumn] ?: time()); + + $vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$this->startColumn.'-'.$this->endColumn); + $vEvent->setDtStart($start); + $vEvent->setDtEnd($end); + + $this->vCalendar->addComponent($vEvent); + } + + return $this; + } + + /** + * Transform results to all day ical events + * + * @access public + * @return TaskFilterICalendarFormatter + */ + public function addFullDayEvents() + { + foreach ($this->query->findAll() as $task) { + + $date = new DateTime; + $date->setTimestamp($task[$this->startColumn]); + + $vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$this->startColumn); + $vEvent->setDtStart($date); + $vEvent->setDtEnd($date); + $vEvent->setNoTime(true); + + $this->vCalendar->addComponent($vEvent); + } + + return $this; + } + + /** + * Get common events for task ical events + * + * @access protected + * @param array $task + * @param string $uid + * @return Event + */ + protected function getTaskIcalEvent(array &$task, $uid) + { + $dateCreation = new DateTime; + $dateCreation->setTimestamp($task['date_creation']); + + $dateModif = new DateTime; + $dateModif->setTimestamp($task['date_modification']); + + $vEvent = new Event($uid); + $vEvent->setCreated($dateCreation); + $vEvent->setModified($dateModif); + $vEvent->setUseTimezone(true); + $vEvent->setSummary(t('#%d', $task['id']).' '.$task['title']); + $vEvent->setUrl($this->helper->url->base().$this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + + if (! empty($task['owner_id'])) { + $vEvent->setOrganizer($task['assignee_name'] ?: $task['assignee_username'], $task['assignee_email']); + } + + if (! empty($task['creator_id'])) { + $attendees = new Attendees; + $attendees->add('MAILTO:'.($task['creator_email'] ?: $task['creator_username'].'@kanboard.local')); + $vEvent->setAttendees($attendees); + } + + return $vEvent; + } +} diff --git a/sources/app/Helper/App.php b/sources/app/Helper/App.php index e5ebefc..5fb89af 100644 --- a/sources/app/Helper/App.php +++ b/sources/app/Helper/App.php @@ -67,9 +67,11 @@ class App extends \Core\Base if (isset($this->session['flash_message'])) { $html = '
'.$this->helper->e($this->session['flash_message']).'
'; unset($this->session['flash_message']); + unset($this->session['flash_error_message']); } else if (isset($this->session['flash_error_message'])) { $html = '
'.$this->helper->e($this->session['flash_error_message']).'
'; + unset($this->session['flash_message']); unset($this->session['flash_error_message']); } diff --git a/sources/app/Helper/Form.php b/sources/app/Helper/Form.php index 83e3b11..a37cc81 100644 --- a/sources/app/Helper/Form.php +++ b/sources/app/Helper/Form.php @@ -103,6 +103,26 @@ class Form extends \Core\Base return ''; } + /** + * Display a checkboxes group + * + * @access public + * @param string $name Field name + * @param array $options Options + * @param array $values Form values + * @return string + */ + public function checkboxes($name, array $options, array $values = array()) + { + $html = ''; + + foreach ($options as $value => $label) { + $html .= $this->checkbox($name.'['.$value.']', $label, $value, isset($values[$name]) && in_array($value, $values[$name])); + } + + return $html; + } + /** * Display a checkbox field * diff --git a/sources/app/Helper/Hook.php b/sources/app/Helper/Hook.php new file mode 100644 index 0000000..dc76e5e --- /dev/null +++ b/sources/app/Helper/Hook.php @@ -0,0 +1,64 @@ +hook->getListeners($hook) as $file) { + $buffer .= $this->helper->asset->$type($file); + } + + return $buffer; + } + + /** + * Render all attached hooks + * + * @access public + * @param string $hook + * @param array $variables + * @return string + */ + public function render($hook, array $variables = array()) + { + $buffer = ''; + + foreach ($this->hook->getListeners($hook) as $template) { + $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->hook->on($hook, $template); + return $this; + } +} diff --git a/sources/app/Helper/User.php b/sources/app/Helper/User.php index cb596fb..6b7d6f7 100644 --- a/sources/app/Helper/User.php +++ b/sources/app/Helper/User.php @@ -10,6 +10,17 @@ namespace Helper; */ class User extends \Core\Base { + /** + * Return true if the logged user as unread notifications + * + * @access public + * @return boolean + */ + public function hasNotifications() + { + return $this->webNotification->hasNotifications($this->userSession->getId()); + } + /** * Get initials from a user * @@ -99,7 +110,7 @@ class User extends \Core\Base return true; } - return $this->memoryCache->proxy('acl', 'handleProjectAdminPermissions', $project_id); + return $this->memoryCache->proxy($this->container['acl'], 'handleProjectAdminPermissions', $project_id); } /** @@ -114,7 +125,7 @@ class User extends \Core\Base return true; } - return $this->memoryCache->proxy('acl', 'handleProjectManagerPermissions', $project_id); + return $this->memoryCache->proxy($this->container['acl'], 'handleProjectManagerPermissions', $project_id); } /** diff --git a/sources/app/Integration/Mailgun.php b/sources/app/Integration/Mailgun.php index 1451b21..076c311 100644 --- a/sources/app/Integration/Mailgun.php +++ b/sources/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/sources/app/Integration/Postmark.php b/sources/app/Integration/Postmark.php index dbb70ae..05bdf58 100644 --- a/sources/app/Integration/Postmark.php +++ b/sources/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/sources/app/Integration/Sendgrid.php b/sources/app/Integration/Sendgrid.php index 902749f..fd58342 100644 --- a/sources/app/Integration/Sendgrid.php +++ b/sources/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/sources/app/Integration/SlackWebhook.php b/sources/app/Integration/SlackWebhook.php index d238652..7124473 100644 --- a/sources/app/Integration/SlackWebhook.php +++ b/sources/app/Integration/SlackWebhook.php @@ -36,7 +36,7 @@ class SlackWebhook extends \Core\Base } $options = $this->projectIntegration->getParameters($project_id); - return $options['slack_webhook_url']; + return isset($options['slack_webhook_url']) ? $options['slack_webhook_url'] : ''; } /** @@ -52,14 +52,14 @@ class SlackWebhook extends \Core\Base if (! empty($channel)) { return $channel; - } + } - $options = $this->projectIntegration->getParameters($project_id); - return $options['slack_webhook_channel']; + $options = $this->projectIntegration->getParameters($project_id); + return isset($options['slack_webhook_channel']) ? $options['slack_webhook_channel'] : ''; } /** - * Send message to the incoming Slack webhook + * Send notification to Slack * * @access public * @param integer $project_id Project id @@ -76,23 +76,52 @@ class SlackWebhook extends \Core\Base $event['event_name'] = $event_name; $event['author'] = $this->user->getFullname($this->session['user']); - $payload = array( - 'text' => '*['.$project['name'].']* '.str_replace('"', '"', $this->projectActivity->getTitle($event)).(isset($event['task']['title']) ? ' ('.$event['task']['title'].')' : ''), - 'username' => 'Kanboard', - 'icon_url' => 'http://kanboard.net/assets/img/favicon.png', - ); + $message = '*['.$project['name'].']* '; + $message .= str_replace('"', '"', $this->projectActivity->getTitle($event)); + $message .= isset($event['task']['title']) ? ' ('.$event['task']['title'].')' : ''; if ($this->config->get('application_url')) { - $payload['text'] .= ' - <'.$this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true); - $payload['text'] .= '|'.t('view the task on Kanboard').'>'; + $message .= ' - <'.$this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true); + $message .= '|'.t('view the task on Kanboard').'>'; } - $channel = $this->getChannel($project_id); - if (! empty($channel)) { - $payload['channel'] = $channel; - } - - $this->httpClient->postJson($this->getWebhookUrl($project_id), $payload); + $this->sendMessage($project_id, $message); } } + + /** + * Send message to Slack + * + * @access public + * @param integer $project_id + * @param string $message + */ + public function sendMessage($project_id, $message) + { + $payload = array( + 'text' => $message, + 'username' => 'Kanboard', + 'icon_url' => 'http://kanboard.net/assets/img/favicon.png', + ); + + $this->sendPayload($project_id, $payload); + } + + /** + * Send payload to Slack + * + * @access public + * @param integer $project_id + * @param array $payload + */ + public function sendPayload($project_id, array $payload) + { + $channel = $this->getChannel($project_id); + + if (! empty($channel)) { + $payload['channel'] = $channel; + } + + $this->httpClient->postJson($this->getWebhookUrl($project_id), $payload); + } } diff --git a/sources/app/Locale/cs_CZ/translations.php b/sources/app/Locale/cs_CZ/translations.php index 557a62c..ba3d809 100644 --- a/sources/app/Locale/cs_CZ/translations.php +++ b/sources/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,75 +665,26 @@ 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ář', - 'All day' => 'Všechny dny', - 'Day' => 'Den', - 'Manage timetable' => 'Spravovat pracovní dobu', - 'Overtime timetable' => 'Přesčasy', - 'Time off timetable' => 'Pracovní volno', - 'Timetable' => 'Pracovní doba', - 'Work timetable' => 'Pracovní doba', - 'Week timetable' => 'Týdenní pracovní doba', - 'Day timetable' => 'Denní pracovní doba', - 'From' => 'Od', - 'To' => 'Do', - 'Time slot created successfully.' => 'Časový úsek byl úspěšně vytvořen.', - 'Unable to save this time slot.' => 'Nelze uložit tento časový úsek.', - 'Time slot removed successfully.' => 'Časový úsek byl odstraněn.', - 'Unable to remove this time slot.' => 'Nelze odstranit tento časový úsek', - 'Do you really want to remove this time slot?' => 'Opravdu chcete odstranit tento časový úsek?', - 'Remove time slot' => 'Odstranit časový úsek', - 'Add new time slot' => 'Přidat nový časový úsek', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Tato pracovní doba se použije když je zaškrtnuto políčko "Celý den" pro plánovanou pracovní dobu i přesčas .', '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.', 'Source column' => 'Zdrojový sloupec', - // 'Show subtask estimates (forecast of future work)' => '', 'Transitions' => 'Změny etap', 'Executer' => 'Vykonavatel', 'Time spent in the column' => 'Trvání jednotlivých etap', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/da_DK/translations.php b/sources/app/Locale/da_DK/translations.php index 6a41f06..fa45e58 100644 --- a/sources/app/Locale/da_DK/translations.php +++ b/sources/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,75 +665,26 @@ 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' => '', - // 'All day' => '', - // 'Day' => '', - // 'Manage timetable' => '', - // 'Overtime timetable' => '', - // 'Time off timetable' => '', - // 'Timetable' => '', - // 'Work timetable' => '', - // 'Week timetable' => '', - // 'Day timetable' => '', - // 'From' => '', - // 'To' => '', - // 'Time slot created successfully.' => '', - // 'Unable to save this time slot.' => '', - // 'Time slot removed successfully.' => '', - // 'Unable to remove this time slot.' => '', - // 'Do you really want to remove this time slot?' => '', - // 'Remove time slot' => '', - // 'Add new time slot' => '', - // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', // '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' => '', // 'Source column' => '', - // 'Show subtask estimates (forecast of future work)' => '', // 'Transitions' => '', // 'Executer' => '', // 'Time spent in the column' => '', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/de_DE/translations.php b/sources/app/Locale/de_DE/translations.php index 7b38e9f..c15e437 100644 --- a/sources/app/Locale/de_DE/translations.php +++ b/sources/app/Locale/de_DE/translations.php @@ -195,7 +195,7 @@ return array( 'Invalid date' => 'Ungültiges Datum', 'Must be done before %B %e, %Y' => 'Muss vor dem %d.%m.%Y erledigt werden', '%B %e, %Y' => '%d.%m.%Y', - // '%b %e, %Y' => '', + '%b %e, %Y' => '%d.%m.%Y', 'Automatic actions' => 'Automatische Aktionen', 'Your automatic action have been created successfully.' => 'Die automatische Aktion wurde erfolgreich erstellt.', 'Unable to create your automatic action.' => 'Erstellen der automatischen Aktion nicht möglich.', @@ -263,10 +263,10 @@ return array( '%d comments' => '%d Kommentare', '%d comment' => '%d Kommentar', 'Email address invalid' => 'Ungültige E-Mail-Adresse', - // '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 external account is not linked anymore to your profile.' => 'Dein externer Account ist nicht mehr mit deinem Profil verbunden.', + 'Unable to unlink your external account.' => 'Externer Account konnte nicht getrennt werden.', + 'External authentication failed' => 'Externe Authentifizierung fehlgeschlagen', + 'Your external account is linked to your profile successfully.' => 'Dein externer Account wurde erfolgreich mit deinem Profil verbunden', 'Email' => 'E-Mail', 'Link my Google Account' => 'Verbinde meinen Google-Account', 'Unlink my Google Account' => 'Verbindung mit meinem Google-Account trennen', @@ -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', @@ -608,7 +606,7 @@ return array( 'Time Tracking' => 'Zeiterfassung', 'You already have one subtask in progress' => 'Bereits eine Teilaufgabe in Bearbeitung', 'Which parts of the project do you want to duplicate?' => 'Welcher Teil des Projekts soll kopiert werden?', - // 'Disallow login form' => '', + 'Disallow login form' => 'Verbiete Login-Formular', 'Bitbucket commit received' => 'Bitbucket-Commit erhalten', 'Bitbucket webhooks' => 'Bitbucket-Webhooks', 'Help on Bitbucket webhooks' => 'Hilfe für Bitbucket-Webhooks', @@ -667,75 +665,26 @@ 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', - 'All day' => 'ganztägig', - 'Day' => 'Tag', - 'Manage timetable' => 'Zeitplan verwalten', - 'Overtime timetable' => 'Überstunden Zeitplan', - 'Time off timetable' => 'Freizeit Zeitplan', - 'Timetable' => 'Zeitplan', - 'Work timetable' => 'Arbeitszeitplan', - 'Week timetable' => 'Wochenzeitplan', - 'Day timetable' => 'Tageszeitplan', - 'From' => 'von', - 'To' => 'bis', - 'Time slot created successfully.' => 'Zeitfenster erfolgreich erstellt.', - 'Unable to save this time slot.' => 'Nicht in der Lage, dieses Zeitfenster zu speichern.', - 'Time slot removed successfully.' => 'Zeitfenster erfolgreich entfernt.', - 'Unable to remove this time slot.' => 'Nicht in der Lage, dieses Zeitfenster zu entfernen', - 'Do you really want to remove this time slot?' => 'Soll diese Zeitfenster wirklich gelöscht werden?', - 'Remove time slot' => 'Zeitfenster entfernen', - 'Add new time slot' => 'Neues Zeitfenster hinzufügen', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Dieses Zeitfenster wird verwendet, wenn die Checkbox "ganztägig" für Freizeit und Überstunden angeklickt ist.', '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.', 'Source column' => 'Quellspalte', - 'Show subtask estimates (forecast of future work)' => 'Teilaufgaben-Schätzungen anzeigen (Prognose)', 'Transitions' => 'Übergänge', 'Executer' => 'Ausführender', 'Time spent in the column' => 'Zeit in Spalte verbracht', @@ -746,7 +695,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', @@ -777,8 +725,8 @@ return array( 'uploaded by: %s' => 'Hochgeladen von: %s', 'uploaded on: %s' => 'Hochgeladen am: %s', 'size: %s' => 'Größe: %s', - 'Burndown chart for "%s"' => 'Burndown-Chart für "%s"', - 'Burndown chart' => 'Burndown-Chart', + 'Burndown chart for "%s"' => 'Burndown-Diagramm für "%s"', + 'Burndown chart' => 'Burndown-Diagramm', 'This chart show the task complexity over the time (Work Remaining).' => 'Dieses Diagramm zeigt die Aufgabenkomplexität über den Faktor Zeit (Verbleibende Arbeit).', 'Screenshot taken %s' => 'Screenshot aufgenommen %s ', 'Add a screenshot' => 'Füge einen Screenshot hinzu', @@ -827,7 +775,7 @@ return array( 'When task is moved from first column' => 'Wenn Aufgabe von erster Spalte verschoben wird', 'When task is moved to last column' => 'Wenn Aufgabe in letzte Spalte verschoben wird', 'Year(s)' => 'Jahr(e)', - // 'Jabber (XMPP)' => '', + 'Jabber (XMPP)' => 'Jabber (XMPP)', 'Send notifications to Jabber' => 'Benachrichtigungen an Jabber senden', 'XMPP server address' => 'XMPP-Server-Adresse', 'Jabber domain' => 'Jabber-Domain', @@ -844,7 +792,7 @@ return array( 'Subtasks time tracking' => 'Teilaufgaben-Zeiterfassung', 'User calendar view' => 'Benutzer-Kalendersicht', 'Automatically update the start date' => 'Beginndatum automatisch aktualisieren', - // 'iCal feed' => '', + 'iCal feed' => 'iCal Feed', 'Preferences' => 'Einstellungen', 'Security' => 'Sicherheit', 'Two factor authentication disabled' => 'Zwei-Faktor-Authentifizierung deaktiviert', @@ -854,21 +802,21 @@ return array( 'User that will receive the email' => 'Empfänger der E-Mail', 'Email subject' => 'E-Mail-Betreff', 'Date' => 'Datum', - // 'By @%s on Bitbucket' => '', + 'By @%s on Bitbucket' => 'Durch @%s auf Bitbucket', 'Bitbucket Issue' => 'Bitbucket-Issue', - // 'Commit made by @%s on Bitbucket' => '', - // 'Commit made by @%s on Github' => '', - // 'By @%s on Github' => '', - // 'Commit made by @%s on Gitlab' => '', + 'Commit made by @%s on Bitbucket' => 'Commit von @%s auf Bitbucket', + 'Commit made by @%s on Github' => 'Commit von @%s auf Github', + 'By @%s on Github' => 'Durch @%s auf Github', + 'Commit made by @%s on Gitlab' => 'Commit von @%s auf Gitlab', 'Add a comment log when moving the task between columns' => 'Kommentar hinzufügen, wenn Aufgabe in andere Spalte verschoben wird', 'Move the task to another column when the category is changed' => 'Aufgabe in andere Spalte verschieben, wenn Kategorie geändert wird', 'Send a task by email to someone' => 'Aufgabe per E-Mail versenden', 'Reopen a task' => 'Aufgabe wieder öffnen', - // 'Bitbucket issue opened' => '', - // 'Bitbucket issue closed' => '', - // 'Bitbucket issue reopened' => '', - // 'Bitbucket issue assignee change' => '', - // 'Bitbucket issue comment created' => '', + 'Bitbucket issue opened' => 'Bitbucket Ticket eröffnet', + 'Bitbucket issue closed' => 'Bitbucket Ticket geschlossen', + 'Bitbucket issue reopened' => 'Bitbucket Ticket wieder eröffnet', + 'Bitbucket issue assignee change' => 'Bitbucket Ticket Zuordnung geändert', + 'Bitbucket issue comment created' => 'Bitbucket Ticket Kommentar erstellt', 'Column change' => 'Spalte geändert', 'Position change' => 'Position geändert', 'Swimlane change' => 'Swimlane geändert', @@ -877,13 +825,10 @@ return array( 'Notification' => 'Benachrichtigungen', '%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' => '', + 'Swimlane' => 'Swimlane', + 'Gravatar' => 'Gravatar', + 'Hipchat' => 'Hipchat', + 'Slack' => 'Slack', '%s moved the task %s to the first swimlane' => '%s hat die Aufgabe %s in die erste Swimlane verschoben', '%s moved the task %s to the swimlane "%s"' => '%s hat die Aufgaben %s in die Swimlane "%s" verschoben', 'This report contains all subtasks information for the given date range.' => 'Der Bericht beinhaltet alle Teilaufgaben im gewählten Zeitraum', @@ -908,22 +853,22 @@ return array( 'The field "%s" have been updated' => 'Das Feld "%s" wurde verändert', 'The description have been modified' => 'Die Beschreibung wurde geändert', 'Do you really want to close the task "%s" as well as all subtasks?' => 'Soll die Aufgabe "%s" wirklich geschlossen werden? (einschließlich Teilaufgaben)', - // 'Swimlane: %s' => '', + 'Swimlane: %s' => 'Swimlane: %s', 'I want to receive notifications for:' => 'Ich möchte Benachrichtigungen erhalten für:', 'All tasks' => 'Alle Aufgaben', 'Only for tasks assigned to me' => 'nur mir zugeordnete Aufgane', 'Only for tasks created by me' => 'nur von mir erstellte Aufgaben', 'Only for tasks created by me and assigned to me' => 'nur mir zugeordnete und von mir erstellte Aufgaben', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', + '%A' => '%A', + '%b %e, %Y, %k:%M %p' => '%b %e, %Y, %k:%M %p', 'New due date: %B %e, %Y' => 'Neues Ablaufdatum: %B %e, %Y', 'Start date changed: %B %e, %Y' => 'Neues Beginndatum: %B %e, %Y', - // '%k:%M %p' => '', - // '%%Y-%%m-%%d' => '', + '%k:%M %p' => '%k:%M %p', + '%%Y-%%m-%%d' => '%%d.%%m.%%Y', 'Total for all columns' => 'Gesamt für alle Spalten', 'You need at least 2 days of data to show the chart.' => 'Es werden mindestens 2 Tage zur Darstellung benötigt', - // '<15m' => '', - // '<30m' => '', + '<15m' => '<15min', + '<30m' => '<30min', 'Stop timer' => 'Stoppe Timer', 'Start timer' => 'Starte Timer', 'Add project member' => 'Projektmitglied hinzufügen', @@ -943,7 +888,7 @@ return array( 'Not assigned' => 'Nicht zugewiesen', 'View advanced search syntax' => 'Zur erweiterten Suchsyntax', 'Overview' => 'Überblick', - // '%b %e %Y' => '', + '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Board-/Kalender-/Listen-Ansicht', 'Switch to the board view' => 'Zur Board-Ansicht', 'Switch to the calendar view' => 'Zur Kalender-Ansicht', @@ -963,108 +908,152 @@ return array( 'Search by category: ' => 'Suche nach Kategorie: ', 'Search by description: ' => 'Suche nach Beschreibung: ', 'Search by due date: ' => 'Suche nach Fälligkeitsdatum: ', - // 'Lead and Cycle time for "%s"' => '', - // 'Average time spent into each column for "%s"' => '', - // 'Average time spent into each column' => '', - // 'Average time spent' => '', - // 'This chart show the average time spent into each column for the last %d tasks.' => '', - // 'Average Lead and Cycle time' => '', - // 'Average lead time: ' => '', - // 'Average cycle time: ' => '', - // 'Cycle Time' => '', - // 'Lead Time' => '', - // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '', - // 'Average time into each column' => '', - // 'Lead and cycle time' => '', - // 'Google Authentication' => '', - // 'Help on Google authentication' => '', - // 'Github Authentication' => '', - // 'Help on Github authentication' => '', - // 'Channel/Group/User (Optional)' => '', - // 'Lead time: ' => '', - // 'Cycle time: ' => '', - // 'Time spent into each column' => '', - // 'The lead time is the duration between the task creation and the completion.' => '', - // 'The cycle time is the duration between the start date and the completion.' => '', - // 'If the task is not closed the current time is used instead of the completion date.' => '', - // 'Set automatically the start date' => '', - // 'Edit Authentication' => '', - // 'Google Id' => '', - // 'Github Id' => '', - // 'Remote user' => '', - // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '', - // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '', - // 'By @%s on Gitlab' => '', - // 'Gitlab issue comment created' => '', - // 'New remote user' => '', - // 'New local user' => '', - // 'Default task color' => '', - // 'Hide sidebar' => '', - // 'Expand sidebar' => '', - // 'This feature does not work with all browsers.' => '', - // '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' => '', - // 'Sort by position' => '', - // 'Sort by date' => '', - // 'Add task' => '', - // 'Start date:' => '', - // 'Due date:' => '', - // 'There is no start date or due date for this task.' => '', - // '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' => '', - // 'NOK - Norwegian Krone' => '', - // 'Show this column' => '', - // 'Hide this column' => '', - // 'open file' => '', - // 'End date' => '', - // 'Users overview' => '', - // 'Managers' => '', - // 'Members' => '', - // 'Shared project' => '', - // 'Project managers' => '', - // 'Project members' => '', - // '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' => '', + 'Lead and Cycle time for "%s"' => 'Durchlauf und Zykluszeit für "%s"', + 'Average time spent into each column for "%s"' => 'Durchschnittliche Zeit in jeder Spalte für "%s"', + 'Average time spent into each column' => 'Durchschnittszeit in jeder Spalte', + 'Average time spent' => 'Durchschnittlicher Zeitverbrauch', + 'This chart show the average time spent into each column for the last %d tasks.' => 'Dieses Diagramm zeigt die durchschnittliche Zeit in jeder Spalte der letzten %d Aufgaben.', + 'Average Lead and Cycle time' => 'Durchschnittliche Zyklus- und Durchlaufzeit', + 'Average lead time: ' => 'Durchschnittliche Durchlaufzeit:', + 'Average cycle time: ' => 'Durchschnittliche Zykluszeit:', + 'Cycle Time' => 'Zykluszeit', + 'Lead Time' => 'Durchlaufzeit', + 'This chart show the average lead and cycle time for the last %d tasks over the time.' => 'Das Diagramm zeigt die durchschnittliche Durchlauf- und Zykluszeit der letzten %d Aufgaben über die Zeit an.', + 'Average time into each column' => 'Durchschnittzeit in jeder Spalte', + 'Lead and cycle time' => 'Durchlauf- und Zykluszeit', + 'Google Authentication' => 'Google-Authentifizierung', + 'Help on Google authentication' => 'Hilfe bei Google-Authentifizierung', + 'Github Authentication' => 'Github-Authentifizierung', + 'Help on Github authentication' => 'Hilfe bei Github-Authentifizierung', + 'Channel/Group/User (Optional)' => 'Kanal/Gruppe/Benutzer (optional)', + 'Lead time: ' => 'Durchlaufzeit:', + 'Cycle time: ' => 'Zykluszeit:', + 'Time spent into each column' => 'zeit verbracht in jeder Spalte', + 'The lead time is the duration between the task creation and the completion.' => 'Die Durchlaufzeit ist die Dauer zwischen Erstellung und Fertigstellung.', + 'The cycle time is the duration between the start date and the completion.' => 'Die Zykluszeit ist die Dauer zwischen Start und Fertigstellung.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Wenn die Aufgabe nicht geschlossen ist, wird die aktuelle Zeit statt der Fertigstellung verwendet.', + 'Set automatically the start date' => 'Setze Startdatum automatisch', + 'Edit Authentication' => 'Authentifizierung bearbeiten', + 'Google Id' => 'Google Id', + 'Github Id' => 'Github Id', + 'Remote user' => 'Remote-Benutzer', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Remote-Benutzer haben kein Passwort in der Kanboard Datenbank, Beispiel LDAP, Goole und Github Accounts', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Wenn die Box "Verbiete Login-Formular" angeschaltet ist, werden Eingaben in das Login Formular ignoriert.', + 'By @%s on Gitlab' => 'Durch @%s auf Gitlab', + 'Gitlab issue comment created' => 'Gitlab Ticket Kommentar erstellt', + 'New remote user' => 'Neuer Remote-Benutzer', + 'New local user' => 'Neuer lokaler Benutzer', + 'Default task color' => 'Voreingestellte Aufgabenfarbe', + 'Hide sidebar' => 'Seitenleiste verstecken', + 'Expand sidebar' => 'Seitenleiste ausklappen', + 'This feature does not work with all browsers.' => 'Diese Funktion funktioniert nicht mit allen Browsern', + 'There is no destination project available.' => 'Es ist kein Zielprojekt vorhanden.', + 'Trigger automatically subtask time tracking' => 'Teilaufgaben Zeiterfassung automatisch starten', + 'Include closed tasks in the cumulative flow diagram' => 'Geschlossen Aufgaben ins kumulative Flussdiagramm einschließen', + 'Current swimlane: %s' => 'Aktuelle Swimlane: %s', + 'Current column: %s' => 'Aktuelle Spalte: %s', + 'Current category: %s' => 'Aktuelle Kategorie: %s', + 'no category' => 'keine Kategorie', + 'Current assignee: %s' => 'Aktuelle Zuordnung: %s', + 'not assigned' => 'nicht zugeordnet', + 'Author:' => 'Autor', + 'contributors' => 'Mitwirkende', + 'License:' => 'Lizenz:', + 'License' => 'Lizenz', + 'Project Administrator' => 'Projektadministrator', + 'Enter the text below' => 'Text unten eingeben', + 'Gantt chart for %s' => 'Gantt Diagramm für %s', + 'Sort by position' => 'Nach Position sortieren', + 'Sort by date' => 'Nach Datum sortieren', + 'Add task' => 'Aufgabe hinzufügen', + 'Start date:' => 'Startdatum:', + 'Due date:' => 'Ablaufdatum:', + 'There is no start date or due date for this task.' => 'Diese Aufgabe hat kein Start oder Ablaufdatum.', + 'Moving or resizing a task will change the start and due date of the task.' => 'Aufgabe verschieben/ändern, ändert auch Start- und Ablaufdatum der Aufgabe.', + 'There is no task in your project.' => 'Es gibt keine Aufgabe in deinem Projekt', + 'Gantt chart' => 'Gantt Diagramm', + 'People who are project managers' => 'Benutzer die Projektmanager sind', + 'People who are project members' => 'Benutzer die Projektmitglieder sind', + 'NOK - Norwegian Krone' => 'NOK - Norwegische Kronen', + 'Show this column' => 'Spalte anzeigen', + 'Hide this column' => 'Spalte verstecken', + 'open file' => 'Datei öffnen', + 'End date' => 'Endedatum', + 'Users overview' => 'Benutzerübersicht', + 'Managers' => 'Manager', + 'Members' => 'Mitglieder', + 'Shared project' => 'Geteiltes Projekt', + 'Project managers' => 'Projektmanager', + 'Project members' => 'Projektmitglieder', + 'Gantt chart for all projects' => 'Gantt Diagramm für alle Projekte', + 'Projects list' => 'Projektliste', + 'Gantt chart for this project' => 'Gantt Diagramm für dieses Projekt', + 'Project board' => 'Projekt Pinnwand', + 'End date:' => 'Endedatum:', + 'There is no start date or end date for this project.' => 'Es gibt kein Startdatum oder Endedatum für dieses Projekt', + 'Projects Gantt chart' => 'Projekt Gantt Diagramm', + 'Start date: %s' => 'Beginndatum: %s', + 'End date: %s' => 'Enddatum: %s', + 'Link type' => 'Verbindungstyp', + 'Change task color when using a specific task link' => 'Aufgabefarbe ändern bei bestimmter Aufgabenverbindung', + 'Task link creation or modification' => 'Aufgabenverbindung erstellen oder bearbeiten', + 'Login with my Gitlab Account' => 'Mit Gitlab Account einloggen', + 'Milestone' => 'Meilenstein', + 'Gitlab Authentication' => 'Gitlab-Authentifizierung', + 'Help on Gitlab authentication' => 'Hilfe bei Gitlab-Authentifizierung', + 'Gitlab Id' => 'Gitlab Id', + 'Gitlab Account' => 'Gitlab Account', + 'Link my Gitlab Account' => 'Verknüpfe mein Gitlab Account', + 'Unlink my Gitlab Account' => 'Trenne meinen Gitlab Account', + 'Documentation: %s' => 'Dokumentation: %s', + 'Switch to the Gantt chart view' => 'Zur Gantt-Diagramm Ansicht wechseln', + 'Reset the search/filter box' => 'Suche/Filter-Box zurücksetzen', + 'Documentation' => 'Dokumentation', + 'Table of contents' => 'Inhaltsverzeichnis', + 'Gantt' => 'Gantt', + 'Help with project permissions' => 'Hilfe bei Projektberechtigungen', + 'Author' => 'Autor', + 'Version' => 'Version', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'Es ist kein Plugin geladen.', + 'Set maximum column height' => 'Setze maximale Spaltenhöhe', + 'Remove maximum column height' => 'Entferne maximale Spaltenhöhe', + 'My notifications' => 'Meine Benachrichtigungen', + 'Custom filters' => 'benutzerdefinierte Filter', + 'Your custom filter have been created successfully.' => 'Benutzerdefinierten Filter erfolgreich erstellt.', + 'Unable to create your custom filter.' => 'Benutzerdefinierter Filter konnte nicht erstellt werden.', + 'Custom filter removed successfully.' => 'Benutzerdefinierten Filter erfolgreich entfernt.', + 'Unable to remove this custom filter.' => 'Benutzerdefinierten Filter konnte nicht entfernt werden.', + 'Edit custom filter' => 'Benutzerdefinierten Filter bearbeiten', + 'Your custom filter have been updated successfully.' => 'Benutzerdefinierten Filter erfolgreich bearbeitet.', + 'Unable to update custom filter.' => 'Benutzerdefinierter Filter konnte nicht geändert werden.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Neuer Anhang für Aufgabe #%d: %s', + 'New comment on task #%d' => 'Neuer Kommentar für Aufgabe #%d', + 'Comment updated on task #%d' => 'Kommentar geändert für Aufgabe #%d', + 'New subtask on task #%d' => 'Neue Teilaufgabe für Aufgabe #%d', + 'Subtask updated on task #%d' => 'Teilaufgabe geändert für Aufgabe #%d', + 'New task #%d: %s' => 'Neue Aufgabe #%d: %s', + 'Task updated #%d' => 'Aufgabe bearbeitet #%d', + 'Task #%d closed' => 'Aufgabe #%d geschlossen', + 'Task #%d opened' => 'Aufgabe #%d eröffnet', + 'Column changed for task #%d' => 'Spalte geändert von Aufgabe #%d', + 'New position for task #%d' => 'Neue Position für Aufgabe #%d', + 'Swimlane changed for task #%d' => 'Neue Swimlane für Aufgabe #%d', + 'Assignee changed on task #%d' => 'Neue Zuordnung für Aufgabe #%d ', + '%d overdue tasks' => '%d überfällige Aufgaben', + 'Task #%d is overdue' => 'Aufgabe #%d ist überfällig', + 'No new notifications.' => 'Keine neuen Benachrichtigungen', + 'Mark all as read' => 'Alles als gelesen markieren', + 'Mark as read' => 'Als gelesen markieren', + 'Total number of tasks in this column across all swimlanes' => 'Anzahl an Aufgaben in dieser Spalte über alle Swimlanes', + 'Collapse swimlane' => 'Swimlane einklappen', + 'Expand swimlane' => 'Swimlane ausklappen', + 'Add a new filter' => 'Neuen Filter hinzufügen', + 'Share with all project members' => 'Mit allen Projektmitgliedern teilen.', + 'Shared' => 'Geteilt', + 'Owner' => 'Eigentümer', + 'Unread notifications' => 'Ungelesene Benachrichtigungen', + 'My filters' => 'Meine Filter', + 'Notification methods:' => 'Benachrichtigungs-Methoden:', ); diff --git a/sources/app/Locale/es_ES/translations.php b/sources/app/Locale/es_ES/translations.php index ac7c0e4..152a617 100644 --- a/sources/app/Locale/es_ES/translations.php +++ b/sources/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,75 +665,26 @@ 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', - 'All day' => 'Todos los días', - 'Day' => 'Día', - 'Manage timetable' => 'Gestionar horario', - 'Overtime timetable' => 'Horario de tiempo extra', - 'Time off timetable' => 'Horario de tiempo libre', - 'Timetable' => 'Horario', - 'Work timetable' => 'Horario de trabajo', - 'Week timetable' => 'Horario semanal', - 'Day timetable' => 'Horario diario', - 'From' => 'De', - 'To' => 'Para', - 'Time slot created successfully.' => 'Intervalo de tiempo creado correctamente.', - 'Unable to save this time slot.' => 'No pude grabar este intervalo de tiempo.', - 'Time slot removed successfully.' => 'Intervalo de tiempo quitado correctamente.', - 'Unable to remove this time slot.' => 'No pude quitar este intervalo de tiempo.', - 'Do you really want to remove this time slot?' => '¿Realmente quiere quitar este intervalo de tiempo?', - 'Remove time slot' => 'Quitar intervalo de tiempo', - 'Add new time slot' => 'Añadir nuevo intervalo de tiempo', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Este horario se usa cuando se marca la casilla "todos los días" para calendario de tiempo libre y horas extras.', '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', 'Source column' => 'Columna fuente', - 'Show subtask estimates (forecast of future work)' => 'Mostrar estimaciones para la subtarea (pronóstico de trabajo futuro)', 'Transitions' => 'Transiciones', 'Executer' => 'Ejecutor', 'Time spent in the column' => 'Tiempo transcurrido en la columna', @@ -746,7 +695,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 +826,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 +985,75 @@ 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', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/fi_FI/translations.php b/sources/app/Locale/fi_FI/translations.php index d8c749a..71844cc 100644 --- a/sources/app/Locale/fi_FI/translations.php +++ b/sources/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,75 +665,26 @@ 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' => '', - // 'All day' => '', - // 'Day' => '', - // 'Manage timetable' => '', - // 'Overtime timetable' => '', - // 'Time off timetable' => '', - // 'Timetable' => '', - // 'Work timetable' => '', - // 'Week timetable' => '', - // 'Day timetable' => '', - // 'From' => '', - // 'To' => '', - // 'Time slot created successfully.' => '', - // 'Unable to save this time slot.' => '', - // 'Time slot removed successfully.' => '', - // 'Unable to remove this time slot.' => '', - // 'Do you really want to remove this time slot?' => '', - // 'Remove time slot' => '', - // 'Add new time slot' => '', - // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', // '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' => '', // 'Source column' => '', - // 'Show subtask estimates (forecast of future work)' => '', // 'Transitions' => '', // 'Executer' => '', // 'Time spent in the column' => '', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/fr_FR/translations.php b/sources/app/Locale/fr_FR/translations.php index 0c95c7e..ec32ecb 100644 --- a/sources/app/Locale/fr_FR/translations.php +++ b/sources/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 ?', - 'Disallow login form' => 'Interdir le formulaire d\'authentification', + 'Which parts of the project do you want to duplicate?' => 'Quelles parties du projet voulez-vous dupliquer ?', + 'Disallow login form' => 'Interdire 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,75 +667,26 @@ 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', - 'All day' => 'Toute la journée', - 'Day' => 'Jour', - 'Manage timetable' => 'Gérer les horaires', - 'Overtime timetable' => 'Heures supplémentaires', - 'Time off timetable' => 'Heures d\'absences', - 'Timetable' => 'Horaires', - 'Work timetable' => 'Horaires travaillés', - 'Week timetable' => 'Horaires de la semaine', - 'Day timetable' => 'Horaire d\'une journée', - 'From' => 'Depuis', - 'To' => 'À', - 'Time slot created successfully.' => 'Créneau horaire créé avec succès.', - 'Unable to save this time slot.' => 'Impossible de sauvegarder ce créneau horaire.', - 'Time slot removed successfully.' => 'Créneau horaire supprimé avec succès.', - 'Unable to remove this time slot.' => 'Impossible de supprimer ce créneau horaire.', - 'Do you really want to remove this time slot?' => 'Voulez-vous vraiment supprimer ce créneau horaire ?', - 'Remove time slot' => 'Supprimer un créneau horaire', - 'Add new time slot' => 'Ajouter un créneau horaire', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ces horaires sont utilisés lorsque la case « Toute la journée » est cochée pour les heures d\'absences ou supplémentaires programmées.', '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', 'Source column' => 'Colonne d\'origine', - 'Show subtask estimates (forecast of future work)' => 'Afficher l\'estimation des sous-tâches (prévision du travail à venir)', 'Transitions' => 'Transitions', 'Executer' => 'Exécutant', 'Time spent in the column' => 'Temps passé dans la colonne', @@ -748,7 +697,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 +717,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 +744,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 +762,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 +784,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 +797,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 +810,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 +828,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 +852,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 +940,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 +968,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 +988,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', @@ -1069,4 +1014,48 @@ return array( 'Table of contents' => 'Table des matières', 'Gantt' => 'Gantt', 'Help with project permissions' => 'Aide avec les permissions des projets', + 'Author' => 'Auteur', + 'Version' => 'Version', + 'Plugins' => 'Extensions', + 'There is no plugin loaded.' => 'Il n\'y a aucune extension chargée.', + 'Set maximum column height' => 'Définir la hauteur max. des colonnes', + 'Remove maximum column height' => 'Enlever la hauteur max. des colonnes', + 'My notifications' => 'Mes notifications', + 'Custom filters' => 'Filtres personalisés', + 'Your custom filter have been created successfully.' => 'Votre filter personalisé a été créé avec succès.', + 'Unable to create your custom filter.' => 'Impossible de créer votre filter personalisé.', + 'Custom filter removed successfully.' => 'Filtre personalisé supprimé avec succès.', + 'Unable to remove this custom filter.' => 'Impossible de supprimer ce filter personalisé.', + 'Edit custom filter' => 'Modification d\'un filtre personalisé', + 'Your custom filter have been updated successfully.' => 'Votre filtre personalisé a été mis à jour avec succès.', + 'Unable to update custom filter.' => 'Impossible de mettre à jour votre filtre personalisé.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nouveau fichier joint sur la tâche n°%d : %s', + 'New comment on task #%d' => 'Nouveau commentaire sur la tâche n°%d', + 'Comment updated on task #%d' => 'Commentaire mis à jour sur la tâche n°%d', + 'New subtask on task #%d' => 'Nouvelle sous-tâche sur la tâche n°%d', + 'Subtask updated on task #%d' => 'Sous-tâche mise à jour sur la tâche n°%d', + 'New task #%d: %s' => 'Nouvelle tâche n°%d : %s', + 'Task updated #%d' => 'Tâche n°%d mise à jour', + 'Task #%d closed' => 'Tâche n°%d fermée', + 'Task #%d opened' => 'Tâche n°%d ouverte', + 'Column changed for task #%d' => 'Changement de colonne pour la tâche n°%d', + 'New position for task #%d' => 'Nouvelle position pour la tâche n°%d', + 'Swimlane changed for task #%d' => 'Changement de swimlane pour la tâche n°%d', + 'Assignee changed on task #%d' => 'Changement de l\'assigné pour la tâche n°%d', + '%d overdue tasks' => '%d tâches en retard', + 'Task #%d is overdue' => 'La tâche n°%d est retard', + 'No new notifications.' => 'Aucune notification.', + 'Mark all as read' => 'Tout marquer comme lu', + 'Mark as read' => 'Marquer comme lu', + 'Total number of tasks in this column across all swimlanes' => 'Nombre total de tâches dans cette colonne pour toutes les swimlanes', + 'Collapse swimlane' => 'Replier la swimlane', + 'Expand swimlane' => 'Déplier la swimlane', + 'Add a new filter' => 'Ajouter un nouveau filtre', + 'Share with all project members' => 'Partager avec tous les membres du projet', + 'Shared' => 'Partagé', + 'Owner' => 'Propriétaire', + 'Unread notifications' => 'Notifications non lus', + 'My filters' => 'Mes filtres', + 'Notification methods:' => 'Méthodes de notifications :', ); diff --git a/sources/app/Locale/hu_HU/translations.php b/sources/app/Locale/hu_HU/translations.php index b346f1e..bef6557 100644 --- a/sources/app/Locale/hu_HU/translations.php +++ b/sources/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,75 +665,26 @@ 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', - 'All day' => 'Egész nap', - 'Day' => 'Nap', - 'Manage timetable' => 'Időbeosztás kezelése', - 'Overtime timetable' => 'Túlóra időbeosztás', - 'Time off timetable' => 'Szabadság időbeosztás', - 'Timetable' => 'Időbeosztás', - 'Work timetable' => 'Munka időbeosztás', - 'Week timetable' => 'Heti időbeosztás', - 'Day timetable' => 'Napi időbeosztás', - 'From' => 'Feladó:', - 'To' => 'Címzett:', - 'Time slot created successfully.' => 'Időszelet sikeresen létrehozva.', - 'Unable to save this time slot.' => 'Időszelet mentése sikertelen.', - 'Time slot removed successfully.' => 'Időszelet sikeresen törölve.', - 'Unable to remove this time slot.' => 'Időszelet törlése sikertelen.', - 'Do you really want to remove this time slot?' => 'Biztos törli ezt az időszeletet?', - 'Remove time slot' => 'Időszelet törlése', - 'Add new time slot' => 'Új Időszelet', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ez az időbeosztás van használatban ha az "egész nap" jelölőnégyzet be van jelölve a tervezett szabadságnál és túlóránál.', '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', 'Source column' => 'Forrás oszlop', - // 'Show subtask estimates (forecast of future work)' => '', // 'Transitions' => '', // 'Executer' => '', // 'Time spent in the column' => '', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/id_ID/translations.php b/sources/app/Locale/id_ID/translations.php new file mode 100644 index 0000000..03c96b8 --- /dev/null +++ b/sources/app/Locale/id_ID/translations.php @@ -0,0 +1,1059 @@ + ',', + '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 diperbaharui', + 'New comment posted by %s' => 'Komentar baru ditambahkan oleh « %s »', + 'New attachment' => 'Lampirkan baru', + 'New comment' => 'Komentar baru', + 'New subtask' => 'Sub-tugas baru', + 'Subtask updated' => 'Sub-tugas diperbaharui', + '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', + '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 :', + 'Currency' => 'Mata uang', + 'Files' => 'Arsip', + 'Images' => 'Gambar', + 'Private project' => 'Proyek pribadi', + 'AUD - Australian Dollar' => 'AUD - Dollar Australia', + 'CAD - Canadian Dollar' => 'CAD - Dollar Kanada', + 'CHF - Swiss Francs' => 'CHF - Swiss Prancis', + 'Custom Stylesheet' => 'Kustomisasi Stylesheet', + 'download' => 'unduh', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Poundsterling inggris', + 'INR - Indian Rupee' => 'INR - Rupe India', + 'JPY - Japanese Yen' => 'JPY - Yen Jepang', + 'NZD - New Zealand Dollar' => 'NZD - Dollar Selandia baru', + 'RSD - Serbian dinar' => 'RSD - Dinar Serbia', + 'USD - US Dollar' => 'USD - Dollar Amerika', + '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', + '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', + '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', + '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', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', +); diff --git a/sources/app/Locale/it_IT/translations.php b/sources/app/Locale/it_IT/translations.php index 06e2c5c..a9f19d8 100644 --- a/sources/app/Locale/it_IT/translations.php +++ b/sources/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,75 +665,26 @@ 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', - 'All day' => 'Tutto il giorno', - 'Day' => 'Giorno', - 'Manage timetable' => 'Gestisci orario', - 'Overtime timetable' => 'Straordinari', - 'Time off timetable' => 'Fuori orario', - 'Timetable' => 'Orario', - 'Work timetable' => 'Orario di lavoro', - 'Week timetable' => 'Orario settimanale', - 'Day timetable' => 'Orario giornaliero', - 'From' => 'Da', - 'To' => 'A', - 'Time slot created successfully.' => 'Fascia oraria creata con successo.', - 'Unable to save this time slot.' => 'Impossibile creare questa fascia oraria.', - 'Time slot removed successfully.' => 'Fascia oraria rimossa con successo.', - 'Unable to remove this time slot.' => 'Impossibile rimuovere questa fascia oraria.', - 'Do you really want to remove this time slot?' => 'Vuoi davvero rimuovere questa fascia oraria?', - 'Remove time slot' => 'Rimuovi fascia oraria', - 'Add new time slot' => 'Aggiungi nuova fascia oraria', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Questo orario è utilizzato quando la casella "tutto il giorno" è selezionata per i fuori orari e per gli straordinari', // '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', 'Source column' => 'Colonna sorgente', - // 'Show subtask estimates (forecast of future work)' => '', 'Transitions' => 'Transizioni', 'Executer' => 'Esecutore', 'Time spent in the column' => 'Tempo trascorso nella colonna', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/ja_JP/translations.php b/sources/app/Locale/ja_JP/translations.php index cb8c550..001fd01 100644 --- a/sources/app/Locale/ja_JP/translations.php +++ b/sources/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,75 +665,26 @@ 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' => 'コメント', - 'All day' => '終日', - 'Day' => '日', - 'Manage timetable' => 'タイムテーブルの管理', - 'Overtime timetable' => '残業タイムテーブル', - 'Time off timetable' => '休暇タイムテーブル', - 'Timetable' => 'タイムテーブル', - 'Work timetable' => 'ワークタイムテーブル', - 'Week timetable' => '週次タイムテーブル', - 'Day timetable' => '日時タイムテーブル', - 'From' => 'ここから', - 'To' => 'ここまで', - // 'Time slot created successfully.' => '', - // 'Unable to save this time slot.' => '', - // 'Time slot removed successfully.' => '', - // 'Unable to remove this time slot.' => '', - // 'Do you really want to remove this time slot?' => '', - 'Remove time slot' => 'タイムスロットの削除', - 'Add new time slot' => 'タイムラインの追加', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'このタイムテーブルは、残業や休暇で全日がチェックされた場合に用いられます。', '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' => 'ユーザの割り当てがなくなったらタスクを他のカラムに移動', 'Source column' => '移動元のカラム', - // 'Show subtask estimates (forecast of future work)' => '', 'Transitions' => '履歴', 'Executer' => '実行者', 'Time spent in the column' => 'カラムでの時間消費', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/nb_NO/translations.php b/sources/app/Locale/nb_NO/translations.php index 155d49b..b1aaf48 100644 --- a/sources/app/Locale/nb_NO/translations.php +++ b/sources/app/Locale/nb_NO/translations.php @@ -1,8 +1,8 @@ '', - // 'number.thousands_separator' => '', + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', 'None' => 'Ingen', 'edit' => 'rediger', 'Edit' => 'Rediger', @@ -79,7 +79,7 @@ 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', @@ -100,7 +100,7 @@ return array( '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"?', + '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', @@ -121,7 +121,7 @@ return array( '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 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', @@ -149,7 +149,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.', @@ -175,10 +175,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', @@ -205,16 +205,16 @@ 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', @@ -236,13 +236,13 @@ 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', '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', @@ -275,7 +275,7 @@ 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', 'Task creation or modification' => 'Oppgaveopprettelse eller endring', @@ -293,7 +293,7 @@ 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"?', 'All categories' => 'Alle kategorier', 'No category' => 'Ingen kategori', 'The name is required' => 'Navnet er påkrevet', @@ -301,9 +301,9 @@ return array( '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,7 +312,7 @@ return array( 'Time tracking' => 'Tidsregistrering', 'Estimate:' => 'Estimat:', 'Spent:' => 'Brukt:', - 'Do you really want to remove this sub-task?' => 'Vil du virkelig fjerne denne deloppgaven?', + 'Do you really want to remove this sub-task?' => 'Vil du fjerne denne deloppgaven?', 'Remaining:' => 'Gjenværende:', 'hours' => 'timer', 'spent' => 'brukt', @@ -354,7 +354,7 @@ 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 closed.' => 'Oppgaven #%d er lukket.', @@ -382,12 +382,12 @@ return array( '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', @@ -395,8 +395,6 @@ return array( 'Remote' => 'Fjernstyrt', 'Enabled' => 'Aktiv', 'Disabled' => 'Deaktivert', - 'Google account linked' => 'Google-konto knyttet', - 'Github account linked' => 'GitHub-konto knyttet', 'Username:' => 'Brukernavn', 'Name:' => 'Navn:', 'Email:' => 'Epost:', @@ -411,7 +409,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.', @@ -466,7 +464,7 @@ return array( '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 +473,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 +483,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 +508,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,9 +534,9 @@ 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', @@ -546,7 +544,7 @@ return array( 'Nothing to preview...' => 'Ingenting å forhåndsvise', 'Preview' => 'Forhåndsvisning', 'Write' => 'Skriv', - 'Active swimlanes' => 'Aktive svæmmebaner', + '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', @@ -558,10 +556,10 @@ return array( 'Rename' => 'Endre navn', 'Show default swimlane' => 'Vis standard svømmebane', // 'Swimlane modification for the project "%s"' => '', - // 'Swimlane not found.' => '', - // 'Swimlane removed successfully.' => '', + 'Swimlane not found.' => 'Svømmebane ikke funnet', + 'Swimlane removed successfully.' => 'Svømmebane fjernet', 'Swimlanes' => 'Svømmebaner', - 'Swimlane updated successfully.' => 'Svæmmebane oppdatert', + '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 +574,27 @@ 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', 'Timezone:' => 'Tidssone', - // 'All columns' => '', + 'All columns' => 'Alle kolonner', 'Calendar' => 'Kalender', - // 'Next' => '', + 'Next' => 'Neste', // '#%d' => '', - // 'All swimlanes' => '', - // 'All colors' => '', - // 'All status' => '', + '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', @@ -612,8 +610,8 @@ return array( // '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 +619,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', @@ -667,75 +665,26 @@ return array( 'Horizontal scrolling' => 'Bla horisontalt', 'Compact/wide view' => 'Kompakt/bred visning', 'No results match:' => 'Ingen resultater', - // 'Remove hourly rate' => '', - // 'Do you really want to remove this hourly rate?' => '', - 'Hourly rates' => 'Timepriser', - 'Hourly rate' => 'Timepris', 'Currency' => 'Valuta', - // 'Effective date' => '', - // 'Add new rate' => '', - // 'Rate removed successfully.' => '', - // 'Unable to remove this rate.' => '', - // 'Unable to save the hourly rate.' => '', - // 'Hourly rate created successfully.' => '', - // 'Start time' => '', - // 'End time' => '', - // 'Comment' => '', - // 'All day' => '', - // 'Day' => '', - 'Manage timetable' => 'Tidstabell', - 'Overtime timetable' => 'Overtidstabell', - 'Time off timetable' => 'Fritidstabell', - 'Timetable' => 'Tidstabell', - 'Work timetable' => 'Arbeidstidstabell', - 'Week timetable' => 'Uketidstabell', - 'Day timetable' => 'Dagtidstabell', - 'From' => 'Fra', - 'To' => 'Til', - // 'Time slot created successfully.' => '', - // 'Unable to save this time slot.' => '', - // 'Time slot removed successfully.' => '', - // 'Unable to remove this time slot.' => '', - // 'Do you really want to remove this time slot?' => '', - // 'Remove time slot' => '', - // 'Add new time slot' => '', - // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', 'Files' => 'Filer', 'Images' => 'Bilder', 'Private project' => 'Privat prosjekt', - // 'Amount' => '', // 'AUD - Australian Dollar' => '', - 'Budget' => 'Budsjett', - // '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?' => '', + 'download' => 'last ned', // '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' => '', + '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' => '', - // 'Show subtask estimates (forecast of future work)' => '', + 'Source column' => 'Opprinnelig kolonne', 'Transitions' => 'Statusendringer', // 'Executer' => '', // 'Time spent in the column' => '', @@ -746,7 +695,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.' => '', @@ -782,7 +730,7 @@ 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', @@ -853,7 +801,7 @@ 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' => '', @@ -877,10 +825,7 @@ return array( 'Notification' => 'Varsel', // '%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.' => '', + 'Swimlane' => 'Svømmebane', // 'Gravatar' => '', // 'Hipchat' => '', // 'Slack' => '', @@ -908,19 +853,19 @@ 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', + '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' => '', + 'Total for all columns' => 'Totalt for alle kolonner', // 'You need at least 2 days of data to show the chart.' => '', // '<15m' => '', // '<30m' => '', @@ -928,41 +873,41 @@ return array( '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 +941,119 @@ 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', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/nl_NL/translations.php b/sources/app/Locale/nl_NL/translations.php index 23d6416..5ee15c2 100644 --- a/sources/app/Locale/nl_NL/translations.php +++ b/sources/app/Locale/nl_NL/translations.php @@ -313,7 +313,7 @@ return array( 'Estimate:' => 'Schatting :', 'Spent:' => 'Besteed :', 'Do you really want to remove this sub-task?' => 'Weet u zeker dat u deze subtaak wil verwijderen ?', - 'Remaining:' => 'Restant :', + 'Remaining:' => 'Resterend :', 'hours' => 'uren', 'spent' => 'besteed', 'estimated' => 'geschat', @@ -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,75 +665,26 @@ 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' => '', - // 'All day' => '', - // 'Day' => '', - // 'Manage timetable' => '', - // 'Overtime timetable' => '', - // 'Time off timetable' => '', - // 'Timetable' => '', - // 'Work timetable' => '', - // 'Week timetable' => '', - // 'Day timetable' => '', - // 'From' => '', - // 'To' => '', - // 'Time slot created successfully.' => '', - // 'Unable to save this time slot.' => '', - // 'Time slot removed successfully.' => '', - // 'Unable to remove this time slot.' => '', - // 'Do you really want to remove this time slot?' => '', - // 'Remove time slot' => '', - // 'Add new time slot' => '', - // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', // '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' => '', // 'Source column' => '', - // 'Show subtask estimates (forecast of future work)' => '', // 'Transitions' => '', // 'Executer' => '', // 'Time spent in the column' => '', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/pl_PL/translations.php b/sources/app/Locale/pl_PL/translations.php index 9947cf3..f0d43ad 100644 --- a/sources/app/Locale/pl_PL/translations.php +++ b/sources/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,75 +665,26 @@ 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', - 'All day' => 'Cały dzień', - 'Day' => 'Dzień', - 'Manage timetable' => 'Zarządzaj rozkładami zajęć', - 'Overtime timetable' => 'Rozkład zajęć - nadgodziny', - 'Time off timetable' => 'Rozkład zajęć - czas wolny', - 'Timetable' => 'Rozkład zajęć', - 'Work timetable' => 'Rozkład zajęć - praca', - 'Week timetable' => 'Tygodniowy rozkład zajęć', - 'Day timetable' => 'Dzienny rozkład zajęć', - 'From' => 'Od', - 'To' => 'Do', - 'Time slot created successfully.' => 'Przydział czasowy utworzony.', - 'Unable to save this time slot.' => 'Nie można zapisać tego przydziału czasowego.', - 'Time slot removed successfully.' => 'Przydział czasowy usunięty.', - 'Unable to remove this time slot.' => 'Nie można usunąć tego przydziału czasowego.', - 'Do you really want to remove this time slot?' => 'Czy na pewno chcesz usunąć ten przedział czasowy?', - 'Remove time slot' => 'Usuń przedział czasowy', - 'Add new time slot' => 'Dodaj przedział czasowy', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ten rozkład zajęć jest używany przypadku zaznaczenia "cały dzień" dla zaplanowanego czasu wolnego i nadgodzin', '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', 'Source column' => 'Kolumna źródłowa', - 'Show subtask estimates (forecast of future work)' => 'Pokaż planowane czasy wykonania pod-zadań', 'Transitions' => 'Przeniesienia', 'Executer' => 'Wykonał', 'Time spent in the column' => 'Czas spędzony w tej kolumnie', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/pt_BR/translations.php b/sources/app/Locale/pt_BR/translations.php index 6aa2f4d..52cb14c 100644 --- a/sources/app/Locale/pt_BR/translations.php +++ b/sources/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,75 +665,26 @@ 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', - 'All day' => 'Dia inteiro', - 'Day' => 'Dia', - 'Manage timetable' => 'Gestão dos horários', - 'Overtime timetable' => 'Horas extras', - 'Time off timetable' => 'Horas de ausência', - 'Timetable' => 'Horários', - 'Work timetable' => 'Horas trabalhadas', - 'Week timetable' => 'Horário da semana', - 'Day timetable' => 'Horário de un dia', - 'From' => 'Desde', - 'To' => 'A', - 'Time slot created successfully.' => 'Intervalo de tempo criado com sucesso.', - 'Unable to save this time slot.' => 'Impossível de guardar este intervalo de tempo.', - 'Time slot removed successfully.' => 'Intervalo de tempo removido com sucesso.', - 'Unable to remove this time slot.' => 'Impossível de remover esse intervalo de tempo.', - 'Do you really want to remove this time slot?' => 'Você deseja realmente remover este intervalo de tempo?', - 'Remove time slot' => 'Remover um intervalo de tempo', - 'Add new time slot' => 'Adicionar um intervalo de tempo', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Esses horários são usados quando a caixa de seleção "Dia inteiro" está marcada para Horas de ausência ou Extras', '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', 'Source column' => 'Coluna de origem', - 'Show subtask estimates (forecast of future work)' => 'Mostrar a estimativa das subtarefas (previsão para o trabalho futuro)', 'Transitions' => 'Transições', 'Executer' => 'Executor(a)', 'Time spent in the column' => 'Tempo gasto na coluna', @@ -746,7 +695,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 +826,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 +1005,55 @@ 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', + 'Author' => 'Autor', + 'Version' => 'Versão', + 'Plugins' => 'Extensão', + 'There is no plugin loaded.' => 'Não há nenhuma extensão carga.', + 'Set maximum column height' => 'Definir a altura máxima das colunas', + 'Remove maximum column height' => 'Retirar a altura máxima das colunas', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/pt_PT/translations.php b/sources/app/Locale/pt_PT/translations.php index d19a31e..ac8961a 100644 --- a/sources/app/Locale/pt_PT/translations.php +++ b/sources/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,75 +665,26 @@ 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', - 'All day' => 'Dia inteiro', - 'Day' => 'Dia', - 'Manage timetable' => 'Gestão dos horários', - 'Overtime timetable' => 'Horas extras', - 'Time off timetable' => 'Horas de ausência', - 'Timetable' => 'Horários', - 'Work timetable' => 'Horas trabalhadas', - 'Week timetable' => 'Horário da semana', - 'Day timetable' => 'Horário de um dia', - 'From' => 'Desde', - 'To' => 'A', - 'Time slot created successfully.' => 'Intervalo de tempo criado com sucesso.', - 'Unable to save this time slot.' => 'Impossível guardar este intervalo de tempo.', - 'Time slot removed successfully.' => 'Intervalo de tempo removido com sucesso.', - 'Unable to remove this time slot.' => 'Impossível remover esse intervalo de tempo.', - 'Do you really want to remove this time slot?' => 'Tem a certeza que quer remover este intervalo de tempo?', - 'Remove time slot' => 'Remover um intervalo de tempo', - 'Add new time slot' => 'Adicionar um intervalo de tempo', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Esses horários são usados quando a caixa de seleção "Dia inteiro" está marcada para Horas de ausência ou Extras', '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', 'Source column' => 'Coluna de origem', - 'Show subtask estimates (forecast of future work)' => 'Mostrar a estimativa das subtarefas (previsão para o trabalho futuro)', 'Transitions' => 'Transições', 'Executer' => 'Executor(a)', 'Time spent in the column' => 'Tempo gasto na coluna', @@ -746,7 +695,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 +826,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 +1005,55 @@ return array( 'Gitlab Account' => 'Conta Gitlab', 'Link my Gitlab Account' => 'Connectar a minha Conta Gitlab', 'Unlink my Gitlab Account' => 'Desconectar a 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 vista de gráfico de Gantt', + 'Reset the search/filter box' => 'Repor caixa de procura/filtro', + 'Documentation' => 'Documentação', + 'Table of contents' => 'Tabela de conteúdos', + 'Gantt' => 'Gantt', + 'Help with project permissions' => 'Ajuda com permissões de projecto', + 'Author' => 'Autor', + 'Version' => 'Versão', + 'Plugins' => 'Extras', + 'There is no plugin loaded.' => 'Não existem extras carregados', + 'Set maximum column height' => 'Definir altura máxima da coluna', + 'Remove maximum column height' => 'Remover altura máxima da coluna', + 'My notifications' => 'As minhas notificações', + 'Custom filters' => 'Filtros personalizados', + 'Your custom filter have been created successfully.' => 'O seu filtro personalizado foi criado com sucesso.', + 'Unable to create your custom filter.' => 'Não foi possivel criar o seu filtro personalizado.', + 'Custom filter removed successfully.' => 'Filtro personalizado removido com sucesso.', + 'Unable to remove this custom filter.' => 'Não foi possivel remover este filtro personalizado.', + 'Edit custom filter' => 'Editar filtro personalizado', + 'Your custom filter have been updated successfully.' => 'O seu filtro personalizado foi actualizado com sucesso.', + 'Unable to update custom filter.' => 'Não foi possivel actualizar o filtro personalizado.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Novo anexo na tarefa #%d: %s', + 'New comment on task #%d' => 'Novo comentário na tarefa #%d', + 'Comment updated on task #%d' => 'Comentário actualizado na tarefa #%d', + 'New subtask on task #%d' => 'Nova sub-tarefa na tarefa #%d', + 'Subtask updated on task #%d' => 'Sub-tarefa actualizada na tarefa #%d', + 'New task #%d: %s' => 'Nova tarefa #%d: %s', + 'Task updated #%d' => 'Tarefa actualizada #%d', + 'Task #%d closed' => 'Tarefa #%d fechada', + 'Task #%d opened' => 'Tarefa #%d aberta', + 'Column changed for task #%d' => 'Coluna alterada para tarefa #%d', + 'New position for task #%d' => 'Nova posição para tarefa #%d', + 'Swimlane changed for task #%d' => 'Swimlane alterado na tarefa #%d', + 'Assignee changed on task #%d' => 'Assignado alterado na tarefa #%d', + '%d overdue tasks' => '%d tarefas em atraso', + 'Task #%d is overdue' => 'Tarefa #%d está em atraso', + 'No new notifications.' => 'Sem novas notificações.', + 'Mark all as read' => 'Marcar tudo como lido', + 'Mark as read' => 'Marcar como lido', + 'Total number of tasks in this column across all swimlanes' => 'Número total de tarefas nesta coluna em todas as swimlanes', + 'Collapse swimlane' => 'Colapsar swimlane', + 'Expand swimlane' => 'Expandir swimlane', + 'Add a new filter' => 'Adicionar um novo filtro', + 'Share with all project members' => 'Partilhar com todos os membros do projecto', + 'Shared' => 'Partilhado', + 'Owner' => 'Dono', + 'Unread notifications' => 'Notificações por ler', + 'My filters' => 'Os meus filtros', + 'Notification methods:' => 'Metodos de notificação:', ); diff --git a/sources/app/Locale/ru_RU/translations.php b/sources/app/Locale/ru_RU/translations.php index feb1b68..4a64e06 100644 --- a/sources/app/Locale/ru_RU/translations.php +++ b/sources/app/Locale/ru_RU/translations.php @@ -20,15 +20,15 @@ return array( 'Red' => 'Красный', 'Orange' => 'Оранжевый', 'Grey' => 'Серый', - // 'Brown' => '', - // 'Deep Orange' => '', - // 'Dark Grey' => '', - // 'Pink' => '', - // 'Teal' => '', - // 'Cyan' => '', - // 'Lime' => '', - // 'Light Green' => '', - // 'Amber' => '', + 'Brown' => 'Коричневый', + 'Deep Orange' => 'Темно-оранжевый', + 'Dark Grey' => 'Темно-серый', + 'Pink' => 'Розовый', + 'Teal' => 'Бирюзовый', + 'Cyan' => 'Голубой', + 'Lime' => 'Лимонный', + 'Light Green' => 'Светло-зеленый', + 'Amber' => 'Янтарный', 'Save' => 'Сохранить', 'Login' => 'Вход', 'Official website:' => 'Официальный сайт:', @@ -61,27 +61,27 @@ return array( 'Inactive' => 'Неактивен', 'Active' => 'Активен', 'Add this column' => 'Добавить колонку', - '%d tasks on the board' => 'Задач на доске - %d', - '%d tasks in total' => 'Задач всего - %d', - 'Unable to update this board.' => 'Не удалось обновить доску.', - 'Edit board' => 'Изменить доски', - 'Disable' => 'Деактивировать', - 'Enable' => 'Активировать', + '%d tasks on the board' => '%d задач на доске', + '%d tasks in total' => 'всего %d задач', + 'Unable to update this board.' => 'Не удалось обновить эту доску.', + 'Edit board' => 'Изменить доску', + 'Disable' => 'Выключить', + 'Enable' => 'Включить', 'New project' => 'Новый проект', - 'Do you really want to remove this project: "%s"?' => 'Вы точно хотите удалить проект: « %s » ?', + 'Do you really want to remove this project: "%s"?' => 'Вы точно хотите удалить проект: "%s"?', 'Remove project' => 'Удалить проект', - 'Edit the board for "%s"' => 'Изменить доску для « %s »', + 'Edit the board for "%s"' => 'Изменить доску для "%s"', 'All projects' => 'Все проекты', 'Change columns' => 'Изменить колонки', 'Add a new column' => 'Добавить новую колонку', 'Title' => 'Название', 'Nobody assigned' => 'Никто не назначен', - 'Assigned to %s' => 'Исполнитель: %s', + 'Assigned to %s' => 'Назначено %s', 'Remove a column' => 'Удалить колонку', 'Remove a column from a board' => 'Удалить колонку с доски', - 'Unable to remove this column.' => 'Не удалось удалить колонку.', - 'Do you really want to remove this column: "%s"?' => 'Вы точно хотите удалить эту колонку: « %s » ?', - 'This action will REMOVE ALL TASKS associated to this column!' => 'Вы УДАЛИТЕ ВСЕ ЗАДАЧИ находящиеся в этой колонке !', + 'Unable to remove this column.' => 'Не удалось удалить эту колонку.', + 'Do you really want to remove this column: "%s"?' => 'Вы точно хотите удалить эту колонку: "%s" ?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Вы УДАЛИТЕ ВСЕ ЗАДАЧИ находящиеся в этой колонке!', 'Settings' => 'Настройки', 'Application settings' => 'Настройки приложения', 'Language' => 'Язык', @@ -100,32 +100,32 @@ return array( 'Create another task' => 'Создать другую задачу', 'New task' => 'Новая задача', 'Open a task' => 'Открыть задачу', - 'Do you really want to open this task: "%s"?' => 'Вы уверены что хотите открыть задачу: « %s » ?', + 'Do you really want to open this task: "%s"?' => 'Вы уверены что хотите открыть задачу: "%s" ?', 'Back to the board' => 'Вернуться на доску', - 'Created on %B %e, %Y at %k:%M %p' => 'Создано %d/%m/%Y в %H:%M', + 'Created on %B %e, %Y at %k:%M %p' => 'Создано %B /%e /%Y в %k:%M %p', 'There is nobody assigned' => 'Никто не назначен', - 'Column on the board:' => 'Колонка на доске : ', + 'Column on the board:' => 'Колонка на доске: ', 'Status is open' => 'Статус - открыт', 'Status is closed' => 'Статус - закрыт', - 'Close this task' => 'Закрыть эту задачу', - 'Open this task' => 'Открыть эту задачу', + 'Close this task' => 'Закрыть задачу', + 'Open this task' => 'Открыть задачу', 'There is no description.' => 'Нет описания.', 'Add a new task' => 'Добавить новую задачу', - 'The username is required' => 'Требуется имя пользователя', + 'The username is required' => 'Необходимо имя пользователя', 'The maximum length is %d characters' => 'Максимальная длина - %d знаков', 'The minimum length is %d characters' => 'Минимальная длина - %d знаков', - 'The password is required' => 'Требуется пароль', - 'This value must be an integer' => 'Это значение должно быть целым', - 'The username must be unique' => 'Требуется уникальное имя пользователя', - 'The user id is required' => 'Требуется ID пользователя', + 'The password is required' => 'Необходим пароль', + 'This value must be an integer' => 'Это значение должно быть целым числом', + 'The username must be unique' => 'Имя пользователя должно быть уникально', + 'The user id is required' => 'Необходим ID пользователя', 'Passwords don\'t match' => 'Пароли не совпадают', - 'The confirmation is required' => 'Требуется подтверждение', - 'The project is required' => 'Требуется проект', - 'The id is required' => 'Требуется ID', - 'The project id is required' => 'Требуется ID проекта', - 'The project name is required' => 'Требуется имя проекта', + 'The confirmation is required' => 'Необходимо подтверждение', + 'The project is required' => 'Необъодимо указать проект', + 'The id is required' => 'Необходим ID', + 'The project id is required' => 'Необходим ID проекта', + 'The project name is required' => 'Необходимо имя проекта', 'This project must be unique' => 'Проект должен быть уникальным', - 'The title is required' => 'Требуется заголовок', + 'The title is required' => 'Необходим заголовок', 'Settings saved successfully.' => 'Параметры успешно сохранены.', 'Unable to save your settings.' => 'Невозможно сохранить параметры.', 'Database optimization done.' => 'База данных оптимизирована.', @@ -157,7 +157,7 @@ return array( 'Ready' => 'Готовые', 'Backlog' => 'Ожидающие', 'Work in progress' => 'В процессе', - 'Done' => 'Выполнена', + 'Done' => 'Выполнено', 'Application version:' => 'Версия приложения:', 'Completed on %B %e, %Y at %k:%M %p' => 'Завершен %d/%m/%Y в %H:%M', '%B %e, %Y at %k:%M %p' => '%d/%m/%Y в %H:%M', @@ -193,9 +193,9 @@ return array( 'Edit this task' => 'Изменить задачу', 'Due Date' => 'Сделать до', 'Invalid date' => 'Неверная дата', - 'Must be done before %B %e, %Y' => 'Должно быть сделано до %d/%m/%Y', - '%B %e, %Y' => '%d/%m/%Y', - // '%b %e, %Y' => '', + 'Must be done before %B %e, %Y' => 'Должно быть сделано до %B %e %Y', + '%B %e, %Y' => '%B, %e, %Y', + '%b %e, %Y' => '%b %e, %Y', 'Automatic actions' => 'Автоматические действия', 'Your automatic action have been created successfully.' => 'Автоматика успешно настроена.', 'Unable to create your automatic action.' => 'Не удалось создать автоматизированное действие.', @@ -263,10 +263,10 @@ return array( '%d comments' => '%d комментариев', '%d comment' => '%d комментарий', 'Email address invalid' => 'Некорректный e-mail адрес', - // '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 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.' => 'Ваш внешний аккаунт успешно подключен к профилю.', 'Email' => 'E-mail', 'Link my Google Account' => 'Привязать мой профиль к Google', 'Unlink my Google Account' => 'Отвязать мой профиль от Google', @@ -310,7 +310,7 @@ return array( 'Edit a comment' => 'Изменить комментарий', 'Summary' => 'Сводка', 'Time tracking' => 'Отслеживание времени', - 'Estimate:' => 'Приблизительно:', + 'Estimate:' => 'Запланировано:', 'Spent:' => 'Затрачено:', 'Do you really want to remove this sub-task?' => 'Вы точно хотите удалить подзадачу?', 'Remaining:' => 'Осталось:', @@ -319,7 +319,7 @@ return array( 'estimated' => 'расчетное', 'Sub-Tasks' => 'Подзадачи', 'Add a sub-task' => 'Добавить подзадачу', - 'Original estimate' => 'Первичная оценка', + 'Original estimate' => 'Запланировано', 'Create another sub-task' => 'Создать другую подзадачу', 'Time spent' => 'Времени затрачено', 'Edit a sub-task' => 'Изменить подзадачу', @@ -395,8 +395,6 @@ return array( 'Remote' => 'Удаленный', 'Enabled' => 'Включен', 'Disabled' => 'Выключены', - 'Google account linked' => 'Профиль Google связан', - 'Github account linked' => 'Профиль Github связан', 'Username:' => 'Имя пользователя:', 'Name:' => 'Имя:', 'Email:' => 'E-mail:', @@ -487,11 +485,11 @@ return array( 'This project is private' => 'Это проект с ограниченным доступом', 'Type here to create a new sub-task' => 'Печатайте сюда чтобы создать подзадачу', 'Add' => 'Добавить', - 'Estimated time: %s hours' => 'Планируемое время: %s часов', + 'Estimated time: %s hours' => 'Запланировано: %s часов', 'Time spent: %s hours' => 'Потрачено времени: %s часов', 'Started on %B %e, %Y' => 'Начато %B %e, %Y', 'Start date' => 'Дата начала', - 'Time estimated' => 'Планируемое время', + 'Time estimated' => 'Запланировано', 'There is nothing assigned to you.' => 'Вам ничего не назначено', 'My tasks' => 'Мои задачи', 'Activity stream' => 'Текущая активность', @@ -522,7 +520,7 @@ return array( 'User repartition for "%s"' => 'Перераспределение пользователей для "%s"', 'Clone this project' => 'Клонировать проект', 'Column removed successfully.' => 'Колонка успешно удалена.', - // 'Github Issue' => '', + 'Github Issue' => 'Вопрос на Github', 'Not enough data to show the graph.' => 'Недостаточно данных, чтобы показать график.', 'Previous' => 'Предыдущий', 'The id must be an integer' => 'Этот id должен быть целочисленным', @@ -593,7 +591,7 @@ return array( 'All columns' => 'Все колонки', 'Calendar' => 'Календарь', 'Next' => 'Следующий', - // '#%d' => '', + '#%d' => '#%d', 'All swimlanes' => 'Все дорожки', 'All colors' => 'Все цвета', 'All status' => 'Все статусы', @@ -608,7 +606,7 @@ return array( 'Time Tracking' => 'Учет времени', 'You already have one subtask in progress' => 'У вас уже есть одна задача в разработке', 'Which parts of the project do you want to duplicate?' => 'Какие части проекта должны быть дублированы?', - // 'Disallow login form' => '', + 'Disallow login form' => 'Запретить форму входа', // 'Bitbucket commit received' => '', 'Bitbucket webhooks' => 'BitBucket webhooks', 'Help on Bitbucket webhooks' => 'Помощь по BitBucket webhooks', @@ -616,7 +614,7 @@ return array( 'End' => 'Конец', 'Task age in days' => 'Возраст задачи в днях', 'Days in this column' => 'Дней в этой колонке', - // '%dd' => '', + '%dd' => '%dd', 'Add a link' => 'Добавить ссылку на другие задачи', 'Add a new link' => 'Добавление новой ссылки', 'Do you really want to remove this link: "%s"?' => 'Вы уверены что хотите удалить ссылку: "%s"?', @@ -638,21 +636,21 @@ return array( 'Unable to create your link.' => 'Не удается создать эту ссылку.', 'Unable to update your link.' => 'Не удается обновить эту ссылку.', 'Unable to remove this link.' => 'Не удается удалить эту ссылку.', - 'relates to' => 'связана с', - 'blocks' => 'блокирует', - 'is blocked by' => 'заблокирована в', - 'duplicates' => 'дублирует', - 'is duplicated by' => 'дублирована в', - 'is a child of' => 'наследник', - 'is a parent of' => 'родитель', - 'targets milestone' => 'часть этапа', - 'is a milestone of' => 'является частью этапа', - 'fixes' => 'исправляет', - 'is fixed by' => 'исправлено в', + 'relates to' => 'относится к', + 'blocks' => 'блокирована', + 'is blocked by' => 'блокирует', + 'duplicates' => 'дублирована', + 'is duplicated by' => 'дублирует', + 'is a child of' => 'является продолжением', + 'is a parent of' => 'является началом для', + 'targets milestone' => 'часть вехи', + 'is a milestone of' => 'является вехой для', + 'fixes' => 'исправлено', + 'is fixed by' => 'исправляет', 'This task' => 'Эта задача', '<1h' => '<1ч', - // '%dh' => '', - // '%b %e' => '', + '%dh' => '%dh', + '%b %e' => '%b %e', 'Expand tasks' => 'Развернуть задачи', 'Collapse tasks' => 'Свернуть задачи', 'Expand/collapse tasks' => 'Развернуть/свернуть задачи', @@ -667,75 +665,26 @@ 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' => 'Комментарий', - 'All day' => 'Весь день', - 'Day' => 'День', - 'Manage timetable' => 'Управление графиками', - 'Overtime timetable' => 'График сверхурочных', - 'Time off timetable' => 'Время в графике', - 'Timetable' => 'График', - 'Work timetable' => 'Work timetable', - 'Week timetable' => 'График на неделю', - 'Day timetable' => 'График на день', - 'From' => 'От кого', - 'To' => 'Кому', - 'Time slot created successfully.' => 'Временной интервал успешно создан.', - 'Unable to save this time slot.' => 'Невозможно сохранить этот временной интервал.', - 'Time slot removed successfully.' => 'Временной интервал успешно удален.', - 'Unable to remove this time slot.' => 'Не удается удалить этот временной интервал.', - 'Do you really want to remove this time slot?' => 'Вы действительно хотите удалить этот период времени?', - 'Remove time slot' => 'Удалить новый интервал времени', - 'Add new time slot' => 'Добавить новый интервал времени', - // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', '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' => 'Переместить задачу в другую колонку, когда назначение снято ', 'Source column' => 'Исходная колонка', - 'Show subtask estimates (forecast of future work)' => 'Показать оценку подзадач (прогноз будущей работы)', 'Transitions' => 'Перемещения', 'Executer' => 'Исполнитель', 'Time spent in the column' => 'Время проведенное в колонке', @@ -745,8 +694,7 @@ return array( 'Currency rates' => 'Курсы валют', 'Rate' => 'Курс', 'Change reference currency' => 'Изменить справочник валют', - 'Add a new currency rate' => 'Add a new currency rate', - 'Currency rates are used to calculate project budget.' => 'Курсы валют используются для расчета бюджета проекта.', + 'Add a new currency rate' => 'Добавить новый валютный курс', 'Reference currency' => 'Справочник валют', 'The currency rate have been added successfully.' => 'Курс валюты был успешно добавлен.', 'Unable to add this currency rate.' => 'Невозможно добавить этот курс валюты.', @@ -774,8 +722,8 @@ return array( 'Test your device' => 'Проверьте свое устройство', 'Assign a color when the task is moved to a specific column' => 'Назначить цвет, когда задача перемещается в определенную колонку', '%s via Kanboard' => '%s через Канборд', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', + 'uploaded by: %s' => 'загружено: %s', + 'uploaded on: %s' => 'загружено в: %s', 'size: %s' => 'размер: %s', 'Burndown chart for "%s"' => 'Диаграмма сгорания для « %s »', 'Burndown chart' => 'Диаграмма сгорания', @@ -805,7 +753,7 @@ return array( 'The identifier must be unique' => 'Идентификатор должен быть уникальным', 'This linked task id doesn\'t exists' => 'Этот ID звязанной задачи не существует', 'This value must be alphanumeric' => 'Это значение должно быть буквенно-цифровым', - 'Edit recurrence' => 'Завершить повторение', + 'Edit recurrence' => 'Редактировать повторы', 'Generate recurrent task' => 'Создать повторяющуюся задачу', 'Trigger to generate recurrent task' => 'Триггер для генерации периодической задачи', 'Factor to calculate new due date' => 'Коэффициент для рассчета новой даты', @@ -850,10 +798,10 @@ return array( 'Two factor authentication disabled' => 'Двухфакторная аутентификация отключена', 'Two factor authentication enabled' => 'Включена двухфакторная аутентификация', 'Unable to update this user.' => 'Не удается обновить этого пользователя.', - 'There is no user management for private projects.' => 'Там нет управления пользователя для частных проектов', - // 'User that will receive the email' => '', - // 'Email subject' => '', - // 'Date' => '', + 'There is no user management for private projects.' => 'Для приватных проектов управление пользователями не предусмотрено.', + 'User that will receive the email' => 'Пользователь, который будет получать e-mail', + 'Email subject' => 'Тема e-mail', + 'Date' => 'Дата', // 'By @%s on Bitbucket' => '', // 'Bitbucket Issue' => '', // 'Commit made by @%s on Bitbucket' => '', @@ -869,202 +817,243 @@ return array( // 'Bitbucket issue reopened' => '', // 'Bitbucket issue assignee change' => '', // 'Bitbucket issue comment created' => '', - // 'Column change' => '', - // 'Position change' => '', - // 'Swimlane change' => '', + 'Column change' => 'Изменение колонки', + 'Position change' => 'Позиция изменена', + 'Swimlane change' => 'Дорожка изменена', // 'Assignee change' => '', - // '[%s] Overdue tasks' => '', - // 'Notification' => '', - // '%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' => '', + '[%s] Overdue tasks' => '[%s] просроченные задачи', + 'Notification' => 'Уведомления', + '%s moved the task #%d to the first swimlane' => '%s задач перемещено #%d в первой дорожке', + '%s moved the task #%d to the swimlane "%s"' => '%s задач перемещено #%d в дорожке "%s"', + 'Swimlane' => 'Дорожки', + 'Gravatar' => 'Граватар', + 'Hipchat' => 'Hipchat', + 'Slack' => 'Slack', // '%s moved the task %s to the first swimlane' => '', // '%s moved the task %s to the swimlane "%s"' => '', - // 'This report contains all subtasks information for the given date range.' => '', - // 'This report contains all tasks information for the given date range.' => '', - // 'Project activities for %s' => '', + 'This report contains all subtasks information for the given date range.' => 'Этот отчет содержит всю информацию подзадач в заданном диапазоне дат.', + 'This report contains all tasks information for the given date range.' => 'Этот отчет содержит всю информацию для задачи в заданном диапазоне дат.', + 'Project activities for %s' => 'Активность проекта для %s', // 'view the board on Kanboard' => '', - // 'The task have been moved to the first swimlane' => '', - // 'The task have been moved to another swimlane:' => '', - // 'Overdue tasks for the project "%s"' => '', - // 'New title: %s' => '', - // 'The task is not assigned anymore' => '', + 'The task have been moved to the first swimlane' => 'Эта задача была перемещена в первую дорожку', + 'The task have been moved to another swimlane:' => 'Эта задача была перемещена в другую дорожку:', + 'Overdue tasks for the project "%s"' => 'Просроченные задачи для проекта "%s"', + 'New title: %s' => 'Новый заголовок: %s', + 'The task is not assigned anymore' => 'Задача больше не назначена', // 'New assignee: %s' => '', - // 'There is no category now' => '', - // 'New category: %s' => '', - // 'New color: %s' => '', - // 'New complexity: %d' => '', - // 'The due date have been removed' => '', - // 'There is no description anymore' => '', - // 'Recurrence settings have been modified' => '', - // 'Time spent changed: %sh' => '', - // 'Time estimated changed: %sh' => '', - // 'The field "%s" have been updated' => '', - // 'The description have been modified' => '', - // 'Do you really want to close the task "%s" as well as all subtasks?' => '', - // 'Swimlane: %s' => '', - // 'I want to receive notifications for:' => '', - // 'All tasks' => '', - // 'Only for tasks assigned to me' => '', - // 'Only for tasks created by me' => '', - // 'Only for tasks created by me and assigned to me' => '', - // '%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.' => '', - // '<15m' => '', - // '<30m' => '', - // 'Stop timer' => '', - // 'Start timer' => '', - // 'Add project member' => '', - // 'Enable notifications' => '', - // 'My activity stream' => '', - // 'My calendar' => '', - // 'Search tasks' => '', - // 'Back to the calendar' => '', - // 'Filters' => '', - // 'Reset filters' => '', - // 'My tasks due tomorrow' => '', - // 'Tasks due today' => '', - // 'Tasks due tomorrow' => '', - // 'Tasks due yesterday' => '', - // 'Closed tasks' => '', - // 'Open tasks' => '', - // 'Not assigned' => '', - // 'View advanced search syntax' => '', - // 'Overview' => '', - // '%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: ' => '', - // 'Lead and Cycle time for "%s"' => '', - // 'Average time spent into each column for "%s"' => '', - // 'Average time spent into each column' => '', - // 'Average time spent' => '', - // 'This chart show the average time spent into each column for the last %d tasks.' => '', - // 'Average Lead and Cycle time' => '', - // 'Average lead time: ' => '', - // 'Average cycle time: ' => '', - // 'Cycle Time' => '', - // 'Lead Time' => '', - // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '', - // 'Average time into each column' => '', - // 'Lead and cycle time' => '', - // 'Google Authentication' => '', - // 'Help on Google authentication' => '', - // 'Github Authentication' => '', - // 'Help on Github authentication' => '', - // 'Channel/Group/User (Optional)' => '', - // 'Lead time: ' => '', - // 'Cycle time: ' => '', - // 'Time spent into each column' => '', - // 'The lead time is the duration between the task creation and the completion.' => '', - // 'The cycle time is the duration between the start date and the completion.' => '', + 'There is no category now' => 'В настоящее время здесь нет категорий', + 'New category: %s' => 'Новая категория: %s', + 'New color: %s' => 'Новый цвет: %s', + 'New complexity: %d' => 'Новая сложность: %d', + 'The due date have been removed' => 'Дата завершения была удалена', + 'There is no description anymore' => 'Здесь больше нет описания', + 'Recurrence settings have been modified' => 'Настройки повтора были изменены', + 'Time spent changed: %sh' => 'Изменение количества затраченного времени: %sh', + 'Time estimated changed: %sh' => 'Ожидаемый срок изменен: %sh', + 'The field "%s" have been updated' => 'Поле "%s" ,было изменено', + 'The description have been modified' => 'Описание было изменено', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Вы действительно хотите закрыть задачу "%s", а также все подзадачи?', + 'Swimlane: %s' => 'Дорожка: %s', + 'I want to receive notifications for:' => 'Я хочу получать уведомления для:', + 'All tasks' => 'Все задачи', + 'Only for tasks assigned to me' => 'Только для задач, назначенных на меня', + 'Only for tasks created by me' => 'Только для задач, созданных мной', + 'Only for tasks created by me and assigned to me' => 'Только для задач, созданных мной и назначенных мной', + '%A' => '%A', + '%b %e, %Y, %k:%M %p' => '%b %e, %Y, %k:%M %p', + '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', + 'Total for all columns' => 'Суммарно для всех колонок', + 'You need at least 2 days of data to show the chart.' => 'Для отображения диаграммы нужно по крайней мере 2 дня.', + '<15m' => '<15м', + '<30m' => '<30м', + 'Stop timer' => 'Остановить таймер', + 'Start timer' => 'Запустить таймер', + 'Add project member' => 'Добавить номер проекта', + 'Enable notifications' => 'Отключить уведомления', + 'My activity stream' => 'Лента моей активности', + 'My calendar' => 'Мой календарь', + 'Search tasks' => 'Поиск задачи', + 'Back to the calendar' => 'Вернуться в календарь', + 'Filters' => 'Фильтры', + 'Reset filters' => 'Сбросить фильтры', + 'My tasks due tomorrow' => 'Мои задачи на завтра', + 'Tasks due today' => 'Задачи, завершающиеся сегодня', + 'Tasks due tomorrow' => 'Задачи, завершающиеся завтра', + 'Tasks due yesterday' => 'Задачи, завершившиеся вчера', + 'Closed tasks' => 'Закрытые задачи', + 'Open tasks' => 'Открытые задачи', + 'Not assigned' => 'Не назначенные', + 'View advanced search syntax' => 'Просмотр расширенного синтаксиса поиска', + 'Overview' => 'Обзор', + '%b %e %Y' => '%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"' => 'Сочетание клавиш: "%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: ' => 'Поиск по дате завершения: ', + 'Lead and Cycle time for "%s"' => 'Затраченное время и время цикла для "%s"', + 'Average time spent into each column for "%s"' => 'Затрачено времени в среднем в каждой колонке для "%s"', + 'Average time spent into each column' => 'Затрачено времени в среднем в каждой колонке', + 'Average time spent' => 'Затрачено времени в среднем', + 'This chart show the average time spent into each column for the last %d tasks.' => 'Эта диаграмма показывает среднее время, проведенное задачами в каждой колонке за последний %d.', + 'Average Lead and Cycle time' => 'Среднее время выполнения и цикла', + 'Average lead time: ' => 'Среднее время выполнения', + 'Average cycle time: ' => 'Среднее время цикла', + 'Cycle Time' => 'Время цикла', + 'Lead Time' => 'Время выполнения', + 'This chart show the average lead and cycle time for the last %d tasks over the time.' => 'Эта диаграма показывает среднее время выполнения и цикла задачь в последние %d.', + 'Average time into each column' => 'Среднее время в каждом столбце', + 'Lead and cycle time' => 'Время выполнения и цикла', + 'Google Authentication' => 'Авторизация Google', + 'Help on Google authentication' => 'Помощь в авторизации Google', + 'Github Authentication' => 'Авторизация Github', + 'Help on Github authentication' => 'Помощь в авторизации Github', + 'Channel/Group/User (Optional)' => 'Канал/Группа/Пользователь (опционально)', + 'Lead time: ' => 'Время выполнения:', + 'Cycle time: ' => 'Время цикла:', + 'Time spent into each column' => 'Время, проведенное в каждой колонке', + 'The lead time is the duration between the task creation and the completion.' => 'Время выполнения - период между созданием задачи и завершения.', + 'The cycle time is the duration between the start date and the completion.' => 'Время цикла - период времени между датой начала и завершения.', // 'If the task is not closed the current time is used instead of the completion date.' => '', - // 'Set automatically the start date' => '', - // 'Edit Authentication' => '', - // 'Google Id' => '', - // 'Github Id' => '', - // 'Remote user' => '', - // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '', - // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '', - // 'By @%s on Gitlab' => '', - // 'Gitlab issue comment created' => '', - // 'New remote user' => '', - // 'New local user' => '', - // 'Default task color' => '', - // 'Hide sidebar' => '', - // 'Expand sidebar' => '', - // 'This feature does not work with all browsers.' => '', - // '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' => '', - // 'Sort by position' => '', - // 'Sort by date' => '', - // 'Add task' => '', - // 'Start date:' => '', - // 'Due date:' => '', - // 'There is no start date or due date for this task.' => '', - // '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' => '', - // 'NOK - Norwegian Krone' => '', - // 'Show this column' => '', - // 'Hide this column' => '', - // 'open file' => '', - // 'End date' => '', - // 'Users overview' => '', - // 'Managers' => '', - // 'Members' => '', - // 'Shared project' => '', - // 'Project managers' => '', - // 'Project members' => '', - // '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' => '', + 'Set automatically the start date' => 'Установить автоматическую дату начала', + 'Edit Authentication' => 'Редактировать авторизацию', + 'Google Id' => 'Google I', + 'Github Id' => 'Github Id', + 'Remote user' => 'Удаленный пользователь', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Учетные данные для входа через LDAP, Google и Github не будут сохранены в Kanboard.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Если вы установите флажок "Запретить форму входа", учетные данные, введенные в форму входа будет игнорироваться.', + 'By @%s on Gitlab' => 'От @%s на Gitlab', + 'Gitlab issue comment created' => 'Был создан комментарий к задаче на Gitlab', + 'New remote user' => 'Новый удаленный пользователь', + 'New local user' => 'Новый локальный пользователь', + 'Default task color' => 'Стандартные цвета задач', + 'Hide sidebar' => 'Свернуть сайдбар', + 'Expand sidebar' => 'Показать сайдбар', + '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' => 'Текущая дорожка: %s', + 'Current column: %s' => 'Текущая колонка: %s', + 'Current category: %s' => 'Текущая категория: %s', + 'no category' => 'без категории', + 'Current assignee: %s' => 'Current assignee: %s', + 'not assigned' => 'не назначен', + 'Author:' => 'Автор:', + 'contributors' => 'соавторы', + 'License:' => 'Лицензия:', + 'License' => 'Лицензия', + 'Project Administrator' => 'Администратор проекта', + 'Enter the text below' => 'Введите текст ниже', + 'Gantt chart for %s' => 'Диаграмма Гантта для %s', + 'Sort by position' => 'Сортировать по позиции', + 'Sort by date' => 'Сортировать по дате', + 'Add task' => 'Добавить задачу', + 'Start date:' => 'Дата начала:', + 'Due date:' => 'Дата завершения:', + 'There is no start date or due date for this task.' => 'Для этой задачи нет даты начала или завершения.', + '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' => 'Люди, которые участники проекта', + 'NOK - Norwegian Krone' => 'НК - Норвежская крона', + 'Show this column' => 'Показать эту колонку', + 'Hide this column' => 'Спрятать эту колонку', + 'open file' => 'открыть файл', + 'End date' => 'Дата завершения', + 'Users overview' => 'Обзор пользователей', + 'Managers' => 'Менеджеры', + 'Members' => 'Участники', + 'Shared project' => 'Общие/публичные проекты', + 'Project managers' => 'Менеджер проекта', + 'Project members' => 'Участники проекта', + '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' => 'Дата начала: %s', + 'End date: %s' => 'Дата завершения: %s', + 'Link type' => 'Тип ссылки', + 'Change task color when using a specific task link' => 'Изменение цвета задач при использовании ссылки на определенные задачи', + 'Task link creation or modification' => 'Ссылка на создание или модификацию задачи', + 'Login with my Gitlab Account' => 'Авторизоваться через аккаунт Gitlab', + 'Milestone' => 'Веха', + 'Gitlab Authentication' => 'Авторизация через Gitlab', + 'Help on Gitlab authentication' => 'Помощь а авторизации через Gitlab', + 'Gitlab Id' => 'Gitlab Id', + 'Gitlab Account' => 'Аккаунт Gitlab', + 'Link my Gitlab Account' => 'Привязать аккаунт Gitlab', + 'Unlink my Gitlab Account' => 'Отвязать аккаунт Gitlab', + 'Documentation: %s' => 'Документация: %s', + 'Switch to the Gantt chart view' => 'Переключиться в режим диаграммы Гантта', + 'Reset the search/filter box' => 'Сбросить поиск/фильтр', + 'Documentation' => 'Документация', + 'Table of contents' => 'Сожержание', + 'Gantt' => 'Гантт', + 'Help with project permissions' => 'Помощь с правами доступа по проекту', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/sr_Latn_RS/translations.php b/sources/app/Locale/sr_Latn_RS/translations.php index 0bc5c24..0f80aa1 100644 --- a/sources/app/Locale/sr_Latn_RS/translations.php +++ b/sources/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,75 +665,26 @@ 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' => '', - // 'All day' => '', - // 'Day' => '', - // 'Manage timetable' => '', - // 'Overtime timetable' => '', - // 'Time off timetable' => '', - // 'Timetable' => '', - // 'Work timetable' => '', - // 'Week timetable' => '', - // 'Day timetable' => '', - // 'From' => '', - // 'To' => '', - // 'Time slot created successfully.' => '', - // 'Unable to save this time slot.' => '', - // 'Time slot removed successfully.' => '', - // 'Unable to remove this time slot.' => '', - // 'Do you really want to remove this time slot?' => '', - // 'Remove time slot' => '', - // 'Add new time slot' => '', - // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', // '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' => '', // 'Source column' => '', - // 'Show subtask estimates (forecast of future work)' => '', // 'Transitions' => '', // 'Executer' => '', // 'Time spent in the column' => '', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/sv_SE/translations.php b/sources/app/Locale/sv_SE/translations.php index 9c76972..f1994f8 100644 --- a/sources/app/Locale/sv_SE/translations.php +++ b/sources/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,75 +665,26 @@ 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', - 'All day' => 'Hela dagen', - 'Day' => 'Dag', - 'Manage timetable' => 'Hantera timplan', - 'Overtime timetable' => 'Övertidstimplan', - 'Time off timetable' => 'Ledighetstimplan', - 'Timetable' => 'Timplan', - 'Work timetable' => 'Arbetstimplan', - 'Week timetable' => 'Veckotidplan', - 'Day timetable' => 'Dagstimplan', - 'From' => 'Från', - 'To' => 'Till', - 'Time slot created successfully.' => 'Tidslucka skapad.', - 'Unable to save this time slot.' => 'Kunde inte spara tidsluckan.', - 'Time slot removed successfully.' => 'Tidsluckan tog bort.', - 'Unable to remove this time slot.' => 'Kunde inte ta bort tidsluckan.', - 'Do you really want to remove this time slot?' => 'Vill du verkligen ta bort tidsluckan?', - 'Remove time slot' => 'Ta bort tidslucka', - 'Add new time slot' => 'Lägg till ny tidslucka', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Denna tidslucka används när kryssrutan "hela dagen" är kryssad vid schemalagd ledighet eller övertid.', '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.', 'Source column' => 'Källkolumn', - 'Show subtask estimates (forecast of future work)' => 'Visa uppskattningar för deluppgifter (prognos för framtida arbete)', 'Transitions' => 'Övergångar', 'Executer' => 'Verkställare', 'Time spent in the column' => 'Tid i kolumnen.', @@ -746,7 +695,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 +826,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', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/th_TH/translations.php b/sources/app/Locale/th_TH/translations.php index a5ed247..931bd87 100644 --- a/sources/app/Locale/th_TH/translations.php +++ b/sources/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,75 +665,26 @@ 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' => 'ความคิดเห็น', - 'All day' => 'ทั้งวัน', - 'Day' => 'วัน', - 'Manage timetable' => 'จัดการตารางเวลา', - 'Overtime timetable' => 'ตารางเวลาโอที', - 'Time off timetable' => 'ตารางเวลาวันหยุด', - 'Timetable' => 'ตารางเวลา', - 'Work timetable' => 'ตารางเวลางาน', - 'Week timetable' => 'ตารางเวลาสัปดาห์', - 'Day timetable' => 'ตารางเวลาวัน', - 'From' => 'จาก', - 'To' => 'ถึง', - 'Time slot created successfully.' => 'สร้างช่วงเวลาเรียบร้อยแล้ว', - 'Unable to save this time slot.' => 'ไม่สามารถบันทึกช่วงเวลานี้', - 'Time slot removed successfully.' => 'ลบช่วงเวลาเรียบร้อยแล้ว', - 'Unable to remove this time slot.' => 'ไม่สามารถลบช่วงเวลาได้', - 'Do you really want to remove this time slot?' => 'คุณต้องการลบช่วงเวลานี้?', - 'Remove time slot' => 'ลบช่วงเวลา', - 'Add new time slot' => 'เพิ่มช่วงเวลาใหม่', - // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', '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' => 'ย้ายงานไปคอลัมน์อื่นเมื่อไม่กำหนดบุคคลรับผิดชอบ', 'Source column' => 'คอลัมน์ต้นทาง', - // 'Show subtask estimates (forecast of future work)' => '', 'Transitions' => 'การเปลี่ยนคอลัมน์', 'Executer' => 'ผู้ประมวลผล', 'Time spent in the column' => 'เวลาที่ใช้ในคอลัมน์', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/tr_TR/translations.php b/sources/app/Locale/tr_TR/translations.php index 9eb5c41..53f8fb9 100644 --- a/sources/app/Locale/tr_TR/translations.php +++ b/sources/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,75 +665,26 @@ 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' => '', - // 'All day' => '', - // 'Day' => '', - // 'Manage timetable' => '', - // 'Overtime timetable' => '', - // 'Time off timetable' => '', - // 'Timetable' => '', - // 'Work timetable' => '', - // 'Week timetable' => '', - // 'Day timetable' => '', - // 'From' => '', - // 'To' => '', - // 'Time slot created successfully.' => '', - // 'Unable to save this time slot.' => '', - // 'Time slot removed successfully.' => '', - // 'Unable to remove this time slot.' => '', - // 'Do you really want to remove this time slot?' => '', - // 'Remove time slot' => '', - // 'Add new time slot' => '', - // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', // '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' => '', // 'Source column' => '', - // 'Show subtask estimates (forecast of future work)' => '', // 'Transitions' => '', // 'Executer' => '', // 'Time spent in the column' => '', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Locale/zh_CN/translations.php b/sources/app/Locale/zh_CN/translations.php index 910bc0b..5863934 100644 --- a/sources/app/Locale/zh_CN/translations.php +++ b/sources/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,75 +665,26 @@ 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' => '注释', - 'All day' => '全天', - 'Day' => '日期', - 'Manage timetable' => '管理时间表', - // 'Overtime timetable' => '', - 'Time off timetable' => '加班时间表', - 'Timetable' => '时间表', - 'Work timetable' => '工作时间表', - 'Week timetable' => '周时间表', - 'Day timetable' => '日时间表', - 'From' => '从', - 'To' => '到', - 'Time slot created successfully.' => '成功创建时间段。', - 'Unable to save this time slot.' => '无法保存此时间段。', - 'Time slot removed successfully.' => '成功删除时间段。', - 'Unable to remove this time slot.' => '无法删除此时间段。', - 'Do you really want to remove this time slot?' => '确认要删除此时间段吗?', - 'Remove time slot' => '删除时间段', - 'Add new time slot' => '添加新时间段', - 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '如果在放假和加班计划中选择全天,则会使用这里配置的时间段。', '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' => '移除负责人时移动到其它栏目', 'Source column' => '原栏目', - // 'Show subtask estimates (forecast of future work)' => '', 'Transitions' => '变更', 'Executer' => '执行者', 'Time spent in the column' => '栏目中的时间消耗', @@ -746,7 +695,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 +826,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' => '', @@ -1067,4 +1012,48 @@ return array( // 'Table of contents' => '', // 'Gantt' => '', // 'Help with project permissions' => '', + // 'Author' => '', + // 'Version' => '', + // 'Plugins' => '', + // 'There is no plugin loaded.' => '', + // 'Set maximum column height' => '', + // 'Remove maximum column height' => '', + // 'My notifications' => '', + // 'Custom filters' => '', + // 'Your custom filter have been created successfully.' => '', + // 'Unable to create your custom filter.' => '', + // 'Custom filter removed successfully.' => '', + // 'Unable to remove this custom filter.' => '', + // 'Edit custom filter' => '', + // 'Your custom filter have been updated successfully.' => '', + // 'Unable to update custom filter.' => '', + // 'Web' => '', + // 'New attachment on task #%d: %s' => '', + // 'New comment on task #%d' => '', + // 'Comment updated on task #%d' => '', + // 'New subtask on task #%d' => '', + // 'Subtask updated on task #%d' => '', + // 'New task #%d: %s' => '', + // 'Task updated #%d' => '', + // 'Task #%d closed' => '', + // 'Task #%d opened' => '', + // 'Column changed for task #%d' => '', + // 'New position for task #%d' => '', + // 'Swimlane changed for task #%d' => '', + // 'Assignee changed on task #%d' => '', + // '%d overdue tasks' => '', + // 'Task #%d is overdue' => '', + // 'No new notifications.' => '', + // 'Mark all as read' => '', + // 'Mark as read' => '', + // 'Total number of tasks in this column across all swimlanes' => '', + // 'Collapse swimlane' => '', + // 'Expand swimlane' => '', + // 'Add a new filter' => '', + // 'Share with all project members' => '', + // 'Shared' => '', + // 'Owner' => '', + // 'Unread notifications' => '', + // 'My filters' => '', + // 'Notification methods:' => '', ); diff --git a/sources/app/Model/Acl.php b/sources/app/Model/Acl.php index 8c28cb1..675ca36 100644 --- a/sources/app/Model/Acl.php +++ b/sources/app/Model/Acl.php @@ -47,6 +47,7 @@ class Acl extends Base 'taskstatus' => '*', 'tasklink' => '*', 'timer' => '*', + 'customfilter' => '*', 'calendar' => array('show', 'project'), ); @@ -64,7 +65,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'), ); @@ -94,6 +94,18 @@ class Acl extends Base 'twofactor' => array('disable'), ); + /** + * 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 * diff --git a/sources/app/Model/Action.php b/sources/app/Model/Action.php index 87058cc..57bd5b0 100644 --- a/sources/app/Model/Action.php +++ b/sources/app/Model/Action.php @@ -30,6 +30,28 @@ class Action extends Base */ const TABLE_PARAMS = 'action_has_params'; + /** + * Extended actions + * + * @access private + * @var array + */ + private $actions = array(); + + /** + * Extend the list of default actions + * + * @access public + * @param string $className + * @param string $description + * @return Action + */ + public function extendActions($className, $description) + { + $this->actions[$className] = $description; + return $this; + } + /** * Return the name and description of available actions * @@ -62,6 +84,8 @@ class Action extends Base 'TaskAssignColorLink' => t('Change task color when using a specific task link'), ); + $values = array_merge($values, $this->actions); + asort($values); return $values; @@ -296,7 +320,7 @@ class Action extends Base */ public function load($name, $project_id, $event) { - $className = '\Action\\'.$name; + $className = $name{0} !== '\\' ? '\Action\\'.$name : $name; return new $className($this->container, $project_id, $event); } diff --git a/sources/app/Model/Base.php b/sources/app/Model/Base.php index 973462c..902ab26 100644 --- a/sources/app/Model/Base.php +++ b/sources/app/Model/Base.php @@ -12,26 +12,6 @@ use Pimple\Container; */ abstract class Base extends \Core\Base { - /** - * Database instance - * - * @access protected - * @var \PicoDb\Database - */ - protected $db; - - /** - * Constructor - * - * @access public - * @param \Pimple\Container $container - */ - public function __construct(Container $container) - { - $this->container = $container; - $this->db = $this->container['db']; - } - /** * Save a record in the database * @@ -62,14 +42,30 @@ abstract class Base extends \Core\Base public function removeFields(array &$values, array $keys) { foreach ($keys as $key) { - if (isset($values[$key])) { + if (array_key_exists($key, $values)) { unset($values[$key]); } } } /** - * Force some fields to be at 0 if empty + * Remove keys from an array if empty + * + * @access public + * @param array $values Input array + * @param string[] $keys List of keys to remove + */ + public function removeEmptyFields(array &$values, array $keys) + { + foreach ($keys as $key) { + if (array_key_exists($key, $values) && empty($values[$key])) { + unset($values[$key]); + } + } + } + + /** + * Force fields to be at 0 if empty * * @access public * @param array $values Input array @@ -100,6 +96,22 @@ abstract class Base extends \Core\Base } } + /** + * Force some fields to be null if empty + * + * @access public + * @param array $values Input array + * @param string[] $keys List of keys + */ + public function convertNullFields(array &$values, array $keys) + { + foreach ($keys as $key) { + if (array_key_exists($key, $values) && empty($values[$key])) { + $values[$key] = null; + } + } + } + /** * Build SQL condition for a given time range * @@ -124,26 +136,6 @@ abstract class Base extends \Core\Base return $start_column.' IS NOT NULL AND '.$start_column.' > 0 AND ('.implode(' OR ', $conditions).')'; } - /** - * Get common properties for task calendar events - * - * @access protected - * @param array $task - * @return array - */ - protected function getTaskCalendarProperties(array &$task) - { - return array( - 'timezoneParam' => $this->config->getCurrentTimezone(), - 'id' => $task['id'], - 'title' => t('#%d', $task['id']).' '.$task['title'], - 'backgroundColor' => $this->color->getBackgroundColor($task['color_id']), - 'borderColor' => $this->color->getBorderColor($task['color_id']), - 'textColor' => 'black', - 'url' => $this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), - ); - } - /** * Group a collection of records by a column * diff --git a/sources/app/Model/Board.php b/sources/app/Model/Board.php index b1032e9..7217542 100644 --- a/sources/app/Model/Board.php +++ b/sources/app/Model/Board.php @@ -252,16 +252,24 @@ class Board extends Base $swimlanes[$i]['columns'] = $columns; $swimlanes[$i]['nb_columns'] = $nb_columns; $swimlanes[$i]['nb_tasks'] = 0; + $swimlanes[$i]['nb_swimlanes'] = $ilen; for ($j = 0; $j < $nb_columns; $j++) { $column_id = $columns[$j]['id']; $swimlane_id = $swimlanes[$i]['id']; + if (! isset($swimlanes[0]['columns'][$j]['nb_column_tasks'])) { + $swimlanes[0]['columns'][$j]['nb_column_tasks'] = 0; + $swimlanes[0]['columns'][$j]['total_score'] = 0; + } + $swimlanes[$i]['columns'][$j]['tasks'] = $callback === null ? $this->taskFinder->getTasksByColumnAndSwimlane($project_id, $column_id, $swimlane_id) : $callback($project_id, $column_id, $swimlane_id); $swimlanes[$i]['columns'][$j]['nb_tasks'] = count($swimlanes[$i]['columns'][$j]['tasks']); $swimlanes[$i]['columns'][$j]['score'] = $this->getColumnSum($swimlanes[$i]['columns'][$j]['tasks'], 'score'); $swimlanes[$i]['nb_tasks'] += $swimlanes[$i]['columns'][$j]['nb_tasks']; + $swimlanes[0]['columns'][$j]['nb_column_tasks'] += $swimlanes[$i]['columns'][$j]['nb_tasks']; + $swimlanes[0]['columns'][$j]['total_score'] += $swimlanes[$i]['columns'][$j]['score']; } } @@ -394,6 +402,18 @@ class Board extends Base return (int) $this->db->table(self::TABLE)->eq('project_id', $project_id)->eq('title', $title)->findOneColumn('id'); } + /** + * Get a column title by the id + * + * @access public + * @param integer $column_id + * @return integer + */ + public function getColumnTitleById($column_id) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->findOneColumn('title'); + } + /** * Get the position of the last column for a given project * diff --git a/sources/app/Model/Budget.php b/sources/app/Model/Budget.php deleted file mode 100644 index 76c42ca..0000000 --- a/sources/app/Model/Budget.php +++ /dev/null @@ -1,214 +0,0 @@ -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/sources/app/Model/Config.php b/sources/app/Model/Config.php index 6fa98f9..bbc86a8 100644 --- a/sources/app/Model/Config.php +++ b/sources/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(); @@ -206,6 +208,11 @@ class Config extends Base { foreach ($values as $option => $value) { + // Be sure that a trailing slash is there for the url + if ($option === 'application_url' && ! empty($value) && substr($value, -1) !== '/') { + $value .= '/'; + } + $result = $this->db->table(self::TABLE)->eq('option', $option)->update(array('value' => $value)); if (! $result) { diff --git a/sources/app/Model/CustomFilter.php b/sources/app/Model/CustomFilter.php new file mode 100644 index 0000000..2c48524 --- /dev/null +++ b/sources/app/Model/CustomFilter.php @@ -0,0 +1,163 @@ +db + ->table(self::TABLE) + ->columns( + User::TABLE.'.name as owner_name', + User::TABLE.'.username as owner_username', + self::TABLE.'.id', + self::TABLE.'.user_id', + self::TABLE.'.project_id', + self::TABLE.'.filter', + self::TABLE.'.name', + self::TABLE.'.is_shared' + ) + ->asc(self::TABLE.'.name') + ->join(User::TABLE, 'id', 'user_id') + ->beginOr() + ->eq('is_shared', 1) + ->eq('user_id', $user_id) + ->closeOr() + ->eq('project_id', $project_id) + ->findAll(); + } + + /** + * Get custom filter by id + * + * @access private + * @param integer $filter_id + * @return array + */ + public function getById($filter_id) + { + return $this->db->table(self::TABLE)->eq('id', $filter_id)->findOne(); + } + + /** + * Create a custom filter + * + * @access public + * @param array $values Form values + * @return bool|integer + */ + public function create(array $values) + { + return $this->persist(self::TABLE, $values); + } + + /** + * Update a custom filter + * + * @access public + * @param array $values Form values + * @return bool + */ + public function update(array $values) + { + return $this->db->table(self::TABLE) + ->eq('id', $values['id']) + ->update($values); + } + + /** + * Remove a custom filter + * + * @access public + * @param integer $filter_id + * @return bool + */ + public function remove($filter_id) + { + return $this->db->table(self::TABLE)->eq('id', $filter_id)->remove(); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Required('project_id', t('Field required')), + new Validators\Required('user_id', t('Field required')), + new Validators\Required('name', t('Field required')), + new Validators\Required('filter', t('Field required')), + new Validators\Integer('user_id', t('This value must be an integer')), + new Validators\Integer('project_id', t('This value must be an integer')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 100), 100), + new Validators\MaxLength('filter', t('The maximum length is %d characters', 100), 100) + ); + } + + /** + * Validate filter 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, $this->commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate filter modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('Field required')), + new Validators\Integer('id', t('This value must be an integer')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/sources/app/Model/EmailNotification.php b/sources/app/Model/EmailNotification.php new file mode 100644 index 0000000..39b60fc --- /dev/null +++ b/sources/app/Model/EmailNotification.php @@ -0,0 +1,123 @@ +emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $this->getMailSubject($event_name, $event_data), + $this->getMailContent($event_name, $event_data) + ); + } + } + + /** + * Get the mail content for a given template name + * + * @access public + * @param string $event_name Event name + * @param array $event_data Event data + * @return string + */ + public function getMailContent($event_name, array $event_data) + { + return $this->template->render( + 'notification/'.str_replace('.', '_', $event_name), + $event_data + array('application_url' => $this->config->get('application_url')) + ); + } + + /** + * Get the mail subject for a given template name + * + * @access public + * @param string $event_name Event name + * @param array $event_data Event data + * @return string + */ + public function getMailSubject($event_name, array $event_data) + { + switch ($event_name) { + case File::EVENT_CREATE: + $subject = $this->getStandardMailSubject(e('New attachment'), $event_data); + break; + case Comment::EVENT_CREATE: + $subject = $this->getStandardMailSubject(e('New comment'), $event_data); + break; + case Comment::EVENT_UPDATE: + $subject = $this->getStandardMailSubject(e('Comment updated'), $event_data); + break; + case Subtask::EVENT_CREATE: + $subject = $this->getStandardMailSubject(e('New subtask'), $event_data); + break; + case Subtask::EVENT_UPDATE: + $subject = $this->getStandardMailSubject(e('Subtask updated'), $event_data); + break; + case Task::EVENT_CREATE: + $subject = $this->getStandardMailSubject(e('New task'), $event_data); + break; + case Task::EVENT_UPDATE: + $subject = $this->getStandardMailSubject(e('Task updated'), $event_data); + break; + case Task::EVENT_CLOSE: + $subject = $this->getStandardMailSubject(e('Task closed'), $event_data); + break; + case Task::EVENT_OPEN: + $subject = $this->getStandardMailSubject(e('Task opened'), $event_data); + break; + case Task::EVENT_MOVE_COLUMN: + $subject = $this->getStandardMailSubject(e('Column change'), $event_data); + break; + case Task::EVENT_MOVE_POSITION: + $subject = $this->getStandardMailSubject(e('Position change'), $event_data); + break; + case Task::EVENT_MOVE_SWIMLANE: + $subject = $this->getStandardMailSubject(e('Swimlane change'), $event_data); + break; + case Task::EVENT_ASSIGNEE_CHANGE: + $subject = $this->getStandardMailSubject(e('Assignee change'), $event_data); + break; + case Task::EVENT_OVERDUE: + $subject = e('[%s] Overdue tasks', $event_data['project_name']); + break; + default: + $subject = e('Notification'); + } + + return $subject; + } + + /** + * Get the mail subject for a given label + * + * @access private + * @param string $label Label + * @param array $data Template data + * @return string + */ + private function getStandardMailSubject($label, array $data) + { + return sprintf('[%s][%s] %s (#%d)', $data['task']['project_name'], $label, $data['task']['title'], $data['task']['id']); + } +} diff --git a/sources/app/Model/File.php b/sources/app/Model/File.php index f884e46..1d44a41 100644 --- a/sources/app/Model/File.php +++ b/sources/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,21 @@ 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']); + + if ($file['is_image'] == 1) { + $this->objectStorage->remove($this->getThumbnailPath($file['path'])); + } + + return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove(); + } + catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + return false; } - - return false; } /** @@ -66,11 +75,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); @@ -195,6 +204,30 @@ class File extends Base return false; } + /** + * 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 * @@ -209,6 +242,18 @@ class File extends Base return $project_id.DIRECTORY_SEPARATOR.$task_id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time()); } + /** + * 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 * @@ -218,11 +263,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 +279,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 (@move_uploaded_file($uploaded_filename, FILES_DIR.$destination_filename)) { - - $results[] = $this->create( - $task_id, - $original_filename, - $destination_filename, - $_FILES[$form_name]['size'][$key] - ); + if ($this->isImage($original_filename)) { + $this->generateThumbnailFromFile($uploaded_filename, $destination_filename); } + + $this->objectStorage->moveUploadedFile($uploaded_filename, $destination_filename); + + $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 +313,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)) { + $data = base64_decode($blob); + + if (empty($data)) { + return false; + } + + $destination_filename = $this->generatePath($project_id, $task_id, $original_filename); + $this->objectStorage->put($destination_filename, $data); + + if ($this->isImage($original_filename)) { + $this->generateThumbnailFromData($destination_filename, $data); + } + + return $this->create( + $task_id, + $original_filename, + $destination_filename, + strlen($data) + ); + } + catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); return false; } - - $destination_filename = $this->generatePath($project_id, $task_id, $filename); - - @mkdir(FILES_DIR.dirname($destination_filename), 0755, true); - @file_put_contents(FILES_DIR.$destination_filename, $data); - - return $this->create( - $task_id, - $filename, - $destination_filename, - strlen($data) - ); } /** - * 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; + $temp_filename = tempnam(sys_get_temp_dir(), 'datafile'); - if (empty($metadata['mime'])) { - return; - } + file_put_contents($temp_filename, $data); + $this->generateThumbnailFromFile($temp_filename, $destination_filename); + unlink($temp_filename); + } - 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($filename); - break; - case 'image/png': - $src_image = imagecreatefrompng($filename); - break; - case 'image/gif': - $src_image = imagecreatefromgif($filename); - 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); + /** + * 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/sources/app/Model/HourlyRate.php b/sources/app/Model/HourlyRate.php deleted file mode 100644 index 1550bda..0000000 --- a/sources/app/Model/HourlyRate.php +++ /dev/null @@ -1,121 +0,0 @@ -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/sources/app/Model/Notification.php b/sources/app/Model/Notification.php index 9628e34..525e7f1 100644 --- a/sources/app/Model/Notification.php +++ b/sources/app/Model/Notification.php @@ -12,71 +12,6 @@ use Core\Translator; */ class Notification extends Base { - /** - * SQL table name - * - * @var string - */ - const TABLE = 'user_has_notifications'; - - /** - * User filters - * - * @var integer - */ - const FILTER_NONE = 1; - const FILTER_ASSIGNEE = 2; - const FILTER_CREATOR = 3; - const FILTER_BOTH = 4; - - /** - * Send overdue tasks - * - * @access public - */ - public function sendOverdueTaskNotifications() - { - $tasks = $this->taskFinder->getOverdueTasks(); - - foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) { - - // Get the list of users that should receive notifications for each projects - $users = $this->notification->getUsersWithNotificationEnabled($project_id); - - foreach ($users as $user) { - $this->sendUserOverdueTaskNotifications($user, $project_tasks); - } - } - - return $tasks; - } - - /** - * Send overdue tasks for a given user - * - * @access public - * @param array $user - * @param array $tasks - */ - public function sendUserOverdueTaskNotifications(array $user, array $tasks) - { - $user_tasks = array(); - - foreach ($tasks as $task) { - if ($this->notification->shouldReceiveNotification($user, array('task' => $task))) { - $user_tasks[] = $task; - } - } - - if (! empty($user_tasks)) { - $this->sendEmailNotification( - $user, - Task::EVENT_OVERDUE, - array('tasks' => $user_tasks, 'project_name' => $tasks[0]['project_name']) - ); - } - } - /** * Send notifications to people * @@ -89,26 +24,31 @@ class Notification extends Base $logged_user_id = $this->userSession->isLogged() ? $this->userSession->getId() : 0; $users = $this->notification->getUsersWithNotificationEnabled($event_data['task']['project_id'], $logged_user_id); - foreach ($users as $user) { - if ($this->shouldReceiveNotification($user, $event_data)) { - $this->sendEmailNotification($user, $event_name, $event_data); - } - } + if (! empty($users)) { - // Restore locales - $this->config->setupTranslations(); + foreach ($users as $user) { + if ($this->notificationFilter->shouldReceiveNotification($user, $event_data)) { + $this->sendUserNotification($user, $event_name, $event_data); + } + } + + // Restore locales + $this->config->setupTranslations(); + } } /** - * Send email notification to someone + * Send notification to someone * * @access public * @param array $user User * @param string $event_name * @param array $event_data */ - public function sendEmailNotification(array $user, $event_name, array $event_data) + public function sendUserNotification(array $user, $event_name, array $event_data) { + Translator::unload(); + // Use the user language otherwise use the application language (do not use the session language) if (! empty($user['language'])) { Translator::load($user['language']); @@ -117,109 +57,10 @@ class Notification extends Base Translator::load($this->config->get('application_language', 'en_US')); } - $this->emailClient->send( - $user['email'], - $user['name'] ?: $user['username'], - $this->getMailSubject($event_name, $event_data), - $this->getMailContent($event_name, $event_data) - ); - } - - /** - * Return true if the user should receive notification - * - * @access public - * @param array $user - * @param array $event_data - * @return boolean - */ - public function shouldReceiveNotification(array $user, array $event_data) - { - $filters = array( - 'filterNone', - 'filterAssignee', - 'filterCreator', - 'filterBoth', - ); - - foreach ($filters as $filter) { - if ($this->$filter($user, $event_data)) { - return $this->filterProject($user, $event_data); - } + foreach ($this->notificationType->getUserSelectedTypes($user['id']) as $type) { + $className = strtolower($type).'Notification'; + $this->$className->send($user, $event_name, $event_data); } - - return false; - } - - /** - * Return true if the user will receive all notifications - * - * @access public - * @param array $user - * @return boolean - */ - public function filterNone(array $user) - { - return $user['notifications_filter'] == self::FILTER_NONE; - } - - /** - * Return true if the user is the assignee and selected the filter "assignee" - * - * @access public - * @param array $user - * @param array $event_data - * @return boolean - */ - public function filterAssignee(array $user, array $event_data) - { - return $user['notifications_filter'] == self::FILTER_ASSIGNEE && $event_data['task']['owner_id'] == $user['id']; - } - - /** - * Return true if the user is the creator and enabled the filter "creator" - * - * @access public - * @param array $user - * @param array $event_data - * @return boolean - */ - public function filterCreator(array $user, array $event_data) - { - return $user['notifications_filter'] == self::FILTER_CREATOR && $event_data['task']['creator_id'] == $user['id']; - } - - /** - * Return true if the user is the assignee or the creator and selected the filter "both" - * - * @access public - * @param array $user - * @param array $event_data - * @return boolean - */ - public function filterBoth(array $user, array $event_data) - { - return $user['notifications_filter'] == self::FILTER_BOTH && - ($event_data['task']['creator_id'] == $user['id'] || $event_data['task']['owner_id'] == $user['id']); - } - - /** - * Return true if the user want to receive notification for the selected project - * - * @access public - * @param array $user - * @param array $event_data - * @return boolean - */ - public function filterProject(array $user, array $event_data) - { - $projects = $this->db->table(self::TABLE)->eq('user_id', $user['id'])->findAllByColumn('project_id'); - - if (! empty($projects)) { - return in_array($event_data['task']['project_id'], $projects); - } - - return true; } /** @@ -233,114 +74,34 @@ class Notification extends Base public function getUsersWithNotificationEnabled($project_id, $exclude_user_id = 0) { if ($this->projectPermission->isEverybodyAllowed($project_id)) { - - return $this->db - ->table(User::TABLE) - ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language', User::TABLE.'.notifications_filter') - ->eq('notifications_enabled', '1') - ->neq('email', '') - ->neq(User::TABLE.'.id', $exclude_user_id) - ->findAll(); + return $this->getEverybodyWithNotificationEnabled($exclude_user_id); } - return $this->db - ->table(ProjectPermission::TABLE) - ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language', User::TABLE.'.notifications_filter') - ->join(User::TABLE, 'id', 'user_id') - ->eq('project_id', $project_id) - ->eq('notifications_enabled', '1') - ->neq('email', '') - ->neq(User::TABLE.'.id', $exclude_user_id) - ->findAll(); + return $this->getProjectMembersWithNotificationEnabled($project_id, $exclude_user_id); } /** - * Get the mail content for a given template name + * Enable notification for someone * * @access public - * @param string $event_name Event name - * @param array $event_data Event data - * @return string + * @param integer $user_id + * @return boolean */ - public function getMailContent($event_name, array $event_data) + public function enableNotification($user_id) { - return $this->template->render( - 'notification/'.str_replace('.', '_', $event_name), - $event_data + array('application_url' => $this->config->get('application_url')) - ); + return $this->db->table(User::TABLE)->eq('id', $user_id)->update(array('notifications_enabled' => 1)); } /** - * Get the mail subject for a given template name + * Disable notification for someone * * @access public - * @param string $event_name Event name - * @param array $event_data Event data - * @return string + * @param integer $user_id + * @return boolean */ - public function getMailSubject($event_name, array $event_data) + public function disableNotification($user_id) { - switch ($event_name) { - case File::EVENT_CREATE: - $subject = $this->getStandardMailSubject(e('New attachment'), $event_data); - break; - case Comment::EVENT_CREATE: - $subject = $this->getStandardMailSubject(e('New comment'), $event_data); - break; - case Comment::EVENT_UPDATE: - $subject = $this->getStandardMailSubject(e('Comment updated'), $event_data); - break; - case Subtask::EVENT_CREATE: - $subject = $this->getStandardMailSubject(e('New subtask'), $event_data); - break; - case Subtask::EVENT_UPDATE: - $subject = $this->getStandardMailSubject(e('Subtask updated'), $event_data); - break; - case Task::EVENT_CREATE: - $subject = $this->getStandardMailSubject(e('New task'), $event_data); - break; - case Task::EVENT_UPDATE: - $subject = $this->getStandardMailSubject(e('Task updated'), $event_data); - break; - case Task::EVENT_CLOSE: - $subject = $this->getStandardMailSubject(e('Task closed'), $event_data); - break; - case Task::EVENT_OPEN: - $subject = $this->getStandardMailSubject(e('Task opened'), $event_data); - break; - case Task::EVENT_MOVE_COLUMN: - $subject = $this->getStandardMailSubject(e('Column change'), $event_data); - break; - case Task::EVENT_MOVE_POSITION: - $subject = $this->getStandardMailSubject(e('Position change'), $event_data); - break; - case Task::EVENT_MOVE_SWIMLANE: - $subject = $this->getStandardMailSubject(e('Swimlane change'), $event_data); - break; - case Task::EVENT_ASSIGNEE_CHANGE: - $subject = $this->getStandardMailSubject(e('Assignee change'), $event_data); - break; - case Task::EVENT_OVERDUE: - $subject = e('[%s] Overdue tasks', $event_data['project_name']); - break; - default: - $subject = e('Notification'); - } - - return $subject; - } - - /** - * Get the mail subject for a given label - * - * @access private - * @param string $label Label - * @param array $data Template data - * @return string - */ - private function getStandardMailSubject($label, array $data) - { - return sprintf('[%s][%s] %s (#%d)', $data['task']['project_name'], $label, $data['task']['title'], $data['task']['id']); + return $this->db->table(User::TABLE)->eq('id', $user_id)->update(array('notifications_enabled' => 0)); } /** @@ -352,35 +113,24 @@ class Notification extends Base */ public function saveSettings($user_id, array $values) { - // Delete all selected projects - $this->db->table(self::TABLE)->eq('user_id', $user_id)->remove(); + $this->db->startTransaction(); if (isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1) { + $this->enableNotification($user_id); - // Activate notifications - $this->db->table(User::TABLE)->eq('id', $user_id)->update(array( - 'notifications_enabled' => '1', - 'notifications_filter' => empty($values['notifications_filter']) ? self::FILTER_BOTH : $values['notifications_filter'], - )); + $filter = empty($values['notifications_filter']) ? NotificationFilter::FILTER_BOTH : $values['notifications_filter']; + $projects = empty($values['notification_projects']) ? array() : array_keys($values['notification_projects']); + $types = empty($values['notification_types']) ? array() : array_keys($values['notification_types']); - // Save selected projects - if (! empty($values['projects'])) { - - foreach ($values['projects'] as $project_id => $checkbox_value) { - $this->db->table(self::TABLE)->insert(array( - 'user_id' => $user_id, - 'project_id' => $project_id, - )); - } - } + $this->notificationFilter->saveUserFilter($user_id, $filter); + $this->notificationFilter->saveUserSelectedProjects($user_id, $projects); + $this->notificationType->saveUserSelectedTypes($user_id, $types); } else { - - // Disable notifications - $this->db->table(User::TABLE)->eq('id', $user_id)->update(array( - 'notifications_enabled' => '0' - )); + $this->disableNotification($user_id); } + + $this->db->closeTransaction(); } /** @@ -393,12 +143,45 @@ class Notification extends Base public function readSettings($user_id) { $values = $this->db->table(User::TABLE)->eq('id', $user_id)->columns('notifications_enabled', 'notifications_filter')->findOne(); - $projects = $this->db->table(self::TABLE)->eq('user_id', $user_id)->findAllByColumn('project_id'); - - foreach ($projects as $project_id) { - $values['project_'.$project_id] = true; - } - + $values['notification_types'] = $this->notificationType->getUserSelectedTypes($user_id); + $values['notification_projects'] = $this->notificationFilter->getUserSelectedProjects($user_id); return $values; } + + /** + * Get a list of project members with notification enabled + * + * @access private + * @param integer $project_id Project id + * @param integer $exclude_user_id User id to exclude + * @return array + */ + private function getProjectMembersWithNotificationEnabled($project_id, $exclude_user_id) + { + return $this->db + ->table(ProjectPermission::TABLE) + ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language', User::TABLE.'.notifications_filter') + ->join(User::TABLE, 'id', 'user_id') + ->eq('project_id', $project_id) + ->eq('notifications_enabled', '1') + ->neq(User::TABLE.'.id', $exclude_user_id) + ->findAll(); + } + + /** + * Get a list of project members with notification enabled + * + * @access private + * @param integer $exclude_user_id User id to exclude + * @return array + */ + private function getEverybodyWithNotificationEnabled($exclude_user_id) + { + return $this->db + ->table(User::TABLE) + ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language', User::TABLE.'.notifications_filter') + ->eq('notifications_enabled', '1') + ->neq(User::TABLE.'.id', $exclude_user_id) + ->findAll(); + } } diff --git a/sources/app/Model/NotificationFilter.php b/sources/app/Model/NotificationFilter.php new file mode 100644 index 0000000..6279551 --- /dev/null +++ b/sources/app/Model/NotificationFilter.php @@ -0,0 +1,199 @@ + t('All tasks'), + self::FILTER_ASSIGNEE => t('Only for tasks assigned to me'), + self::FILTER_CREATOR => t('Only for tasks created by me'), + self::FILTER_BOTH => t('Only for tasks created by me and assigned to me'), + ); + } + + /** + * Get user selected filter + * + * @access public + * @param integer $user_id + * @return integer + */ + public function getUserSelectedFilter($user_id) + { + return $this->db->table(User::TABLE)->eq('id', $user_id)->findOneColumn('notifications_filter'); + } + + /** + * Save selected filter for a user + * + * @access public + * @param integer $user_id + * @param string $filter + */ + public function saveUserFilter($user_id, $filter) + { + $this->db->table(User::TABLE)->eq('id', $user_id)->update(array( + 'notifications_filter' => $filter, + )); + } + + /** + * Get user selected projects + * + * @access public + * @param integer $user_id + * @return array + */ + public function getUserSelectedProjects($user_id) + { + return $this->db->table(self::PROJECT_TABLE)->eq('user_id', $user_id)->findAllByColumn('project_id'); + } + + /** + * Save selected projects for a user + * + * @access public + * @param integer $user_id + * @param integer[] $project_ids + */ + public function saveUserSelectedProjects($user_id, array $project_ids) + { + $this->db->table(self::PROJECT_TABLE)->eq('user_id', $user_id)->remove(); + + foreach ($project_ids as $project_id) { + $this->db->table(self::PROJECT_TABLE)->insert(array( + 'user_id' => $user_id, + 'project_id' => $project_id, + )); + } + } + + /** + * Return true if the user should receive notification + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function shouldReceiveNotification(array $user, array $event_data) + { + $filters = array( + 'filterNone', + 'filterAssignee', + 'filterCreator', + 'filterBoth', + ); + + foreach ($filters as $filter) { + if ($this->$filter($user, $event_data)) { + return $this->filterProject($user, $event_data); + } + } + + return false; + } + + /** + * Return true if the user will receive all notifications + * + * @access public + * @param array $user + * @return boolean + */ + public function filterNone(array $user) + { + return $user['notifications_filter'] == self::FILTER_NONE; + } + + /** + * Return true if the user is the assignee and selected the filter "assignee" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterAssignee(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_ASSIGNEE && $event_data['task']['owner_id'] == $user['id']; + } + + /** + * Return true if the user is the creator and enabled the filter "creator" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterCreator(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_CREATOR && $event_data['task']['creator_id'] == $user['id']; + } + + /** + * Return true if the user is the assignee or the creator and selected the filter "both" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterBoth(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_BOTH && + ($event_data['task']['creator_id'] == $user['id'] || $event_data['task']['owner_id'] == $user['id']); + } + + /** + * Return true if the user want to receive notification for the selected project + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterProject(array $user, array $event_data) + { + $projects = $this->getUserSelectedProjects($user['id']); + + if (! empty($projects)) { + return in_array($event_data['task']['project_id'], $projects); + } + + return true; + } +} diff --git a/sources/app/Model/NotificationType.php b/sources/app/Model/NotificationType.php new file mode 100644 index 0000000..3fd5c15 --- /dev/null +++ b/sources/app/Model/NotificationType.php @@ -0,0 +1,73 @@ + t('Email'), + self::TYPE_WEB => t('Web'), + ); + } + + /** + * Get selected notification types for a given user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getUserSelectedTypes($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('notification_type')->findAllByColumn('notification_type'); + } + + /** + * Save notification types for a given user + * + * @access public + * @param integer $user_id + * @param string[] $types + * @return boolean + */ + public function saveUserSelectedTypes($user_id, array $types) + { + $results = array(); + $this->db->table(self::TABLE)->eq('user_id', $user_id)->remove(); + + foreach ($types as $type) { + $results[] = $this->db->table(self::TABLE)->insert(array('user_id' => $user_id, 'notification_type' => $type)); + } + + return ! in_array(false, $results); + } +} diff --git a/sources/app/Model/OverdueNotification.php b/sources/app/Model/OverdueNotification.php new file mode 100644 index 0000000..19bd098 --- /dev/null +++ b/sources/app/Model/OverdueNotification.php @@ -0,0 +1,60 @@ +taskFinder->getOverdueTasks(); + + foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) { + + // Get the list of users that should receive notifications for each projects + $users = $this->notification->getUsersWithNotificationEnabled($project_id); + + foreach ($users as $user) { + $this->sendUserOverdueTaskNotifications($user, $project_tasks); + } + } + + return $tasks; + } + + /** + * Send overdue tasks for a given user + * + * @access public + * @param array $user + * @param array $tasks + */ + public function sendUserOverdueTaskNotifications(array $user, array $tasks) + { + $user_tasks = array(); + + foreach ($tasks as $task) { + if ($this->notificationFilter->shouldReceiveNotification($user, array('task' => $task))) { + $user_tasks[] = $task; + } + } + + if (! empty($user_tasks)) { + $this->notification->sendUserNotification( + $user, + Task::EVENT_OVERDUE, + array('tasks' => $user_tasks, 'project_name' => $tasks[0]['project_name']) + ); + } + } +} diff --git a/sources/app/Model/Project.php b/sources/app/Model/Project.php index 5250082..1bd5b62 100644 --- a/sources/app/Model/Project.php +++ b/sources/app/Model/Project.php @@ -114,54 +114,6 @@ class Project extends Base return $this->db->table(self::TABLE)->eq('id', $project_id)->eq('is_private', 1)->exists(); } - /** - * Get all projects to generate the Gantt chart - * - * @access public - * @param array $project_ids - * @return array - */ - public function getGanttBars(array $project_ids) - { - if (empty($project_ids)) { - return array(); - } - - $colors = $this->color->getDefaultColors(); - $projects = $this->db->table(self::TABLE)->asc('start_date')->in('id', $project_ids)->eq('is_active', self::ACTIVE)->eq('is_private', 0)->findAll(); - $bars = array(); - - foreach ($projects as $project) { - $start = empty($project['start_date']) ? time() : strtotime($project['start_date']); - $end = empty($project['end_date']) ? $start : strtotime($project['end_date']); - $color = next($colors) ?: reset($colors); - - $bars[] = array( - 'type' => 'project', - 'id' => $project['id'], - 'title' => $project['name'], - 'start' => array( - (int) date('Y', $start), - (int) date('n', $start), - (int) date('j', $start), - ), - 'end' => array( - (int) date('Y', $end), - (int) date('n', $end), - (int) date('j', $end), - ), - 'link' => $this->helper->url->href('project', 'show', array('project_id' => $project['id'])), - 'board_link' => $this->helper->url->href('board', 'show', array('project_id' => $project['id'])), - 'gantt_link' => $this->helper->url->href('gantt', 'project', array('project_id' => $project['id'])), - 'color' => $color, - 'not_defined' => empty($project['start_date']) || empty($project['end_date']), - 'users' => $this->projectPermission->getProjectUsers($project['id']), - ); - } - - return $bars; - } - /** * Get all projects * @@ -173,6 +125,22 @@ class Project extends Base return $this->db->table(self::TABLE)->asc('name')->findAll(); } + /** + * Get all projects with given Ids + * + * @access public + * @param integer[] $project_ids + * @return array + */ + public function getAllByIds(array $project_ids) + { + if (empty($project_ids)) { + return array(); + } + + return $this->db->table(self::TABLE)->in('id', $project_ids)->asc('name')->findAll(); + } + /** * Get all project ids * diff --git a/sources/app/Model/Subtask.php b/sources/app/Model/Subtask.php index d8a44af..3c6269f 100644 --- a/sources/app/Model/Subtask.php +++ b/sources/app/Model/Subtask.php @@ -49,12 +49,13 @@ class Subtask extends Base */ const EVENT_UPDATE = 'subtask.update'; const EVENT_CREATE = 'subtask.create'; + const EVENT_DELETE = 'subtask.delete'; /** * Get available status * * @access public - * @return array + * @return string[] */ public function getStatusList() { @@ -173,6 +174,23 @@ class Subtask extends Base $this->resetFields($values, array('time_estimated', 'time_spent')); } + /** + * 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 * @@ -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) { @@ -217,20 +233,20 @@ class Subtask extends Base * Update * * @access public - * @param array $values Form values + * @param array $values Form values + * @param bool $fire_events If true, will be called an event * @return bool */ - public function update(array $values) + public function update(array $values, $fire_events = true) { $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) - ); + if ($result && $fire_events) { + $event = $subtask; + $event['changes'] = array_diff_assoc($values, $subtask); + $this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new SubtaskEvent($event)); } return $result; @@ -302,7 +318,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 +417,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/sources/app/Model/SubtaskForecast.php b/sources/app/Model/SubtaskForecast.php deleted file mode 100644 index 263aa27..0000000 --- a/sources/app/Model/SubtaskForecast.php +++ /dev/null @@ -1,124 +0,0 @@ -db - ->table(Subtask::TABLE) - ->columns(Subtask::TABLE.'.id', Task::TABLE.'.project_id', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title', Subtask::TABLE.'.time_estimated') - ->join(Task::TABLE, 'id', 'task_id') - ->asc(Task::TABLE.'.position') - ->asc(Subtask::TABLE.'.position') - ->gt(Subtask::TABLE.'.time_estimated', 0) - ->eq(Subtask::TABLE.'.status', Subtask::STATUS_TODO) - ->eq(Subtask::TABLE.'.user_id', $user_id) - ->findAll(); - } - - /** - * Get the start date for the forecast - * - * @access public - * @param integer $user_id - * @return array - */ - public function getStartDate($user_id) - { - $subtask = $this->db->table(Subtask::TABLE) - ->columns(Subtask::TABLE.'.time_estimated', SubtaskTimeTracking::TABLE.'.start') - ->eq(SubtaskTimeTracking::TABLE.'.user_id', $user_id) - ->eq(SubtaskTimeTracking::TABLE.'.end', 0) - ->status('status', Subtask::STATUS_INPROGRESS) - ->join(SubtaskTimeTracking::TABLE, 'subtask_id', 'id') - ->findOne(); - - if ($subtask && $subtask['time_estimated'] && $subtask['start']) { - return date('Y-m-d H:i', $subtask['start'] + $subtask['time_estimated'] * 3600); - } - - return date('Y-m-d H:i'); - } - - /** - * Get all calendar events according to the user timetable and the subtasks estimates - * - * @access public - * @param integer $user_id - * @param string $end End date of the calendar - * @return array - */ - public function getCalendarEvents($user_id, $end) - { - $events = array(); - $start_date = new DateTime($this->getStartDate($user_id)); - $timetable = $this->timetable->calculate($user_id, $start_date, new DateTime($end)); - $subtasks = $this->getSubtasks($user_id); - $total = count($subtasks); - $offset = 0; - - foreach ($timetable as $slot) { - - $interval = $this->dateParser->getHours($slot[0], $slot[1]); - $start = $slot[0]->getTimestamp(); - - if ($slot[0] < $start_date) { - - if (! $this->dateParser->withinDateRange($start_date, $slot[0], $slot[1])) { - continue; - } - - $interval = $this->dateParser->getHours(new DateTime, $slot[1]); - $start = time(); - } - - while ($offset < $total) { - - $event = array( - 'id' => $subtasks[$offset]['id'].'-'.$subtasks[$offset]['task_id'].'-'.$offset, - 'subtask_id' => $subtasks[$offset]['id'], - 'title' => t('#%d', $subtasks[$offset]['task_id']).' '.$subtasks[$offset]['title'], - 'url' => $this->helper->url->to('task', 'show', array('task_id' => $subtasks[$offset]['task_id'], 'project_id' => $subtasks[$offset]['project_id'])), - 'editable' => false, - 'start' => date('Y-m-d\TH:i:s', $start), - ); - - if ($subtasks[$offset]['time_estimated'] <= $interval) { - - $start += $subtasks[$offset]['time_estimated'] * 3600; - $interval -= $subtasks[$offset]['time_estimated']; - $offset++; - - $event['end'] = date('Y-m-d\TH:i:s', $start); - $events[] = $event; - } - else { - $subtasks[$offset]['time_estimated'] -= $interval; - $event['end'] = $slot[1]->format('Y-m-d\TH:i:s'); - $events[] = $event; - break; - } - } - } - - return $events; - } -} diff --git a/sources/app/Model/SubtaskTimeTracking.php b/sources/app/Model/SubtaskTimeTracking.php index 997031e..4a18c8a 100644 --- a/sources/app/Model/SubtaskTimeTracking.php +++ b/sources/app/Model/SubtaskTimeTracking.php @@ -150,13 +150,14 @@ class SubtaskTimeTracking extends Base * * @access public * @param integer $user_id - * @param integer $start - * @param integer $end + * @param string $start ISO-8601 format + * @param string $end * @return array */ public function getUserCalendarEvents($user_id, $start, $end) { - $result = $this->getUserQuery($user_id) + $hook = 'model:subtask-time-tracking:calendar:events'; + $events = $this->getUserQuery($user_id) ->addCondition($this->getCalendarCondition( $this->dateParser->getTimestampFromIsoFormat($start), $this->dateParser->getTimestampFromIsoFormat($end), @@ -165,9 +166,16 @@ class SubtaskTimeTracking extends Base )) ->findAll(); - $result = $this->timetable->calculateEventsIntersect($user_id, $result, $start, $end); + if ($this->hook->exists($hook)) { + $events = $this->hook->first($hook, array( + 'user_id' => $user_id, + 'events' => $events, + 'start' => $start, + 'end' => $end, + )); + } - return $this->toCalendarEvents($result); + return $this->toCalendarEvents($events); } /** @@ -293,6 +301,7 @@ class SubtaskTimeTracking extends Base */ public function getTimeSpent($subtask_id, $user_id) { + $hook = 'model:subtask-time-tracking:calculate:time-spent'; $start_time = $this->db ->table(self::TABLE) ->eq('subtask_id', $subtask_id) @@ -300,14 +309,23 @@ class SubtaskTimeTracking extends Base ->eq('end', 0) ->findOneColumn('start'); - if ($start_time) { - $start = new DateTime; - $start->setTimestamp($start_time); - - return $this->timetable->calculateEffectiveDuration($user_id, $start, new DateTime); + if (empty($start_time)) { + return 0; } - return 0; + $end = new DateTime; + $start = new DateTime; + $start->setTimestamp($start_time); + + if ($this->hook->exists($hook)) { + return $this->hook->first($hook, array( + 'user_id' => $user_id, + 'start' => $start, + 'end' => $end, + )); + } + + return $this->dateParser->getHours($start, $end); } /** @@ -327,7 +345,7 @@ class SubtaskTimeTracking extends Base 'id' => $subtask['id'], 'time_spent' => $subtask['time_spent'] + $time_spent, 'task_id' => $subtask['task_id'], - )); + ), false); } /** @@ -339,20 +357,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 +378,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/sources/app/Model/Swimlane.php b/sources/app/Model/Swimlane.php index 3b78a40..06e879a 100644 --- a/sources/app/Model/Swimlane.php +++ b/sources/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/sources/app/Model/TaskDuplication.php b/sources/app/Model/TaskDuplication.php index 8048f03..958b2b3 100755 --- a/sources/app/Model/TaskDuplication.php +++ b/sources/app/Model/TaskDuplication.php @@ -105,7 +105,7 @@ class TaskDuplication extends Base { $values = $this->copyFields($task_id); $values['project_id'] = $project_id; - $values['column_id'] = $column_id !== null ? $column_id : $this->board->getFirstColumn($project_id); + $values['column_id'] = $column_id !== null ? $column_id : $values['column_id']; $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $values['swimlane_id']; $values['category_id'] = $category_id !== null ? $category_id : $values['category_id']; $values['owner_id'] = $owner_id !== null ? $owner_id : $values['owner_id']; @@ -134,7 +134,7 @@ class TaskDuplication extends Base $values = array(); $values['is_active'] = 1; $values['project_id'] = $project_id; - $values['column_id'] = $column_id !== null ? $column_id : $this->board->getFirstColumn($project_id); + $values['column_id'] = $column_id !== null ? $column_id : $task['column_id']; $values['position'] = $this->taskFinder->countByColumnId($project_id, $values['column_id']) + 1; $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $task['swimlane_id']; $values['category_id'] = $category_id !== null ? $category_id : $task['category_id']; @@ -181,6 +181,16 @@ class TaskDuplication extends Base ); } + // Check if the column exists for the destination project + if ($values['column_id'] > 0) { + $values['column_id'] = $this->board->getColumnIdByTitle( + $values['project_id'], + $this->board->getColumnTitleById($values['column_id']) + ); + + $values['column_id'] = $values['column_id'] ?: $this->board->getFirstColumn($values['project_id']); + } + return $values; } diff --git a/sources/app/Model/TaskFilter.php b/sources/app/Model/TaskFilter.php index 89ad9aa..efd3be6 100644 --- a/sources/app/Model/TaskFilter.php +++ b/sources/app/Model/TaskFilter.php @@ -2,11 +2,6 @@ namespace Model; -use DateTime; -use Eluceo\iCal\Component\Calendar; -use Eluceo\iCal\Component\Event; -use Eluceo\iCal\Property\Event\Attendees; - /** * Task Filter * @@ -15,6 +10,28 @@ use Eluceo\iCal\Property\Event\Attendees; */ class TaskFilter extends Base { + /** + * Filters mapping + * + * @access private + * @var array + */ + private $filters = array( + 'T_ASSIGNEE' => 'filterByAssignee', + 'T_COLOR' => 'filterByColors', + 'T_DUE' => 'filterByDueDate', + 'T_UPDATED' => 'filterByModificationDate', + 'T_CREATED' => 'filterByCreationDate', + 'T_TITLE' => 'filterByTitle', + 'T_STATUS' => 'filterByStatusName', + 'T_DESCRIPTION' => 'filterByDescription', + 'T_CATEGORY' => 'filterByCategoryName', + 'T_PROJECT' => 'filterByProjectName', + 'T_COLUMN' => 'filterByColumnName', + 'T_REFERENCE' => 'filterByReference', + 'T_SWIMLANE' => 'filterBySwimlaneName', + ); + /** * Query * @@ -40,47 +57,8 @@ class TaskFilter extends Base } foreach ($tree as $filter => $value) { - switch ($filter) { - case 'T_ASSIGNEE': - $this->filterByAssignee($value); - break; - case 'T_COLOR': - $this->filterByColors($value); - break; - case 'T_DUE': - $this->filterByDueDate($value); - break; - case 'T_UPDATED': - $this->filterByModificationDate($value); - break; - case 'T_CREATED': - $this->filterByCreationDate($value); - break; - case 'T_TITLE': - $this->filterByTitle($value); - break; - case 'T_STATUS': - $this->filterByStatusName($value); - break; - case 'T_DESCRIPTION': - $this->filterByDescription($value); - break; - case 'T_CATEGORY': - $this->filterByCategoryName($value); - break; - case 'T_PROJECT': - $this->filterByProjectName($value); - break; - case 'T_COLUMN': - $this->filterByColumnName($value); - break; - case 'T_REFERENCE': - $this->filterByReference($value); - break; - case 'T_SWIMLANE': - $this->filterBySwimlaneName($value); - break; - } + $method = $this->filters[$filter]; + $this->$method($value); } return $this; @@ -137,7 +115,7 @@ class TaskFilter extends Base */ public function copy() { - $filter = clone($this); + $filter = new static($this->container); $filter->query = clone($this->query); $filter->query->condition = clone($this->query->condition); return $filter; @@ -674,223 +652,6 @@ class TaskFilter extends Base }); } - /** - * Format tasks to be displayed in the Gantt chart - * - * @access public - * @return array - */ - public function toGanttBars() - { - $bars = array(); - $columns = array(); - - foreach ($this->query->findAll() as $task) { - if (! isset($column_count[$task['project_id']])) { - $columns[$task['project_id']] = $this->board->getColumnsList($task['project_id']); - } - - $start = $task['date_started'] ?: time(); - $end = $task['date_due'] ?: $start; - - $bars[] = array( - 'type' => 'task', - 'id' => $task['id'], - 'title' => $task['title'], - 'start' => array( - (int) date('Y', $start), - (int) date('n', $start), - (int) date('j', $start), - ), - 'end' => array( - (int) date('Y', $end), - (int) date('n', $end), - (int) date('j', $end), - ), - 'column_title' => $task['column_name'], - 'assignee' => $task['assignee_name'] ?: $task['assignee_username'], - 'progress' => $this->task->getProgress($task, $columns[$task['project_id']]).'%', - 'link' => $this->helper->url->href('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), - 'color' => $this->color->getColorProperties($task['color_id']), - 'not_defined' => empty($task['date_due']) || empty($task['date_started']), - ); - } - - return $bars; - } - - /** - * Format the results to the ajax autocompletion - * - * @access public - * @return array - */ - public function toAutoCompletion() - { - return $this->query->columns(Task::TABLE.'.id', Task::TABLE.'.title')->callback(function(array $results) { - - foreach ($results as &$result) { - $result['value'] = $result['title']; - $result['label'] = '#'.$result['id'].' - '.$result['title']; - } - - return $results; - - })->findAll(); - } - - /** - * Transform results to calendar events - * - * @access public - * @param string $start_column Column name for the start date - * @param string $end_column Column name for the end date - * @return array - */ - public function toDateTimeCalendarEvents($start_column, $end_column) - { - $events = array(); - - foreach ($this->query->findAll() as $task) { - - $events[] = array_merge( - $this->getTaskCalendarProperties($task), - array( - 'start' => date('Y-m-d\TH:i:s', $task[$start_column]), - 'end' => date('Y-m-d\TH:i:s', $task[$end_column] ?: time()), - 'editable' => false, - ) - ); - } - - return $events; - } - - /** - * Transform results to all day calendar events - * - * @access public - * @param string $column Column name for the date - * @return array - */ - public function toAllDayCalendarEvents($column = 'date_due') - { - $events = array(); - - foreach ($this->query->findAll() as $task) { - - $events[] = array_merge( - $this->getTaskCalendarProperties($task), - array( - 'start' => date('Y-m-d', $task[$column]), - 'end' => date('Y-m-d', $task[$column]), - 'allday' => true, - ) - ); - } - - return $events; - } - - /** - * Transform results to ical events - * - * @access public - * @param string $start_column Column name for the start date - * @param string $end_column Column name for the end date - * @param Calendar $vCalendar Calendar object - * @return Calendar - */ - public function addDateTimeIcalEvents($start_column, $end_column, Calendar $vCalendar = null) - { - if ($vCalendar === null) { - $vCalendar = new Calendar('Kanboard'); - } - - foreach ($this->query->findAll() as $task) { - - $start = new DateTime; - $start->setTimestamp($task[$start_column]); - - $end = new DateTime; - $end->setTimestamp($task[$end_column] ?: time()); - - $vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$start_column.'-'.$end_column); - $vEvent->setDtStart($start); - $vEvent->setDtEnd($end); - - $vCalendar->addComponent($vEvent); - } - - return $vCalendar; - } - - /** - * Transform results to all day ical events - * - * @access public - * @param string $column Column name for the date - * @param Calendar $vCalendar Calendar object - * @return Calendar - */ - public function addAllDayIcalEvents($column = 'date_due', Calendar $vCalendar = null) - { - if ($vCalendar === null) { - $vCalendar = new Calendar('Kanboard'); - } - - foreach ($this->query->findAll() as $task) { - - $date = new DateTime; - $date->setTimestamp($task[$column]); - - $vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$column); - $vEvent->setDtStart($date); - $vEvent->setDtEnd($date); - $vEvent->setNoTime(true); - - $vCalendar->addComponent($vEvent); - } - - return $vCalendar; - } - - /** - * Get common events for task ical events - * - * @access protected - * @param array $task - * @param string $uid - * @return Event - */ - protected function getTaskIcalEvent(array &$task, $uid) - { - $dateCreation = new DateTime; - $dateCreation->setTimestamp($task['date_creation']); - - $dateModif = new DateTime; - $dateModif->setTimestamp($task['date_modification']); - - $vEvent = new Event($uid); - $vEvent->setCreated($dateCreation); - $vEvent->setModified($dateModif); - $vEvent->setUseTimezone(true); - $vEvent->setSummary(t('#%d', $task['id']).' '.$task['title']); - $vEvent->setUrl($this->helper->url->base().$this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); - - if (! empty($task['owner_id'])) { - $vEvent->setOrganizer($task['assignee_name'] ?: $task['assignee_username'], $task['assignee_email']); - } - - if (! empty($task['creator_id'])) { - $attendees = new Attendees; - $attendees->add('MAILTO:'.($task['creator_email'] ?: $task['creator_username'].'@kanboard.local')); - $vEvent->setAttendees($attendees); - } - - return $vEvent; - } - /** * Filter with an operator * diff --git a/sources/app/Model/TaskFinder.php b/sources/app/Model/TaskFinder.php index 3e76041..9e76e62 100644 --- a/sources/app/Model/TaskFinder.php +++ b/sources/app/Model/TaskFinder.php @@ -93,7 +93,7 @@ class TaskFinder extends Base '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', '(SELECT count(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', - '(SELECT 1 FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id AND '.TaskLink::TABLE.'.link_id = 9) AS is_milestone', + '(SELECT DISTINCT 1 FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id AND '.TaskLink::TABLE.'.link_id = 9) AS is_milestone', 'tasks.id', 'tasks.reference', 'tasks.title', @@ -177,14 +177,14 @@ class TaskFinder extends Base } /** - * Get a list of overdue tasks for all projects + * Get overdue tasks query * * @access public - * @return array + * @return \PicoDb\Table */ - public function getOverdueTasks() + public function getOverdueTasksQuery() { - $tasks = $this->db->table(Task::TABLE) + return $this->db->table(Task::TABLE) ->columns( Task::TABLE.'.id', Task::TABLE.'.title', @@ -201,10 +201,42 @@ class TaskFinder extends Base ->eq(Project::TABLE.'.is_active', 1) ->eq(Task::TABLE.'.is_active', 1) ->neq(Task::TABLE.'.date_due', 0) - ->lte(Task::TABLE.'.date_due', mktime(23, 59, 59)) - ->findAll(); + ->lte(Task::TABLE.'.date_due', mktime(23, 59, 59)); + } - return $tasks; + /** + * Get a list of overdue tasks for all projects + * + * @access public + * @return array + */ + public function getOverdueTasks() + { + return $this->getOverdueTasksQuery()->findAll(); + } + + /** + * Get a list of overdue tasks by project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getOverdueTasksByProject($project_id) + { + return $this->getOverdueTasksQuery()->eq(Task::TABLE.'.project_id', $project_id)->findAll(); + } + + /** + * Get a list of overdue tasks by user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getOverdueTasksByUser($user_id) + { + return $this->getOverdueTasksQuery()->eq(Task::TABLE.'.owner_id', $user_id)->findAll(); } /** diff --git a/sources/app/Model/TaskLink.php b/sources/app/Model/TaskLink.php index 466e44d..1c7d628 100644 --- a/sources/app/Model/TaskLink.php +++ b/sources/app/Model/TaskLink.php @@ -82,13 +82,15 @@ class TaskLink extends Base Task::TABLE.'.owner_id AS task_assignee_id', User::TABLE.'.username AS task_assignee_username', User::TABLE.'.name AS task_assignee_name', - Board::TABLE.'.title AS column_title' + Board::TABLE.'.title AS column_title', + Project::TABLE.'.name AS project_name' ) ->eq(self::TABLE.'.task_id', $task_id) ->join(Link::TABLE, 'id', 'link_id') ->join(Task::TABLE, 'id', 'opposite_task_id') ->join(Board::TABLE, 'id', 'column_id', Task::TABLE) ->join(User::TABLE, 'id', 'owner_id', Task::TABLE) + ->join(Project::TABLE, 'id', 'project_id', Task::TABLE) ->asc(Link::TABLE.'.id') ->desc(Board::TABLE.'.position') ->desc(Task::TABLE.'.is_active') diff --git a/sources/app/Model/TaskStatus.php b/sources/app/Model/TaskStatus.php index 3f62667..23d77fa 100644 --- a/sources/app/Model/TaskStatus.php +++ b/sources/app/Model/TaskStatus.php @@ -12,23 +12,6 @@ use Event\TaskEvent; */ class TaskStatus extends Base { - /** - * Return the list of statuses - * - * @access public - * @param boolean $prepend Prepend default value - * @return array - */ - public function getList($prepend = false) - { - $listing = $prepend ? array(-1 => t('All status')) : array(); - - return $listing + array( - Task::STATUS_OPEN => t('Open'), - Task::STATUS_CLOSED => t('Closed'), - ); - } - /** * Return true if the task is closed * diff --git a/sources/app/Model/Timetable.php b/sources/app/Model/Timetable.php deleted file mode 100644 index 6ddf826..0000000 --- a/sources/app/Model/Timetable.php +++ /dev/null @@ -1,356 +0,0 @@ -setTime(0, 0); - - $end_dt = new DateTime($end); - $end_dt->setTime(23, 59); - - $timetable = $this->calculate($user_id, $start_dt, $end_dt); - - // The user has no timetable - if (empty($this->week)) { - return $events; - } - - $results = array(); - - foreach ($events as $event) { - $results = array_merge($results, $this->calculateEventIntersect($event, $timetable)); - } - - return $results; - } - - /** - * Get a serie of events based on the timetable and the provided event - * - * @access public - * @param array $event - * @param array $timetable - * @return array - */ - public function calculateEventIntersect(array $event, array $timetable) - { - $events = array(); - - foreach ($timetable as $slot) { - - $start_ts = $slot[0]->getTimestamp(); - $end_ts = $slot[1]->getTimestamp(); - - if ($start_ts > $event['end']) { - break; - } - - if ($event['start'] <= $start_ts) { - $event['start'] = $start_ts; - } - - if ($event['start'] >= $start_ts && $event['start'] <= $end_ts) { - - if ($event['end'] >= $end_ts) { - $events[] = array_merge($event, array('end' => $end_ts)); - } - else { - $events[] = $event; - break; - } - } - } - - return $events; - } - - /** - * Calculate effective worked hours by taking into consideration the timetable - * - * @access public - * @param integer $user_id - * @param \DateTime $start - * @param \DateTime $end - * @return float - */ - public function calculateEffectiveDuration($user_id, DateTime $start, DateTime $end) - { - $end_timetable = clone($end); - $end_timetable->setTime(23, 59); - - $timetable = $this->calculate($user_id, $start, $end_timetable); - $found_start = false; - $hours = 0; - - // The user has no timetable - if (empty($this->week)) { - return $this->dateParser->getHours($start, $end); - } - - foreach ($timetable as $slot) { - - $isStartSlot = $this->dateParser->withinDateRange($start, $slot[0], $slot[1]); - $isEndSlot = $this->dateParser->withinDateRange($end, $slot[0], $slot[1]); - - // Start and end are within the same time slot - if ($isStartSlot && $isEndSlot) { - return $this->dateParser->getHours($start, $end); - } - - // We found the start slot - if (! $found_start && $isStartSlot) { - $found_start = true; - $hours = $this->dateParser->getHours($start, $slot[1]); - } - else if ($found_start) { - - // We found the end slot - if ($isEndSlot) { - $hours += $this->dateParser->getHours($slot[0], $end); - break; - } - else { - - // Sum hours of the intermediate time slots - $hours += $this->dateParser->getHours($slot[0], $slot[1]); - } - } - } - - // The start date was not found in regular hours so we get the nearest time slot - if (! empty($timetable) && ! $found_start) { - $slot = $this->findClosestTimeSlot($start, $timetable); - - if ($start < $slot[0]) { - return $this->calculateEffectiveDuration($user_id, $slot[0], $end); - } - } - - return $hours; - } - - /** - * Find the nearest time slot - * - * @access public - * @param DateTime $date - * @param array $timetable - * @return array - */ - public function findClosestTimeSlot(DateTime $date, array $timetable) - { - $values = array(); - - foreach ($timetable as $slot) { - $t1 = abs($slot[0]->getTimestamp() - $date->getTimestamp()); - $t2 = abs($slot[1]->getTimestamp() - $date->getTimestamp()); - - $values[] = min($t1, $t2); - } - - asort($values); - return $timetable[key($values)]; - } - - /** - * Get the timetable for a user for a given date range - * - * @access public - * @param integer $user_id - * @param \DateTime $start - * @param \DateTime $end - * @return array - */ - public function calculate($user_id, DateTime $start, DateTime $end) - { - $timetable = array(); - - $this->day = $this->timetableDay->getByUser($user_id); - $this->week = $this->timetableWeek->getByUser($user_id); - $this->overtime = $this->timetableExtra->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); - $this->timeoff = $this->timetableOff->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); - - for ($today = clone($start); $today <= $end; $today->add(new DateInterval('P1D'))) { - $week_day = $today->format('N'); - $timetable = array_merge($timetable, $this->getWeekSlots($today, $week_day)); - $timetable = array_merge($timetable, $this->getOvertimeSlots($today, $week_day)); - } - - return $timetable; - } - - /** - * Return worked time slots for the given day - * - * @access public - * @param \DateTime $today - * @param string $week_day - * @return array - */ - public function getWeekSlots(DateTime $today, $week_day) - { - $slots = array(); - $dayoff = $this->getDayOff($today); - - if (! empty($dayoff) && $dayoff['all_day'] == 1) { - return array(); - } - - foreach ($this->week as $slot) { - if ($week_day == $slot['day']) { - $slots = array_merge($slots, $this->getDayWorkSlots($slot, $dayoff, $today)); - } - } - - return $slots; - } - - /** - * Get the overtime time slots for the given day - * - * @access public - * @param \DateTime $today - * @param string $week_day - * @return array - */ - public function getOvertimeSlots(DateTime $today, $week_day) - { - $slots = array(); - - foreach ($this->overtime as $slot) { - - $day = new DateTime($slot['date']); - - if ($week_day == $day->format('N')) { - - if ($slot['all_day'] == 1) { - $slots = array_merge($slots, $this->getDaySlots($today)); - } - else { - $slots[] = $this->getTimeSlot($slot, $day); - } - } - } - - return $slots; - } - - /** - * Get worked time slots and remove time off - * - * @access public - * @param array $slot - * @param array $dayoff - * @param \DateTime $today - * @return array - */ - public function getDayWorkSlots(array $slot, array $dayoff, DateTime $today) - { - $slots = array(); - - if (! empty($dayoff) && $dayoff['start'] < $slot['end']) { - - if ($dayoff['start'] > $slot['start']) { - $slots[] = $this->getTimeSlot(array('end' => $dayoff['start']) + $slot, $today); - } - - if ($dayoff['end'] < $slot['end']) { - $slots[] = $this->getTimeSlot(array('start' => $dayoff['end']) + $slot, $today); - } - } - else { - $slots[] = $this->getTimeSlot($slot, $today); - } - - return $slots; - } - - /** - * Get regular day work time slots - * - * @access public - * @param \DateTime $today - * @return array - */ - public function getDaySlots(DateTime $today) - { - $slots = array(); - - foreach ($this->day as $day) { - $slots[] = $this->getTimeSlot($day, $today); - } - - return $slots; - } - - /** - * Get the start and end time slot for a given day - * - * @access public - * @param array $slot - * @param \DateTime $today - * @return array - */ - public function getTimeSlot(array $slot, DateTime $today) - { - $date = $today->format('Y-m-d'); - - return array( - new DateTime($date.' '.$slot['start']), - new DateTime($date.' '.$slot['end']), - ); - } - - /** - * Return day off time slot - * - * @access public - * @param \DateTime $today - * @return array - */ - public function getDayOff(DateTime $today) - { - foreach ($this->timeoff as $day) { - - if ($day['date'] === $today->format('Y-m-d')) { - return $day; - } - } - - return array(); - } -} diff --git a/sources/app/Model/TimetableDay.php b/sources/app/Model/TimetableDay.php deleted file mode 100644 index 0c7bf20..0000000 --- a/sources/app/Model/TimetableDay.php +++ /dev/null @@ -1,87 +0,0 @@ -db->table(self::TABLE)->eq('user_id', $user_id)->asc('start')->findAll(); - } - - /** - * Add a new time slot in the database - * - * @access public - * @param integer $user_id User id - * @param string $start Start hour (24h format) - * @param string $end End hour (24h format) - * @return boolean|integer - */ - public function create($user_id, $start, $end) - { - $values = array( - 'user_id' => $user_id, - 'start' => $start, - 'end' => $end, - ); - - return $this->persist(self::TABLE, $values); - } - - /** - * Remove a specific time slot - * - * @access public - * @param integer $slot_id - * @return boolean - */ - public function remove($slot_id) - { - return $this->db->table(self::TABLE)->eq('id', $slot_id)->remove(); - } - - /** - * Validate creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, array( - new Validators\Required('user_id', t('Field required')), - new Validators\Required('start', t('Field required')), - new Validators\Required('end', t('Field required')), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } -} diff --git a/sources/app/Model/TimetableExtra.php b/sources/app/Model/TimetableExtra.php deleted file mode 100644 index 48db662..0000000 --- a/sources/app/Model/TimetableExtra.php +++ /dev/null @@ -1,22 +0,0 @@ -db->table(static::TABLE)->eq('user_id', $user_id); - } - - /** - * Get the timetable for a given user - * - * @access public - * @param integer $user_id User id - * @return array - */ - public function getByUser($user_id) - { - return $this->db->table(static::TABLE)->eq('user_id', $user_id)->desc('date')->asc('start')->findAll(); - } - - /** - * Get the timetable for a given user - * - * @access public - * @param integer $user_id User id - * @param string $start_date - * @param string $end_date - * @return array - */ - public function getByUserAndDate($user_id, $start_date, $end_date) - { - return $this->db->table(static::TABLE) - ->eq('user_id', $user_id) - ->gte('date', $start_date) - ->lte('date', $end_date) - ->desc('date') - ->asc('start') - ->findAll(); - } - - /** - * Add a new time slot in the database - * - * @access public - * @param integer $user_id User id - * @param string $date Day (ISO8601 format) - * @param boolean $all_day All day flag - * @param float $start Start hour (24h format) - * @param float $end End hour (24h format) - * @param string $comment - * @return boolean|integer - */ - public function create($user_id, $date, $all_day, $start = '', $end = '', $comment = '') - { - $values = array( - 'user_id' => $user_id, - 'date' => $date, - 'all_day' => (int) $all_day, // Postgres fix - 'start' => $all_day ? '' : $start, - 'end' => $all_day ? '' : $end, - 'comment' => $comment, - ); - - return $this->persist(static::TABLE, $values); - } - - /** - * Remove a specific time slot - * - * @access public - * @param integer $slot_id - * @return boolean - */ - public function remove($slot_id) - { - return $this->db->table(static::TABLE)->eq('id', $slot_id)->remove(); - } - - /** - * Validate creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, array( - new Validators\Required('user_id', t('Field required')), - new Validators\Required('date', t('Field required')), - new Validators\Numeric('all_day', t('This value must be numeric')), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } -} diff --git a/sources/app/Model/TimetableWeek.php b/sources/app/Model/TimetableWeek.php deleted file mode 100644 index b22b3b7..0000000 --- a/sources/app/Model/TimetableWeek.php +++ /dev/null @@ -1,91 +0,0 @@ -db->table(self::TABLE)->eq('user_id', $user_id)->asc('day')->asc('start')->findAll(); - } - - /** - * Add a new time slot in the database - * - * @access public - * @param integer $user_id User id - * @param string $day Day of the week (ISO-8601) - * @param string $start Start hour (24h format) - * @param string $end End hour (24h format) - * @return boolean|integer - */ - public function create($user_id, $day, $start, $end) - { - $values = array( - 'user_id' => $user_id, - 'day' => $day, - 'start' => $start, - 'end' => $end, - ); - - return $this->persist(self::TABLE, $values); - } - - /** - * Remove a specific time slot - * - * @access public - * @param integer $slot_id - * @return boolean - */ - public function remove($slot_id) - { - return $this->db->table(self::TABLE)->eq('id', $slot_id)->remove(); - } - - /** - * Validate creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, array( - new Validators\Required('user_id', t('Field required')), - new Validators\Required('day', t('Field required')), - new Validators\Numeric('day', t('This value must be numeric')), - new Validators\Required('start', t('Field required')), - new Validators\Required('end', t('Field required')), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } -} diff --git a/sources/app/Model/User.php b/sources/app/Model/User.php index 8a7eff4..fd2ec95 100644 --- a/sources/app/Model/User.php +++ b/sources/app/Model/User.php @@ -91,7 +91,7 @@ class User extends Base ->table(User::TABLE) ->eq('id', $user_id) ->eq('is_admin', 1) - ->count() === 1; + ->exists(); } /** @@ -251,7 +251,7 @@ class User extends Base $result = array(); foreach ($users as $user) { - $result[$user['id']] = $user['name'] ?: $user['username']; + $result[$user['id']] = $this->getFullname($user); } asort($result); @@ -278,7 +278,9 @@ class User extends Base } $this->removeFields($values, array('confirmation', 'current_password')); - $this->resetFields($values, array('is_admin', 'is_ldap_user', 'is_project_admin')); + $this->resetFields($values, array('is_admin', 'is_ldap_user', 'is_project_admin', 'disable_login_form')); + $this->convertNullFields($values, array('gitlab_id')); + $this->convertIntegerFields($values, array('gitlab_id')); } /** diff --git a/sources/app/Model/WebNotification.php b/sources/app/Model/WebNotification.php new file mode 100644 index 0000000..4f4e8af --- /dev/null +++ b/sources/app/Model/WebNotification.php @@ -0,0 +1,156 @@ +db->table(self::TABLE)->insert(array( + 'user_id' => $user['id'], + 'date_creation' => time(), + 'event_name' => $event_name, + 'event_data' => json_encode($event_data), + )); + } + + /** + * Get all notifications for a user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getAll($user_id) + { + $events = $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('date_creation')->findAll(); + + foreach ($events as &$event) { + $event['event_data'] = json_decode($event['event_data'], true); + $event['title'] = $this->getTitleFromEvent($event['event_name'], $event['event_data']); + } + + return $events; + } + + /** + * Mark a notification as read + * + * @access public + * @param integer $user_id + * @param integer $notification_id + * @return boolean + */ + public function markAsRead($user_id, $notification_id) + { + return $this->db->table(self::TABLE)->eq('id', $notification_id)->eq('user_id', $user_id)->remove(); + } + + /** + * Mark all notifications as read for a user + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function markAllAsRead($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->remove(); + } + + /** + * Return true if the user as unread notifications + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function hasNotifications($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->exists(); + } + + /** + * Get title from event + * + * @access public + * @param string $event_name + * @param array $event_data + * @return string + */ + public function getTitleFromEvent($event_name, array $event_data) + { + switch ($event_name) { + case File::EVENT_CREATE: + $title = t('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']); + break; + case Comment::EVENT_CREATE: + $title = t('New comment on task #%d', $event_data['comment']['task_id']); + break; + case Comment::EVENT_UPDATE: + $title = t('Comment updated on task #%d', $event_data['comment']['task_id']); + break; + case Subtask::EVENT_CREATE: + $title = t('New subtask on task #%d', $event_data['subtask']['task_id']); + break; + case Subtask::EVENT_UPDATE: + $title = t('Subtask updated on task #%d', $event_data['subtask']['task_id']); + break; + case Task::EVENT_CREATE: + $title = t('New task #%d: %s', $event_data['task']['id'], $event_data['task']['title']); + break; + case Task::EVENT_UPDATE: + $title = t('Task updated #%d', $event_data['task']['id']); + break; + case Task::EVENT_CLOSE: + $title = t('Task #%d closed', $event_data['task']['id']); + break; + case Task::EVENT_OPEN: + $title = t('Task #%d opened', $event_data['task']['id']); + break; + case Task::EVENT_MOVE_COLUMN: + $title = t('Column changed for task #%d', $event_data['task']['id']); + break; + case Task::EVENT_MOVE_POSITION: + $title = t('New position for task #%d', $event_data['task']['id']); + break; + case Task::EVENT_MOVE_SWIMLANE: + $title = t('Swimlane changed for task #%d', $event_data['task']['id']); + break; + case Task::EVENT_ASSIGNEE_CHANGE: + $title = t('Assignee changed on task #%d', $event_data['task']['id']); + break; + case Task::EVENT_OVERDUE: + $nb = count($event_data['tasks']); + $title = $nb > 1 ? t('%d overdue tasks', $nb) : t('Task #%d is overdue', $event_data['tasks'][0]['id']); + break; + default: + $title = e('Notification'); + } + + return $title; + } +} diff --git a/sources/app/Schema/Mysql.php b/sources/app/Schema/Mysql.php index b1ac0ab..37894db 100644 --- a/sources/app/Schema/Mysql.php +++ b/sources/app/Schema/Mysql.php @@ -6,7 +6,96 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 85; +const VERSION = 90; + +function version_90($pdo) +{ + $pdo->exec("ALTER TABLE tasks MODIFY date_due BIGINT"); + $pdo->exec("ALTER TABLE tasks MODIFY date_creation BIGINT"); + $pdo->exec("ALTER TABLE tasks MODIFY date_completed BIGINT"); + $pdo->exec("ALTER TABLE tasks MODIFY date_started BIGINT"); + $pdo->exec("ALTER TABLE tasks MODIFY date_moved BIGINT"); + $pdo->exec("ALTER TABLE comments MODIFY date_creation BIGINT"); + $pdo->exec("ALTER TABLE last_logins MODIFY date_creation BIGINT"); + $pdo->exec("ALTER TABLE project_activities MODIFY date_creation BIGINT"); + $pdo->exec("ALTER TABLE projects MODIFY last_modified BIGINT"); + $pdo->exec("ALTER TABLE remember_me MODIFY date_creation BIGINT"); + $pdo->exec('ALTER TABLE files MODIFY `date` BIGINT'); + $pdo->exec('ALTER TABLE transitions MODIFY `date` BIGINT'); + $pdo->exec('ALTER TABLE subtask_time_tracking MODIFY `start` BIGINT'); + $pdo->exec('ALTER TABLE subtask_time_tracking MODIFY `end` BIGINT'); + $pdo->exec('ALTER TABLE users MODIFY `lock_expiration_date` BIGINT'); +} + +function version_89($pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_unread_notifications ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + date_creation BIGINT NOT NULL, + event_name VARCHAR(50) NOT NULL, + event_data TEXT NOT NULL, + PRIMARY KEY(id), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE user_has_notification_types ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + notification_type VARCHAR(50), + PRIMARY KEY(id), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec('CREATE UNIQUE INDEX user_has_notification_types_user_idx ON user_has_notification_types(user_id, notification_type)'); + + // Migrate people who have notification enabled before + $rq = $pdo->prepare('SELECT id FROM users WHERE notifications_enabled=1'); + $rq->execute(); + $user_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + foreach ($user_ids as $user_id) { + $rq = $pdo->prepare('INSERT INTO user_has_notification_types (user_id, notification_type) VALUES (?, ?)'); + $rq->execute(array($user_id, 'email')); + } +} + +function version_88($pdo) +{ + $pdo->exec(" + CREATE TABLE custom_filters ( + id INT NOT NULL AUTO_INCREMENT, + filter VARCHAR(100) NOT NULL, + project_id INT NOT NULL, + user_id INT NOT NULL, + name VARCHAR(100) NOT NULL, + is_shared TINYINT(1) DEFAULT 0, + PRIMARY KEY(id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +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) { @@ -150,7 +239,6 @@ function version_69($pdo) $result = $rq->fetch(PDO::FETCH_ASSOC); $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); - $rq->execute(array('calendar_user_subtasks_forecast', isset($result['subtask_forecast']) && $result['subtask_forecast'] == 1 ? 1 : 0)); $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); $rq->execute(array('calendar_user_tasks', 'date_started')); $rq->execute(array('calendar_project_tasks', 'date_started')); @@ -306,78 +394,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 ( - id INT NOT NULL AUTO_INCREMENT, - user_id INT NOT NULL, - start VARCHAR(5) NOT NULL, - end VARCHAR(5) NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, - PRIMARY KEY(id) - ) ENGINE=InnoDB CHARSET=utf8'); - - $pdo->exec('CREATE TABLE timetable_week ( - id INT NOT NULL AUTO_INCREMENT, - user_id INTEGER NOT NULL, - day INT NOT NULL, - start VARCHAR(5) NOT NULL, - end VARCHAR(5) NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, - PRIMARY KEY(id) - ) ENGINE=InnoDB CHARSET=utf8'); - - $pdo->exec('CREATE TABLE timetable_off ( - id INT NOT NULL AUTO_INCREMENT, - user_id INT NOT NULL, - date VARCHAR(10) NOT NULL, - all_day TINYINT(1) DEFAULT 0, - start VARCHAR(5) DEFAULT 0, - end VARCHAR(5) DEFAULT 0, - comment TEXT, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, - PRIMARY KEY(id) - ) ENGINE=InnoDB CHARSET=utf8'); - - $pdo->exec('CREATE TABLE timetable_extra ( - id INT NOT NULL AUTO_INCREMENT, - user_id INT NOT NULL, - date VARCHAR(10) NOT NULL, - all_day TINYINT(1) DEFAULT 0, - start VARCHAR(5) DEFAULT 0, - end VARCHAR(5) DEFAULT 0, - comment TEXT, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, - PRIMARY KEY(id) - ) ENGINE=InnoDB CHARSET=utf8'); -} - -function version_50($pdo) -{ - $pdo->exec("CREATE TABLE hourly_rates ( - id INT NOT NULL AUTO_INCREMENT, - user_id INT NOT NULL, - rate FLOAT DEFAULT 0, - date_effective INTEGER NOT NULL, - currency 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/sources/app/Schema/Postgres.php b/sources/app/Schema/Postgres.php index 9477b41..2a446e4 100644 --- a/sources/app/Schema/Postgres.php +++ b/sources/app/Schema/Postgres.php @@ -6,7 +6,90 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 65; +const VERSION = 70; + +function version_70($pdo) +{ + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_due TYPE BIGINT"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_completed TYPE BIGINT"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_started TYPE BIGINT"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_moved TYPE BIGINT"); + $pdo->exec("ALTER TABLE comments ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec("ALTER TABLE last_logins ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec("ALTER TABLE project_activities ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec("ALTER TABLE projects ALTER COLUMN last_modified TYPE BIGINT"); + $pdo->exec("ALTER TABLE remember_me ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec('ALTER TABLE files ALTER COLUMN "date" TYPE BIGINT'); + $pdo->exec('ALTER TABLE transitions ALTER COLUMN "date" TYPE BIGINT'); + $pdo->exec('ALTER TABLE subtask_time_tracking ALTER COLUMN "start" TYPE BIGINT'); + $pdo->exec('ALTER TABLE subtask_time_tracking ALTER COLUMN "end" TYPE BIGINT'); + $pdo->exec('ALTER TABLE users ALTER COLUMN "lock_expiration_date" TYPE BIGINT'); +} + +function version_69($pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_unread_notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + date_creation BIGINT NOT NULL, + event_name VARCHAR(50) NOT NULL, + event_data TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec(" + CREATE TABLE user_has_notification_types ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + notification_type VARCHAR(50), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('CREATE UNIQUE INDEX user_has_notification_types_user_idx ON user_has_notification_types(user_id, notification_type)'); + + // Migrate people who have notification enabled before + $rq = $pdo->prepare("SELECT id FROM users WHERE notifications_enabled='1'"); + $rq->execute(); + $user_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + foreach ($user_ids as $user_id) { + $rq = $pdo->prepare('INSERT INTO user_has_notification_types (user_id, notification_type) VALUES (?, ?)'); + $rq->execute(array($user_id, 'email')); + } +} + +function version_68($pdo) +{ + $pdo->exec(" + CREATE TABLE custom_filters ( + id SERIAL PRIMARY KEY, + filter VARCHAR(100) NOT NULL, + project_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + name VARCHAR(100) NOT NULL, + is_shared BOOLEAN DEFAULT '0' + ) + "); +} + +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) { @@ -146,7 +229,6 @@ function version_50($pdo) $result = $rq->fetch(PDO::FETCH_ASSOC); $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); - $rq->execute(array('calendar_user_subtasks_forecast', isset($result['subtask_forecast']) && $result['subtask_forecast'] == 1 ? 1 : 0)); $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); $rq->execute(array('calendar_user_tasks', 'date_started')); $rq->execute(array('calendar_project_tasks', 'date_started')); @@ -300,72 +382,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 ( - "id" SERIAL PRIMARY KEY, - "user_id" INTEGER NOT NULL, - "start" VARCHAR(5) NOT NULL, - "end" VARCHAR(5) NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )'); - - $pdo->exec('CREATE TABLE timetable_week ( - "id" SERIAL PRIMARY KEY, - "user_id" INTEGER NOT NULL, - "day" INTEGER NOT NULL, - "start" VARCHAR(5) NOT NULL, - "end" VARCHAR(5) NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )'); - - $pdo->exec('CREATE TABLE timetable_off ( - "id" SERIAL PRIMARY KEY, - "user_id" INTEGER NOT NULL, - "date" VARCHAR(10) NOT NULL, - "all_day" BOOLEAN DEFAULT \'0\', - "start" VARCHAR(5) DEFAULT 0, - "end" VARCHAR(5) DEFAULT 0, - "comment" TEXT, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )'); - - $pdo->exec('CREATE TABLE timetable_extra ( - "id" SERIAL PRIMARY KEY, - "user_id" INTEGER NOT NULL, - "date" VARCHAR(10) NOT NULL, - "all_day" BOOLEAN DEFAULT \'0\', - "start" VARCHAR(5) DEFAULT 0, - "end" VARCHAR(5) DEFAULT 0, - "comment" TEXT, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )'); -} - -function version_31($pdo) -{ - $pdo->exec("CREATE TABLE hourly_rates ( - id SERIAL PRIMARY KEY, - user_id INTEGER NOT NULL, - rate REAL DEFAULT 0, - date_effective INTEGER NOT NULL, - currency 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/sources/app/Schema/Sqlite.php b/sources/app/Schema/Sqlite.php index b4e4b94..633c90a 100644 --- a/sources/app/Schema/Sqlite.php +++ b/sources/app/Schema/Sqlite.php @@ -6,7 +6,71 @@ use Core\Security; use PDO; use Model\Link; -const VERSION = 81; +const VERSION = 85; + +function version_85($pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_unread_notifications ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + event_data TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec(" + CREATE TABLE user_has_notification_types ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + notification_type TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('CREATE UNIQUE INDEX user_has_notification_types_user_idx ON user_has_notification_types(user_id, notification_type)'); + + // Migrate people who have notification enabled before + $rq = $pdo->prepare('SELECT id FROM users WHERE notifications_enabled=1'); + $rq->execute(); + $user_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + foreach ($user_ids as $user_id) { + $rq = $pdo->prepare('INSERT INTO user_has_notification_types (user_id, notification_type) VALUES (?, ?)'); + $rq->execute(array($user_id, 'email')); + } +} + +function version_84($pdo) +{ + $pdo->exec(" + CREATE TABLE custom_filters ( + id INTEGER PRIMARY KEY, + filter TEXT NOT NULL, + project_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + name TEXT NOT NULL, + is_shared INTEGER DEFAULT 0 + ) + "); +} + +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) { @@ -123,7 +187,6 @@ function version_68($pdo) $result = $rq->fetch(PDO::FETCH_ASSOC); $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); - $rq->execute(array('calendar_user_subtasks_forecast', isset($result['subtask_forecast']) && $result['subtask_forecast'] == 1 ? 1 : 0)); $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); $rq->execute(array('calendar_user_tasks', 'date_started')); $rq->execute(array('calendar_project_tasks', 'date_started')); @@ -277,72 +340,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 ( - "id" INTEGER PRIMARY KEY, - "user_id" INTEGER NOT NULL, - "start" TEXT NOT NULL, - "end" TEXT NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )'); - - $pdo->exec('CREATE TABLE timetable_week ( - "id" INTEGER PRIMARY KEY, - "user_id" INTEGER NOT NULL, - "day" INTEGER NOT NULL, - "start" TEXT NOT NULL, - "end" TEXT NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )'); - - $pdo->exec('CREATE TABLE timetable_off ( - "id" INTEGER PRIMARY KEY, - "user_id" INTEGER NOT NULL, - "date" TEXT NOT NULL, - "all_day" INTEGER DEFAULT 0, - "start" TEXT DEFAULT 0, - "end" TEXT DEFAULT 0, - "comment" TEXT, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )'); - - $pdo->exec('CREATE TABLE timetable_extra ( - "id" INTEGER PRIMARY KEY, - "user_id" INTEGER NOT NULL, - "date" TEXT NOT NULL, - "all_day" INTEGER DEFAULT 0, - "start" TEXT DEFAULT 0, - "end" TEXT DEFAULT 0, - "comment" TEXT, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )'); -} - -function version_49($pdo) -{ - $pdo->exec("CREATE TABLE hourly_rates ( - id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - rate REAL DEFAULT 0, - date_effective INTEGER NOT NULL, - currency TEXT NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE - )"); -} - function version_48($pdo) { $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); diff --git a/sources/app/ServiceProvider/ClassProvider.php b/sources/app/ServiceProvider/ClassProvider.php index ef7aa57..5d15774 100644 --- a/sources/app/ServiceProvider/ClassProvider.php +++ b/sources/app/ServiceProvider/ClassProvider.php @@ -2,13 +2,17 @@ namespace ServiceProvider; +use Core\Plugin\Loader; +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,18 +22,22 @@ class ClassProvider implements ServiceProviderInterface 'Action', 'Authentication', 'Board', - 'Budget', 'Category', 'Color', 'Comment', 'Config', 'Currency', + 'CustomFilter', 'DateParser', 'File', - 'HourlyRate', 'LastLogin', 'Link', 'Notification', + 'NotificationType', + 'NotificationFilter', + 'OverdueNotification', + 'WebNotification', + 'EmailNotification', 'Project', 'ProjectActivity', 'ProjectAnalytic', @@ -40,7 +48,6 @@ class ClassProvider implements ServiceProviderInterface 'ProjectPermission', 'Subtask', 'SubtaskExport', - 'SubtaskForecast', 'SubtaskTimeTracking', 'Swimlane', 'Task', @@ -56,27 +63,34 @@ class ClassProvider implements ServiceProviderInterface 'TaskPosition', 'TaskStatus', 'TaskValidator', - 'Timetable', - 'TimetableDay', - 'TimetableExtra', - 'TimetableWeek', - 'TimetableOff', 'Transition', 'User', 'UserSession', 'Webhook', ), + 'Formatter' => array( + 'TaskFilterGanttFormatter', + 'TaskFilterAutoCompleteFormatter', + 'TaskFilterCalendarFormatter', + 'TaskFilterICalendarFormatter', + 'ProjectGanttFormatter', + ), 'Core' => array( 'EmailClient', 'Helper', 'HttpClient', 'Lexer', - 'MemoryCache', 'Request', 'Router', 'Session', 'Template', ), + 'Core\Cache' => array( + 'MemoryCache', + ), + 'Core\Plugin' => array( + 'Hook', + ), 'Integration' => array( 'BitbucketWebhook', 'GithubWebhook', @@ -93,17 +107,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 +116,17 @@ class ClassProvider implements ServiceProviderInterface $container['oauth'] = $container->factory(function ($c) { return new OAuth2($c); }); + + $container['htmlConverter'] = function() { + return new HtmlConverter(array('strip_tags' => true)); + }; + + $container['objectStorage'] = function() { + return new FileStorage(FILES_DIR); + }; + + $container['pluginLoader'] = new Loader($container); + + $container['cspRules'] = array('style-src' => "'self' 'unsafe-inline'", 'img-src' => '* data:'); } } diff --git a/sources/app/ServiceProvider/LoggingProvider.php b/sources/app/ServiceProvider/LoggingProvider.php index dd79d65..16fa6ce 100644 --- a/sources/app/ServiceProvider/LoggingProvider.php +++ b/sources/app/ServiceProvider/LoggingProvider.php @@ -13,11 +13,13 @@ class LoggingProvider implements ServiceProviderInterface { public function register(Container $container) { - $syslog = new Syslog('kanboard'); - $syslog->setLevel(LogLevel::ERROR); - $logger = new Logger; - $logger->setLogger($syslog); + + if (ENABLE_SYSLOG) { + $syslog = new Syslog('kanboard'); + $syslog->setLevel(LogLevel::ERROR); + $logger->setLogger($syslog); + } if (DEBUG) { $logger->setLogger(new File(DEBUG_FILE)); diff --git a/sources/app/Subscriber/SubtaskTimeTrackingSubscriber.php b/sources/app/Subscriber/SubtaskTimeTrackingSubscriber.php index e45b2c9..2d3b5f9 100644 --- a/sources/app/Subscriber/SubtaskTimeTrackingSubscriber.php +++ b/sources/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/sources/app/Template/app/notifications.php b/sources/app/Template/app/notifications.php new file mode 100644 index 0000000..4f7dd35 --- /dev/null +++ b/sources/app/Template/app/notifications.php @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + +
+ text->contains($notification['event_name'], 'subtask')): ?> + + text->contains($notification['event_name'], 'task.move')): ?> + + text->contains($notification['event_name'], 'task.overdue')): ?> + + text->contains($notification['event_name'], 'task')): ?> + + text->contains($notification['event_name'], 'comment')): ?> + + text->contains($notification['event_name'], 'file')): ?> + + + + text->contains($notification['event_name'], 'comment')): ?> + url->link($notification['title'], 'task', 'show', array('task_id' => $notification['event_data']['task']['id'], 'project_id' => $notification['event_data']['task']['project_id']), false, '', '', false, 'comment-'.$notification['event_data']['comment']['id']) ?> + text->contains($notification['event_name'], 'task.overdue')): ?> + 1): ?> + + + url->link($notification['title'], 'task', 'show', array('task_id' => $notification['event_data']['tasks'][0]['id'], 'project_id' => $notification['event_data']['tasks'][0]['project_id'])) ?> + + + url->link($notification['title'], 'task', 'show', array('task_id' => $notification['event_data']['task']['id'], 'project_id' => $notification['event_data']['task']['project_id'])) ?> + + + + + + url->link(t('Mark as read'), 'webnotification', 'remove', array('user_id' => $user['id'], 'notification_id' => $notification['id'])) ?> +
+ \ No newline at end of file diff --git a/sources/app/Template/app/projects.php b/sources/app/Template/app/projects.php index 43db85b..cf22707 100644 --- a/sources/app/Template/app/projects.php +++ b/sources/app/Template/app/projects.php @@ -6,8 +6,9 @@ - - + + + getCollection() as $project): ?> @@ -15,11 +16,17 @@ + + + + + diff --git a/sources/app/Template/board/table_container.php b/sources/app/Template/board/table_container.php index 98b40eb..b2d475a 100644 --- a/sources/app/Template/board/table_container.php +++ b/sources/app/Template/board/table_container.php @@ -1,31 +1,58 @@
- -
order('Id', 'id') ?>order(t('Project'), 'name') ?>order('Id', 'id') ?>order('', 'is_private') ?>order(t('Project'), 'name') ?>
url->link('#'.$project['id'], 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link') ?> + + + + user->isProjectManagementAllowed($project['id'])): ?> - url->link('', 'project', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Settings')) ?>  + url->link('', 'gantt', 'project', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Gantt chart')) ?> + url->link('', 'listing', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('List')) ?>  url->link('', 'calendar', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Calendar')) ?>  url->link($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?> diff --git a/sources/app/Template/app/sidebar.php b/sources/app/Template/app/sidebar.php index 2d96600..552a9c2 100644 --- a/sources/app/Template/app/sidebar.php +++ b/sources/app/Template/app/sidebar.php @@ -19,6 +19,10 @@
  • app->getRouterAction() === 'activity' ? 'class="active"' : '' ?>> url->link(t('My activity stream'), 'app', 'activity', array('user_id' => $user['id'])) ?>
  • +
  • app->getRouterAction() === 'notifications' ? 'class="active"' : '' ?>> + url->link(t('My notifications'), 'app', 'notifications', array('user_id' => $user['id'])) ?> +
  • + hook->render('template:dashboard:sidebar') ?> diff --git a/sources/app/Template/board/table_column.php b/sources/app/Template/board/table_column.php new file mode 100644 index 0000000..2a6b496 --- /dev/null +++ b/sources/app/Template/board/table_column.php @@ -0,0 +1,56 @@ + +
    + + +
    + + + +
    + + +
    + +
    + url->link('+', 'taskcreation', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'popover', t('Add a new task')) ?> +
    + + + 1 && ! empty($column['nb_column_tasks'])): ?> + + () + + + + + e($column['title']) ?> + + + + '> + + + + + + + + + + + + + (/e($column['task_limit']) ?>) + + + + () + + +
    + +
    + +

    -
    - - - -

    - + +
    - render('board/table_swimlane', array( - 'project' => $project, - 'swimlane' => $swimlane, - 'board_highlight_period' => $board_highlight_period, - 'hide_swimlane' => count($swimlanes) === 1, - 'not_editable' => isset($not_editable), - )) ?> +
    - -
    + + $swimlane): ?> + + + + 0 && $swimlane['nb_swimlanes'] > 1): ?> + render('board/table_swimlane', array( + 'project' => $project, + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + )) ?> + + + render('board/table_column', array( + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + )) ?> + + 1): ?> + render('board/table_swimlane', array( + 'project' => $project, + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + )) ?> + + + render('board/table_tasks', array( + 'project' => $project, + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + 'board_highlight_period' => $board_highlight_period, + )) ?> + + + + + + + \ No newline at end of file diff --git a/sources/app/Template/board/table_swimlane.php b/sources/app/Template/board/table_swimlane.php index be40163..dd38fc9 100644 --- a/sources/app/Template/board/table_swimlane.php +++ b/sources/app/Template/board/table_swimlane.php @@ -1,94 +1,26 @@ + - - - - - - - - - e($swimlane['name']) ?> - - - + + + + + + + - - - -
    - - + e($swimlane['name']) ?> + + + + -
    -
    - -
    - url->link('+', 'taskcreation', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'popover', t('Add a new task')) ?> -
    - + - - e($column['title']) ?> - - - - '> - - - - - - -   - - - - - - (/e($column['task_limit']) ?>) - - - - () - - -
    + + () + - - - - - - - e($swimlane['name']) ?> - -
    - () -
    - - - - - - -
    - - render($not_editable ? 'board/task_public' : 'board/task_private', array( - 'project' => $project, - 'task' => $task, - 'board_highlight_period' => $board_highlight_period, - 'not_editable' => $not_editable, - )) ?> - -
    -
    -
    -
    - e($column['title']) ?> -
    -
    -
    - - - \ No newline at end of file diff --git a/sources/app/Template/board/table_tasks.php b/sources/app/Template/board/table_tasks.php new file mode 100644 index 0000000..f10d48e --- /dev/null +++ b/sources/app/Template/board/table_tasks.php @@ -0,0 +1,31 @@ + + + + + + +
    + + render($not_editable ? 'board/task_public' : 'board/task_private', array( + 'project' => $project, + 'task' => $task, + 'board_highlight_period' => $board_highlight_period, + 'not_editable' => $not_editable, + )) ?> + +
    + + +
    +
    +
    + e($column['title']) ?> +
    +
    +
    + + + diff --git a/sources/app/Template/board/task_menu.php b/sources/app/Template/board/task_menu.php index 7cda510..3eb3570 100644 --- a/sources/app/Template/board/task_menu.php +++ b/sources/app/Template/board/task_menu.php @@ -1,13 +1,17 @@ \ No newline at end of file diff --git a/sources/app/Template/board/task_private.php b/sources/app/Template/board/task_private.php index a6c8a9e..da993fd 100644 --- a/sources/app/Template/board/task_private.php +++ b/sources/app/Template/board/task_private.php @@ -9,10 +9,11 @@ data-task-url="url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"> - render('board/task_menu', array('task' => $task)) ?> - board->isCollapsed($project['id'])): ?> + board->isCollapsed($task['project_id'])): ?>
    + render('board/task_menu', array('task' => $task)) ?> + e($this->user->getInitials($task['assignee_name'] ?: $task['assignee_username'])) ?> @@ -22,6 +23,7 @@
    + render('board/task_menu', array('task' => $task)) ?> @@ -29,9 +31,10 @@ + url->link( - (! empty($task['owner_id']) ? ($task['assignee_name'] ?: $task['assignee_username']) : t('Nobody assigned')), + $task['assignee_name'] ?: $task['assignee_username'], 'board', 'changeAssignee', array('task_id' => $task['id'], 'project_id' => $task['project_id']), @@ -40,6 +43,7 @@ t('Change assignee') ) ?> +
    diff --git a/sources/app/Template/board/tooltip_tasklinks.php b/sources/app/Template/board/tooltip_tasklinks.php index 25aa91a..5991fec 100644 --- a/sources/app/Template/board/tooltip_tasklinks.php +++ b/sources/app/Template/board/tooltip_tasklinks.php @@ -3,6 +3,7 @@
  • + [] url->link( $this->e('#'.$link['task_id'].' - '.$link['title']), 'task', 'show', array('task_id' => $link['task_id'], 'project_id' => $link['project_id']), diff --git a/sources/app/Template/board/private_view.php b/sources/app/Template/board/view_private.php similarity index 90% rename from sources/app/Template/board/private_view.php rename to sources/app/Template/board/view_private.php index d4c2c65..63d261f 100644 --- a/sources/app/Template/board/private_view.php +++ b/sources/app/Template/board/view_private.php @@ -5,6 +5,7 @@ 'filters' => $filters, 'categories_list' => $categories_list, 'users_list' => $users_list, + 'custom_filters_list' => $custom_filters_list, 'is_board' => true, )) ?> diff --git a/sources/app/Template/board/public_view.php b/sources/app/Template/board/view_public.php similarity index 100% rename from sources/app/Template/board/public_view.php rename to sources/app/Template/board/view_public.php diff --git a/sources/app/Template/budget/breakdown.php b/sources/app/Template/budget/breakdown.php deleted file mode 100644 index 9256118..0000000 --- a/sources/app/Template/budget/breakdown.php +++ /dev/null @@ -1,30 +0,0 @@ - - -isEmpty()): ?> -

    - - - - - - - - - - - getCollection() as $record): ?> - - - - - - - - - -
    order(t('Task'), 'task_title') ?>order(t('Subtask'), 'subtask_title') ?>order(t('User'), 'username') ?>order(t('Time spent'), \Model\SubtaskTimeTracking::TABLE.'.time_spent') ?>order(t('Date'), 'start') ?>
    url->link($this->e($record['task_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?>url->link($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?>url->link($this->e($record['name'] ?: $record['username']), 'user', 'show', array('user_id' => $record['user_id'])) ?>
    - - - \ No newline at end of file diff --git a/sources/app/Template/budget/create.php b/sources/app/Template/budget/create.php deleted file mode 100644 index a563796..0000000 --- a/sources/app/Template/budget/create.php +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - -
    e($line['comment']) ?> - url->link(t('Remove'), 'budget', 'confirm', array('project_id' => $project['id'], 'budget_id' => $line['id'])) ?> -
    - -

    - - -
    - - form->csrf() ?> - - form->hidden('id', $values) ?> - form->hidden('project_id', $values) ?> - - form->label(t('Amount'), 'amount') ?> - form->text('amount', $values, $errors, array('required'), 'form-numeric') ?> - - form->label(t('Date'), 'date') ?> - form->text('date', $values, $errors, array('required'), 'form-date') ?> - - form->label(t('Comment'), 'comment') ?> - form->text('comment', $values, $errors) ?> - -
    - -
    -
    \ No newline at end of file diff --git a/sources/app/Template/budget/index.php b/sources/app/Template/budget/index.php deleted file mode 100644 index 51ef3d8..0000000 --- a/sources/app/Template/budget/index.php +++ /dev/null @@ -1,34 +0,0 @@ - - - -
    -
    t('Budget line'), 'out' => t('Expenses'), 'left' => t('Remaining'), 'value' => t('Amount'), 'date' => t('Date'), 'type' => t('Type')), JSON_HEX_APOS) ?>'>
    -
    -
    - - - - - - - - - - - -
    - - - -
    - -

    - - -asset->js('assets/js/vendor/d3.v3.min.js') ?> -asset->js('assets/js/vendor/c3.min.js') ?> \ No newline at end of file diff --git a/sources/app/Template/budget/remove.php b/sources/app/Template/budget/remove.php deleted file mode 100644 index a5b906a..0000000 --- a/sources/app/Template/budget/remove.php +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -

    - -
    - url->link(t('Yes'), 'budget', 'remove', array('project_id' => $project['id'], 'budget_id' => $budget_id), true, 'btn btn-red') ?> - - url->link(t('cancel'), 'budget', 'create', array('project_id' => $project['id'])) ?> -
    -
    \ No newline at end of file diff --git a/sources/app/Template/budget/sidebar.php b/sources/app/Template/budget/sidebar.php deleted file mode 100644 index 8477c05..0000000 --- a/sources/app/Template/budget/sidebar.php +++ /dev/null @@ -1,16 +0,0 @@ - \ No newline at end of file diff --git a/sources/app/Template/config/calendar.php b/sources/app/Template/config/calendar.php index 1cc985c..f525013 100644 --- a/sources/app/Template/config/calendar.php +++ b/sources/app/Template/config/calendar.php @@ -6,24 +6,25 @@ form->csrf() ?> -

    +

    form->radios('calendar_project_tasks', array( 'date_creation' => t('Show tasks based on the creation date'), 'date_started' => t('Show tasks based on the start date'), ), $values) ?>
    -

    +

    form->radios('calendar_user_tasks', array( 'date_creation' => t('Show tasks based on the creation date'), 'date_started' => t('Show tasks based on the start date'), ), $values) ?> +
    -

    +
    +

    form->checkbox('calendar_user_subtasks_time_tracking', t('Show subtasks based on the time tracking'), 1, $values['calendar_user_subtasks_time_tracking'] == 1) ?> - form->checkbox('calendar_user_subtasks_forecast', t('Show subtask estimates (forecast of future work)'), 1, $values['calendar_user_subtasks_forecast'] == 1) ?>
    diff --git a/sources/app/Template/config/plugins.php b/sources/app/Template/config/plugins.php new file mode 100644 index 0000000..fea48d5 --- /dev/null +++ b/sources/app/Template/config/plugins.php @@ -0,0 +1,30 @@ + + + +

    + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sources/app/Template/config/sidebar.php b/sources/app/Template/config/sidebar.php index 3617979..4195cde 100644 --- a/sources/app/Template/config/sidebar.php +++ b/sources/app/Template/config/sidebar.php @@ -4,6 +4,9 @@
  • app->getRouterAction() === 'index' ? 'class="active"' : '' ?>> url->link(t('About'), 'config', 'index') ?>
  • +
  • app->getRouterAction() === 'plugins' ? 'class="active"' : '' ?>> + url->link(t('Plugins'), 'config', 'plugins') ?> +
  • app->getRouterAction() === 'application' ? 'class="active"' : '' ?>> url->link(t('Application settings'), 'config', 'application') ?>
  • @@ -34,6 +37,7 @@
  • url->link(t('Documentation'), 'doc', 'show') ?>
  • + hook->render('template:config:sidebar') ?> diff --git a/sources/app/Template/currency/index.php b/sources/app/Template/currency/index.php index f72c570..1c78c47 100644 --- a/sources/app/Template/currency/index.php +++ b/sources/app/Template/currency/index.php @@ -52,5 +52,3 @@ - -

    diff --git a/sources/app/Template/custom_filter/add.php b/sources/app/Template/custom_filter/add.php new file mode 100644 index 0000000..d4e102b --- /dev/null +++ b/sources/app/Template/custom_filter/add.php @@ -0,0 +1,22 @@ + + + + form->csrf() ?> + form->hidden('project_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('required', 'maxlength="100"')) ?> + + form->label(t('Filter'), 'filter') ?> + form->text('filter', $values, $errors, array('required', 'maxlength="100"')) ?> + + user->isProjectManagementAllowed($project['id'])): ?> + form->checkbox('is_shared', t('Share with all project members'), 1) ?> + + +
    + +
    + \ No newline at end of file diff --git a/sources/app/Template/custom_filter/edit.php b/sources/app/Template/custom_filter/edit.php new file mode 100644 index 0000000..7525574 --- /dev/null +++ b/sources/app/Template/custom_filter/edit.php @@ -0,0 +1,30 @@ + + + + + form->csrf() ?> + + form->hidden('id', $values) ?> + form->hidden('user_id', $values) ?> + form->hidden('project_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="100"')) ?> + + form->label(t('Filter'), 'filter') ?> + form->text('filter', $values, $errors, array('required', 'maxlength="100"')) ?> + + user->isProjectManagementAllowed($project['id'])): ?> + form->checkbox('is_shared', t('Share with all project members'), 1, $values['is_shared'] == 1) ?> + + form->hidden('is_shared', $values) ?> + + +
    + + + url->link(t('cancel'), 'customfilter', 'index', array('project_id' => $project['id'])) ?> +
    + \ No newline at end of file diff --git a/sources/app/Template/custom_filter/index.php b/sources/app/Template/custom_filter/index.php new file mode 100644 index 0000000..a53d0d0 --- /dev/null +++ b/sources/app/Template/custom_filter/index.php @@ -0,0 +1,40 @@ + + +
    +
    + getPluginHomepage()): ?> + e($plugin->getPluginName()) ?> + + e($plugin->getPluginName()) ?> + + e($plugin->getPluginAuthor()) ?>e($plugin->getPluginVersion()) ?>e($plugin->getPluginDescription()) ?>
    + + + + + + + + + + + + + + + + +
    e($filter['name']) ?>e($filter['filter']) ?> + + + + + + e($filter['owner_name'] ?: $filter['owner_username']) ?> + user->getId() || $this->user->isProjectManagementAllowed($project['id'])): ?> +
      +
    • url->link(t('Remove'), 'customfilter', 'remove', array('project_id' => $filter['project_id'], 'filter_id' => $filter['id']), true) ?>
    • +
    • url->link(t('Edit'), 'customfilter', 'edit', array('project_id' => $filter['project_id'], 'filter_id' => $filter['id'])) ?>
    • +
    + +
    +
    + + +render('custom_filter/add', array('project' => $project, 'values' => $values, 'errors' => $errors)) ?> \ No newline at end of file diff --git a/sources/app/Template/export/sidebar.php b/sources/app/Template/export/sidebar.php index f204d29..4444852 100644 --- a/sources/app/Template/export/sidebar.php +++ b/sources/app/Template/export/sidebar.php @@ -13,6 +13,7 @@
  • app->getRouterAction() === 'summary' ? 'class="active"' : '' ?>> url->link(t('Daily project summary'), 'export', 'summary', array('project_id' => $project['id'])) ?>
  • + hook->render('template:export:sidebar') ?> diff --git a/sources/app/Template/file/show.php b/sources/app/Template/file/show.php index b1a0a81..a390c9f 100644 --- a/sources/app/Template/file/show.php +++ b/sources/app/Template/file/show.php @@ -11,7 +11,7 @@
  • - <?= $this->e($file['name']) ?> + <?= $this->e($file['name']) ?>

    diff --git a/sources/app/Template/gantt/task_creation.php b/sources/app/Template/gantt/task_creation.php index d0d14c1..7997e23 100644 --- a/sources/app/Template/gantt/task_creation.php +++ b/sources/app/Template/gantt/task_creation.php @@ -12,7 +12,6 @@ form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"'), 'form-input-large') ?> form->label(t('Description'), 'description') ?> -

    form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"')) ?> @@ -29,6 +28,8 @@
  • + + render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?>
    @@ -43,17 +44,14 @@ form->select('swimlane_id', $swimlanes_list, $values, $errors, array('tabindex="5"')) ?>
    - form->label(t('Color'), 'color_id') ?> - form->select('color_id', $colors_list, $values, $errors, array('tabindex="7"')) ?>
    - form->label(t('Complexity'), 'score') ?> - form->number('score', $values, $errors, array('tabindex="8"')) ?>
    + form->number('score', $values, $errors, array('tabindex="6"')) ?>
    form->label(t('Start Date'), 'date_started') ?> - form->text('date_started', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="9"'), 'form-date') ?> + form->text('date_started', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="7"'), 'form-date') ?> form->label(t('Due Date'), 'date_due') ?> - form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="10"'), 'form-date') ?>
    + form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="8"'), 'form-date') ?>
    diff --git a/sources/app/Template/header.php b/sources/app/Template/header.php new file mode 100644 index 0000000..19ffbbf --- /dev/null +++ b/sources/app/Template/header.php @@ -0,0 +1,37 @@ +
    + +
    \ No newline at end of file diff --git a/sources/app/Template/hourlyrate/index.php b/sources/app/Template/hourlyrate/index.php deleted file mode 100644 index af305d0..0000000 --- a/sources/app/Template/hourlyrate/index.php +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - -
    - url->link(t('Remove'), 'hourlyrate', 'confirm', array('user_id' => $user['id'], 'rate_id' => $rate['id'])) ?> -
    - -

    - - -
    - - form->hidden('user_id', $values) ?> - form->csrf() ?> - - form->label(t('Hourly rate'), 'rate') ?> - form->text('rate', $values, $errors, array('required'), 'form-numeric') ?> - - form->label(t('Currency'), 'currency') ?> - form->select('currency', $currencies_list, $values, $errors, array('required')) ?> - - form->label(t('Effective date'), 'date_effective') ?> - form->text('date_effective', $values, $errors, array('required'), 'form-date') ?> - -
    - -
    -
    diff --git a/sources/app/Template/hourlyrate/remove.php b/sources/app/Template/hourlyrate/remove.php deleted file mode 100644 index 121436e..0000000 --- a/sources/app/Template/hourlyrate/remove.php +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -

    - -
    - url->link(t('Yes'), 'hourlyrate', 'remove', array('user_id' => $user['id'], 'rate_id' => $rate_id), true, 'btn btn-red') ?> - - url->link(t('cancel'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?> -
    -
    \ No newline at end of file diff --git a/sources/app/Template/layout.php b/sources/app/Template/layout.php index 3e883fb..2058295 100644 --- a/sources/app/Template/layout.php +++ b/sources/app/Template/layout.php @@ -21,6 +21,9 @@ asset->css('assets/css/print.css', true, 'print') ?> asset->customCss() ?> + hook->asset('css', 'template:layout:css') ?> + hook->asset('js', 'template:layout:js') ?> + @@ -28,52 +31,29 @@ <?= isset($title) ? $this->e($title) : 'Kanboard' ?> + + hook->render('template:layout:head') ?> -
    - -
    + hook->render('template:layout:top') ?> + render('header', array( + 'title' => $title, + 'description' => isset($description) ? $description : '', + 'board_selector' => isset($board_selector) ? $board_selector : array(), + )) ?>
    app->flashMessage() ?>
    + hook->render('template:layout:bottom') ?> diff --git a/sources/app/Template/project/dropdown.php b/sources/app/Template/project/dropdown.php index 0a53cc0..1eb87b0 100644 --- a/sources/app/Template/project/dropdown.php +++ b/sources/app/Template/project/dropdown.php @@ -1,29 +1,31 @@
  • - +   url->link(t('Activity'), 'activity', 'project', array('project_id' => $project['id'])) ?>
  • +
  • +   + url->link(t('Custom filters'), 'customfilter', 'index', array('project_id' => $project['id'])) ?> +
  • - url->link(t('Public link'), 'board', 'readonly', array('token' => $project['token']), false, '', '', true) ?> +  url->link(t('Public link'), 'board', 'readonly', array('token' => $project['token']), false, '', '', true) ?>
  • +hook->render('template:project:dropdown', array('project' => $project)) ?> + user->isProjectManagementAllowed($project['id'])): ?> -
  • - - url->link(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?> -
  • -
  • - - url->link(t('Budget'), 'budget', 'index', array('project_id' => $project['id'])) ?> -
  • -
  • - - url->link(t('Exports'), 'export', 'tasks', array('project_id' => $project['id'])) ?> -
  • -
  • - - url->link(t('Settings'), 'project', 'show', array('project_id' => $project['id'])) ?> -
  • +
  • +   + url->link(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?> +
  • +
  • +   + url->link(t('Exports'), 'export', 'tasks', array('project_id' => $project['id'])) ?> +
  • +
  • +   + url->link(t('Settings'), 'project', 'show', array('project_id' => $project['id'])) ?> +
  • diff --git a/sources/app/Template/project/filters.php b/sources/app/Template/project/filters.php index fa50b36..c22d7b4 100644 --- a/sources/app/Template/project/filters.php +++ b/sources/app/Template/project/filters.php @@ -21,6 +21,14 @@ "> +
  • + + + + +
  • render('project/dropdown', array('project' => $project)) ?> @@ -55,14 +63,24 @@
    render('app/filters_helper', array('reset' => 'status:open')) ?> + + + + @@ -72,10 +90,9 @@ diff --git a/sources/app/Template/project/index.php b/sources/app/Template/project/index.php index 5ca6e6b..4b62a27 100644 --- a/sources/app/Template/project/index.php +++ b/sources/app/Template/project/index.php @@ -11,91 +11,89 @@
    -
    - isEmpty()): ?> -

    - - - - - - - - - user->isAdmin() || $this->user->isProjectAdmin()): ?> - - + isEmpty()): ?> +

    + +
    order(t('Id'), 'id') ?>order(t('Status'), 'is_active') ?>order(t('Project'), 'name') ?>order(t('Start date'), 'start_date') ?>order(t('End date'), 'end_date') ?>
    + + + + + + + user->isAdmin() || $this->user->isProjectAdmin()): ?> + + + + + + getCollection() as $project): ?> + + + - - getCollection() as $project): ?> - - - - + - - - user->isAdmin() || $this->user->isProjectAdmin()): ?> - + + + user->isAdmin() || $this->user->isProjectAdmin()): ?> + + - - - - -
    order(t('Id'), 'id') ?>order(t('Status'), 'is_active') ?>order(t('Project'), 'name') ?>order(t('Start date'), 'start_date') ?>order(t('End date'), 'end_date') ?>
    + url->link('#'.$project['id'], 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link') ?> + + + + + -
    - url->link('#'.$project['id'], 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link') ?> - - - - - - - - url->link('', 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Board')) ?> - url->link('', 'gantt', 'project', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Gantt chart')) ?> + + url->link('', 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Board')) ?> + url->link('', 'gantt', 'project', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Gantt chart')) ?> - - - - - - + + + + + + - - '> - - - + + '> + + + - url->link($this->e($project['name']), 'project', 'show', array('project_id' => $project['id'])) ?> - - - - - + url->link($this->e($project['name']), 'project', 'show', array('project_id' => $project['id'])) ?> + + + + + +
      + $user_name): ?> +
    • url->link($this->e($user_name), 'projectuser', 'opens', array('user_id' => $user_id)) ?>
    • + +
    +
    + + +
      - $user_name): ?> + $user_name): ?>
    • url->link($this->e($user_name), 'projectuser', 'opens', array('user_id' => $user_id)) ?>
    -
    - - - -
      - $user_name): ?> -
    • url->link($this->e($user_name), 'projectuser', 'opens', array('user_id' => $user_id)) ?>
    • - -
    - -
    - - - e($column['title']) ?> - -
    + + + + + + e($column['title']) ?> + + + + + - - -
    + + diff --git a/sources/app/Template/project/sidebar.php b/sources/app/Template/project/sidebar.php index 7b5d976..d8b35e3 100644 --- a/sources/app/Template/project/sidebar.php +++ b/sources/app/Template/project/sidebar.php @@ -4,6 +4,9 @@
  • app->getRouterAction() === 'show' ? 'class="active"' : '' ?>> url->link(t('Summary'), 'project', 'show', array('project_id' => $project['id'])) ?>
  • +
  • app->getRouterController() === 'customfilter' && $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>> + url->link(t('Custom filters'), 'customfilter', 'index', array('project_id' => $project['id'])) ?> +
  • user->isProjectManagementAllowed($project['id'])): ?>
  • app->getRouterController() === 'project' && $this->app->getRouterAction() === 'share' ? 'class="active"' : '' ?>> @@ -48,6 +51,8 @@
  • + + hook->render('template:project:sidebar') ?> diff --git a/sources/app/Template/project_user/sidebar.php b/sources/app/Template/project_user/sidebar.php index 8cc3f41..b81ba14 100644 --- a/sources/app/Template/project_user/sidebar.php +++ b/sources/app/Template/project_user/sidebar.php @@ -24,5 +24,7 @@
  • app->getRouterAction() === 'closed' ? 'class="active"' : '' ?>> url->link(t('Closed tasks'), 'projectuser', 'closed', $filter) ?>
  • + + hook->render('template:project-user:sidebar') ?> \ No newline at end of file diff --git a/sources/app/Template/swimlane/edit.php b/sources/app/Template/swimlane/edit.php index 1788fed..dfc5cf0 100644 --- a/sources/app/Template/swimlane/edit.php +++ b/sources/app/Template/swimlane/edit.php @@ -12,6 +12,27 @@ form->label(t('Name'), 'name') ?> form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + form->label(t('Description'), 'description') ?> + +
    + +
    + form->textarea('description', $values, $errors) ?> +
    +
    +
    +
    + +
    +
    url->doc(t('Write your text in Markdown'), 'syntax-guide') ?>
    +
    diff --git a/sources/app/Template/swimlane/index.php b/sources/app/Template/swimlane/index.php index daee6af..95b462d 100644 --- a/sources/app/Template/swimlane/index.php +++ b/sources/app/Template/swimlane/index.php @@ -14,7 +14,27 @@ form->hidden('project_id', $values) ?> form->label(t('Name'), 'name') ?> - form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + form->text('name', $values, $errors, array('required', 'maxlength="50"')) ?> + + form->label(t('Description'), 'description') ?> + +
    +
    + form->textarea('description', $values, $errors) ?> +
    +
    +
    +
    + +
    +
    url->doc(t('Write your text in Markdown'), 'syntax-guide') ?>
    @@ -30,9 +50,11 @@ form->hidden('id', $default_swimlane) ?> form->label(t('Rename'), 'default_swimlane') ?> - form->text('default_swimlane', $default_swimlane, array(), array('autofocus', 'required', 'maxlength="50"')) ?>
    + form->text('default_swimlane', $default_swimlane, array(), array('required', 'maxlength="50"')) ?>
    - form->checkbox('show_default_swimlane', t('Show default swimlane'), 1, isset($default_swimlane['show_default_swimlane']) && $default_swimlane['show_default_swimlane'] == 1) ?> + + form->checkbox('show_default_swimlane', t('Show default swimlane'), 1, isset($default_swimlane['show_default_swimlane']) && $default_swimlane['show_default_swimlane'] == 1) ?> +
    diff --git a/sources/app/Template/swimlane/table.php b/sources/app/Template/swimlane/table.php index f38572a..b708e63 100644 --- a/sources/app/Template/swimlane/table.php +++ b/sources/app/Template/swimlane/table.php @@ -25,7 +25,7 @@
  • - url->link(t('Rename'), 'swimlane', 'edit', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id'])) ?> + url->link(t('Edit'), 'swimlane', 'edit', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id'])) ?>
  • diff --git a/sources/app/Template/task/color_picker.php b/sources/app/Template/task/color_picker.php new file mode 100644 index 0000000..a849b9c --- /dev/null +++ b/sources/app/Template/task/color_picker.php @@ -0,0 +1,11 @@ +
    + $color_name): ?> +
    +
    + +
    + +form->hidden('color_id', $values) ?> \ No newline at end of file diff --git a/sources/app/Template/task/sidebar.php b/sources/app/Template/task/sidebar.php index 1f06ab8..9ee1e7d 100644 --- a/sources/app/Template/task/sidebar.php +++ b/sources/app/Template/task/sidebar.php @@ -18,6 +18,8 @@ url->link(t('Time tracking'), 'task', 'timetracking', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
  • + + hook->render('template:task:sidebar:information') ?>

      @@ -66,6 +68,8 @@ url->link(t('Remove'), 'task', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + + hook->render('template:task:sidebar:actions') ?>
    diff --git a/sources/app/Template/task_creation/form.php b/sources/app/Template/task_creation/form.php index 8a29896..325ca1c 100644 --- a/sources/app/Template/task_creation/form.php +++ b/sources/app/Template/task_creation/form.php @@ -10,8 +10,7 @@
    -
    -
    + form->csrf() ?> @@ -38,7 +37,7 @@
    -
    url->doc(t('Write your text in Markdown'), 'syntax-guide') ?>
    + render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?> form->checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?> @@ -62,9 +61,6 @@ form->label(t('Column'), 'column_id') ?> form->select('column_id', $columns_list, $values, $errors, array('tabindex="6"')) ?>
    - form->label(t('Color'), 'color_id') ?> - form->select('color_id', $colors_list, $values, $errors, array('tabindex="7"')) ?>
    - form->label(t('Complexity'), 'score') ?> form->number('score', $values, $errors, array('tabindex="8"')) ?>
    @@ -80,5 +76,4 @@ url->link(t('cancel'), 'board', 'show', array('project_id' => $values['project_id']), false, 'close-popover') ?>
    - - + \ No newline at end of file diff --git a/sources/app/Template/task_modification/edit_task.php b/sources/app/Template/task_modification/edit_task.php index fe4696d..f4d7449 100644 --- a/sources/app/Template/task_modification/edit_task.php +++ b/sources/app/Template/task_modification/edit_task.php @@ -1,8 +1,7 @@ -
    -
    + form->csrf() ?> @@ -12,7 +11,6 @@ form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"')) ?>
    form->label(t('Description'), 'description') ?> -
    form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"')) ?> @@ -30,6 +28,7 @@
    + render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?>
    @@ -42,9 +41,6 @@ form->label(t('Category'), 'category_id') ?> form->select('category_id', $categories_list, $values, $errors, array('tabindex="4"')) ?>
    - form->label(t('Color'), 'color_id') ?> - form->select('color_id', $colors_list, $values, $errors, array('tabindex="5"')) ?>
    - form->label(t('Complexity'), 'score') ?> form->number('score', $values, $errors, array('tabindex="6"')) ?>
    @@ -62,5 +58,4 @@ url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
    -
    -
    + \ No newline at end of file diff --git a/sources/app/Template/task_status/close.php b/sources/app/Template/task_status/close.php index 4de3dcb..d32863b 100644 --- a/sources/app/Template/task_status/close.php +++ b/sources/app/Template/task_status/close.php @@ -4,7 +4,7 @@

    - e($task['title'])) ?> +

    diff --git a/sources/app/Template/task_status/open.php b/sources/app/Template/task_status/open.php index 0043fda..615b246 100644 --- a/sources/app/Template/task_status/open.php +++ b/sources/app/Template/task_status/open.php @@ -4,12 +4,12 @@

    - e($task['title'])) ?> +

    - url->link(t('Yes'), 'taskstatus', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes'), true, 'btn btn-red') ?> + url->link(t('Yes'), 'taskstatus', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes', 'redirect' => $redirect), true, 'btn btn-red') ?> - url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?>
    \ No newline at end of file diff --git a/sources/app/Template/tasklink/show.php b/sources/app/Template/tasklink/show.php index 7125b11..97a3a76 100644 --- a/sources/app/Template/tasklink/show.php +++ b/sources/app/Template/tasklink/show.php @@ -6,6 +6,7 @@ + @@ -52,6 +53,7 @@ e($link['task_time_estimated']).'h' ?> + e($link['project_name']) ?> e($link['column_title']) ?> diff --git a/sources/app/Template/timetable/index.php b/sources/app/Template/timetable/index.php deleted file mode 100644 index 7a63a2e..0000000 --- a/sources/app/Template/timetable/index.php +++ /dev/null @@ -1,44 +0,0 @@ - - -
    - - form->hidden('controller', $values) ?> - form->hidden('action', $values) ?> - form->hidden('user_id', $values) ?> - - form->label(t('From'), 'from') ?> - form->text('from', $values, array(), array(), 'form-date') ?> - - form->label(t('To'), 'to') ?> - form->text('to', $values, array(), array(), 'form-date') ?> - - -
    - - -
    -

    - - - - - - - - - - - - - -
    getTimestamp()) ?>getTimestamp()) ?>getTimestamp()) ?>
    - - \ No newline at end of file diff --git a/sources/app/Template/timetable_day/index.php b/sources/app/Template/timetable_day/index.php deleted file mode 100644 index 386ceec..0000000 --- a/sources/app/Template/timetable_day/index.php +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - -
    - url->link(t('Remove'), 'timetableday', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> -
    - -

    - - -
    - - form->hidden('user_id', $values) ?> - form->csrf() ?> - - form->label(t('Start time'), 'start') ?> - form->select('start', $this->dt->getDayHours(), $values, $errors) ?> - - form->label(t('End time'), 'end') ?> - form->select('end', $this->dt->getDayHours(), $values, $errors) ?> - -
    - -
    -
    - -

    - -

    \ No newline at end of file diff --git a/sources/app/Template/timetable_day/remove.php b/sources/app/Template/timetable_day/remove.php deleted file mode 100644 index 1b33b26..0000000 --- a/sources/app/Template/timetable_day/remove.php +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -

    - -
    - url->link(t('Yes'), 'timetableday', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> - - url->link(t('cancel'), 'timetableday', 'index', array('user_id' => $user['id'])) ?> -
    -
    \ No newline at end of file diff --git a/sources/app/Template/timetable_extra/index.php b/sources/app/Template/timetable_extra/index.php deleted file mode 100644 index e998233..0000000 --- a/sources/app/Template/timetable_extra/index.php +++ /dev/null @@ -1,56 +0,0 @@ - - -isEmpty()): ?> - - - - - - - - - - - getCollection() as $slot): ?> - - - - - - - - - -
    order(t('Day'), 'Day') ?>order(t('All day'), 'all_day') ?>order(t('Start time'), 'start') ?>order(t('End time'), 'end') ?>
    e($slot['comment']) ?> - url->link(t('Remove'), 'timetableextra', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> -
    - - - - - -
    - - form->hidden('user_id', $values) ?> - form->csrf() ?> - - form->label(t('Day'), 'date') ?> - form->text('date', $values, $errors, array('required'), 'form-date') ?> - - form->checkbox('all_day', t('All day'), 1) ?> - - form->label(t('Start time'), 'start') ?> - form->select('start', $this->dt->getDayHours(), $values, $errors) ?> - - form->label(t('End time'), 'end') ?> - form->select('end', $this->dt->getDayHours(), $values, $errors) ?> - - form->label(t('Comment'), 'comment') ?> - form->text('comment', $values, $errors) ?> - -
    - -
    -
    \ No newline at end of file diff --git a/sources/app/Template/timetable_extra/remove.php b/sources/app/Template/timetable_extra/remove.php deleted file mode 100644 index fc90743..0000000 --- a/sources/app/Template/timetable_extra/remove.php +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -

    - -
    - url->link(t('Yes'), 'timetableextra', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> - - url->link(t('cancel'), 'timetableextra', 'index', array('user_id' => $user['id'])) ?> -
    -
    \ No newline at end of file diff --git a/sources/app/Template/timetable_off/index.php b/sources/app/Template/timetable_off/index.php deleted file mode 100644 index 615c2b8..0000000 --- a/sources/app/Template/timetable_off/index.php +++ /dev/null @@ -1,56 +0,0 @@ - - -isEmpty()): ?> - - - - - - - - - - - getCollection() as $slot): ?> - - - - - - - - - -
    order(t('Day'), 'Day') ?>order(t('All day'), 'all_day') ?>order(t('Start time'), 'start') ?>order(t('End time'), 'end') ?>
    e($slot['comment']) ?> - url->link(t('Remove'), 'timetableoff', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> -
    - - - - - -
    - - form->hidden('user_id', $values) ?> - form->csrf() ?> - - form->label(t('Day'), 'date') ?> - form->text('date', $values, $errors, array('required'), 'form-date') ?> - - form->checkbox('all_day', t('All day'), 1) ?> - - form->label(t('Start time'), 'start') ?> - form->select('start', $this->dt->getDayHours(), $values, $errors) ?> - - form->label(t('End time'), 'end') ?> - form->select('end', $this->dt->getDayHours(), $values, $errors) ?> - - form->label(t('Comment'), 'comment') ?> - form->text('comment', $values, $errors) ?> - -
    - -
    -
    \ No newline at end of file diff --git a/sources/app/Template/timetable_off/remove.php b/sources/app/Template/timetable_off/remove.php deleted file mode 100644 index 621e191..0000000 --- a/sources/app/Template/timetable_off/remove.php +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -

    - -
    - url->link(t('Yes'), 'timetableoff', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> - - url->link(t('cancel'), 'timetableoff', 'index', array('user_id' => $user['id'])) ?> -
    -
    \ No newline at end of file diff --git a/sources/app/Template/timetable_week/index.php b/sources/app/Template/timetable_week/index.php deleted file mode 100644 index d58c6cf..0000000 --- a/sources/app/Template/timetable_week/index.php +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - -
    dt->getWeekDay($slot['day']) ?> - url->link(t('Remove'), 'timetableweek', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> -
    - -

    - - -
    - - form->hidden('user_id', $values) ?> - form->csrf() ?> - - form->label(t('Day'), 'day') ?> - form->select('day', $this->dt->getWeekDays(), $values, $errors) ?> - - form->label(t('Start time'), 'start') ?> - form->select('start', $this->dt->getDayHours(), $values, $errors) ?> - - form->label(t('End time'), 'end') ?> - form->select('end', $this->dt->getDayHours(), $values, $errors) ?> - -
    - -
    -
    \ No newline at end of file diff --git a/sources/app/Template/timetable_week/remove.php b/sources/app/Template/timetable_week/remove.php deleted file mode 100644 index f5a1019..0000000 --- a/sources/app/Template/timetable_week/remove.php +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -

    - -
    - url->link(t('Yes'), 'timetableweek', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> - - url->link(t('cancel'), 'timetableweek', 'index', array('user_id' => $user['id'])) ?> -
    -
    \ No newline at end of file diff --git a/sources/app/Template/user/create_local.php b/sources/app/Template/user/create_local.php index 3c8b43b..98c38f0 100644 --- a/sources/app/Template/user/create_local.php +++ b/sources/app/Template/user/create_local.php @@ -37,7 +37,7 @@ form->label(t('Language'), 'language') ?> form->select('language', $languages, $values, $errors) ?>
    - form->checkbox('notifications_enabled', t('Enable notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> + form->checkbox('notifications_enabled', t('Enable email notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> form->checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?> form->checkbox('is_project_admin', t('Project Administrator'), 1, isset($values['is_project_admin']) && $values['is_project_admin'] == 1 ? true : false) ?>
    diff --git a/sources/app/Template/user/create_remote.php b/sources/app/Template/user/create_remote.php index 1d04bc8..49d1548 100644 --- a/sources/app/Template/user/create_remote.php +++ b/sources/app/Template/user/create_remote.php @@ -21,13 +21,13 @@ form->email('email', $values, $errors) ?>
    form->label(t('Google Id'), 'google_id') ?> - form->password('google_id', $values, $errors) ?>
    + form->text('google_id', $values, $errors) ?>
    form->label(t('Github Id'), 'github_id') ?> - form->password('github_id', $values, $errors) ?>
    + form->text('github_id', $values, $errors) ?>
    form->label(t('Gitlab Id'), 'gitlab_id') ?> - form->password('gitlab_id', $values, $errors) ?>
    + form->text('gitlab_id', $values, $errors) ?>
    @@ -40,7 +40,7 @@ form->label(t('Language'), 'language') ?> form->select('language', $languages, $values, $errors) ?>
    - form->checkbox('notifications_enabled', t('Enable notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> + form->checkbox('notifications_enabled', t('Enable email notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> form->checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?> form->checkbox('is_project_admin', t('Project Administrator'), 1, isset($values['is_project_admin']) && $values['is_project_admin'] == 1 ? true : false) ?> form->checkbox('disable_login_form', t('Disallow login form'), 1, isset($values['disable_login_form']) && $values['disable_login_form'] == 1) ?> diff --git a/sources/app/Template/user/external.php b/sources/app/Template/user/external.php index 9ef0b4d..7a42f38 100644 --- a/sources/app/Template/user/external.php +++ b/sources/app/Template/user/external.php @@ -34,7 +34,7 @@

    - +

     

    diff --git a/sources/app/Template/user/notifications.php b/sources/app/Template/user/notifications.php index a425705..7223013 100644 --- a/sources/app/Template/user/notifications.php +++ b/sources/app/Template/user/notifications.php @@ -1,33 +1,24 @@

    form->csrf() ?> - form->checkbox('notifications_enabled', t('Enable email notifications'), '1', $notifications['notifications_enabled'] == 1) ?>
    + form->checkbox('notifications_enabled', t('Enable notifications'), '1', $notifications['notifications_enabled'] == 1) ?>

    - - - - form->radios('notifications_filter', array( - \Model\Notification::FILTER_NONE => t('All tasks'), - \Model\Notification::FILTER_ASSIGNEE => t('Only for tasks assigned to me'), - \Model\Notification::FILTER_CREATOR => t('Only for tasks created by me'), - \Model\Notification::FILTER_BOTH => t('Only for tasks created by me and assigned to me'), - ), $notifications) ?>
    +

    + form->checkboxes('notification_types', $types, $notifications) ?>
    +

    + form->radios('notifications_filter', $filters, $notifications) ?> +
    -



    - -
    - $project_name): ?> - form->checkbox('projects['.$project_id.']', $project_name, '1', isset($notifications['project_'.$project_id])) ?>
    - -
    +

    + form->checkboxes('notification_projects', $projects, $notifications) ?>
    diff --git a/sources/app/Template/user/sidebar.php b/sources/app/Template/user/sidebar.php index cd1c85c..ca1e062 100644 --- a/sources/app/Template/user/sidebar.php +++ b/sources/app/Template/user/sidebar.php @@ -20,6 +20,8 @@ url->link(t('Persistent connections'), 'user', 'sessions', array('user_id' => $user['id'])) ?> + + hook->render('template:user:sidebar:information') ?>

    @@ -49,7 +51,7 @@ url->link(t('Public access'), 'user', 'share', array('user_id' => $user['id'])) ?>
  • app->getRouterController() === 'user' && $this->app->getRouterAction() === 'notifications' ? 'class="active"' : '' ?>> - url->link(t('Email notifications'), 'user', 'notifications', array('user_id' => $user['id'])) ?> + url->link(t('Notifications'), 'user', 'notifications', array('user_id' => $user['id'])) ?>
  • app->getRouterController() === 'user' && $this->app->getRouterAction() === 'external' ? 'class="active"' : '' ?>> url->link(t('External accounts'), 'user', 'external', array('user_id' => $user['id'])) ?> @@ -60,14 +62,10 @@
  • app->getRouterController() === 'user' && $this->app->getRouterAction() === 'authentication' ? 'class="active"' : '' ?>> url->link(t('Edit Authentication'), 'user', 'authentication', array('user_id' => $user['id'])) ?>
  • -
  • app->getRouterController() === 'hourlyrate' ? 'class="active"' : '' ?>> - url->link(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?> -
  • -
  • app->getRouterController() === 'timetable' ? 'class="active"' : '' ?>> - url->link(t('Manage timetable'), 'timetable', 'index', array('user_id' => $user['id'])) ?> -
  • + hook->render('template:user:sidebar:actions', array('user' => $user)) ?> + user->isAdmin() && ! $this->user->isCurrentUser($user['id'])): ?>
  • app->getRouterController() === 'user' && $this->app->getRouterAction() === 'remove' ? 'class="active"' : '' ?>> url->link(t('Remove'), 'user', 'remove', array('user_id' => $user['id'])) ?> diff --git a/sources/app/check_setup.php b/sources/app/check_setup.php index 624b6b3..65f291e 100644 --- a/sources/app/check_setup.php +++ b/sources/app/check_setup.php @@ -29,24 +29,7 @@ if (! extension_loaded('mbstring')) { die('PHP extension required: mbstring'); } -// Check if /data is writeable -if (! is_writable('data')) { - die('The directory "data" must be writeable by your web server user'); -} - // Fix wrong value for arg_separator.output, used by the function http_build_query() if (ini_get('arg_separator.output') === '&') { ini_set('arg_separator.output', '&'); } - -// Prepare folder for uploaded files -if (! is_dir(FILES_DIR)) { - if (! mkdir(FILES_DIR, 0755, true)) { - die('Unable to create the upload directory: "'.FILES_DIR.'"'); - } -} - -// Check permissions for files folder -if (! is_writable(FILES_DIR)) { - die('The directory "'.FILES_DIR.'" must be writeable by your webserver user'); -} diff --git a/sources/app/common.php b/sources/app/common.php index 1f1c727..e63d82b 100644 --- a/sources/app/common.php +++ b/sources/app/common.php @@ -30,120 +30,7 @@ $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'; } + +$container['pluginLoader']->scan(); diff --git a/sources/app/constants.php b/sources/app/constants.php index 7df1e4b..2361192 100644 --- a/sources/app/constants.php +++ b/sources/app/constants.php @@ -4,8 +4,11 @@ defined('DEBUG') or define('DEBUG', false); defined('DEBUG_FILE') or define('DEBUG_FILE', __DIR__.'/../data/debug.log'); +// Plugin directory +defined('PLUGINS_DIR') or define('PLUGINS_DIR', __DIR__.'/../plugins'); + // Application version -defined('APP_VERSION') or define('APP_VERSION', '1.0.18'); +defined('APP_VERSION') or define('APP_VERSION', '1.0.19'); // Database driver: sqlite, mysql or postgres defined('DB_DRIVER') or define('DB_DRIVER', 'sqlite'); @@ -34,8 +37,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); @@ -88,6 +94,9 @@ defined('ENABLE_HSTS') or define('ENABLE_HSTS', true); // Enable or disable "X-Frame-Options: DENY" HTTP header defined('ENABLE_XFRAME') or define('ENABLE_XFRAME', true); +// Syslog +defined('ENABLE_SYSLOG') or define('ENABLE_SYSLOG', true); + // Default files directory defined('FILES_DIR') or define('FILES_DIR', 'data/files/'); @@ -111,3 +120,10 @@ defined('BRUTEFORCE_LOCKDOWN_DURATION') or define('BRUTEFORCE_LOCKDOWN_DURATION' // Session duration in second (0 = until the browser is closed) // See http://php.net/manual/en/session.configuration.php#ini.session.cookie-lifetime defined('SESSION_DURATION') or define('SESSION_DURATION', 0); + +// HTTP client proxy +defined('HTTP_PROXY_HOSTNAME') or define('HTTP_PROXY_HOSTNAME', ''); +defined('HTTP_PROXY_PORT') or define('HTTP_PROXY_PORT', '3128'); +defined('HTTP_PROXY_USERNAME') or define('HTTP_PROXY_USERNAME', ''); +defined('HTTP_PROXY_PASSWORD') or define('HTTP_PROXY_PASSWORD', ''); + diff --git a/sources/app/routes.php b/sources/app/routes.php new file mode 100644 index 0000000..159e8f6 --- /dev/null +++ b/sources/app/routes.php @@ -0,0 +1,117 @@ +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/sources/assets/css/app.css b/sources/assets/css/app.css index befd889..c0cca79 100644 --- a/sources/assets/css/app.css +++ b/sources/assets/css/app.css @@ -15,6 +15,7 @@ * Docs & License: http://fullcalendar.io/ * (c) 2015 Adam Shaw */.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 + * Font Awesome 4.4.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.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} +.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}.web-notification-icon{color:#36c}.web-notification-icon:focus,.web-notification-icon:hover{color:#000}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}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;min-height:150px;overflow:auto}.board-rotation{white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.board-column-title{cursor:pointer}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;line-height:70%}.board-add-icon a:focus,.board-add-icon a:hover{text-decoration:none;color:red}.board-column-header-task-count{color:#999;font-weight:normal}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;user-select:none}.draggable-placeholder{border:2px dashed #000;background:#fafafa;height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 3px rgba(0,0,0,0.2)}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-score{font-weight:bold}.task-board .task-score{font-size:1.1em}.task-show-details .task-score{position:absolute;bottom:5px;right:5px;font-size:2em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}.task-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:auto;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;font-size:.9em}.views li{border:1px solid #eee;padding-left:8px;padding-right:8px;padding-top:5px;padding-bottom:5px;display:inline}.menu-inline li.active a,.views li.active a{font-weight:bold;color:#000;text-decoration:none}.views li:first-child{border-right:0;border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-left:0;border-top-right-radius:5px;border-bottom-right-radius:5px}.filters{display:inline-block;border:1px solid #eee;border-radius:5px;padding:5px;padding-right:10px;margin-left:8px}.filters ul{font-size:.8em}.page-header .filters ul{font-size:.9em}form.search{display:inline}div.search{margin-bottom:20px}.filter-dropdowns{font-size:.9em;display:inline-block}div.ganttview-hzheader-month,div.ganttview-hzheader-day,div.ganttview-vtheader,div.ganttview-vtheader-item-name,div.ganttview-vtheader-series,div.ganttview-grid,div.ganttview-grid-row-cell{float:left}div.ganttview-hzheader-month,div.ganttview-hzheader-day{text-align:center}div.ganttview-grid-row-cell.last,div.ganttview-hzheader-day.last,div.ganttview-hzheader-month.last{border-right:0}div.ganttview{border:1px solid #999}div.ganttview-hzheader-month{width:60px;height:20px;border-right:1px solid #d0d0d0;line-height:20px;overflow:hidden}div.ganttview-hzheader-day{width:20px;height:20px;border-right:1px solid #f0f0f0;border-top:1px solid #d0d0d0;line-height:20px;color:#777}div.ganttview-vtheader{margin-top:41px;width:400px;overflow:hidden;background-color:#fff}div.ganttview-vtheader-item{color:#666}div.ganttview-vtheader-series-name{width:400px;height:31px;line-height:31px;padding-left:3px;border-top:1px solid #d0d0d0;font-size:.9em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}div.ganttview-vtheader-series-name a{color:#666;text-decoration:none}div.ganttview-vtheader-series-name a:hover{color:#333;text-decoration:underline}div.ganttview-vtheader-series-name a i{color:#000}div.ganttview-vtheader-series-name a:hover i{color:#666}div.ganttview-slide-container{overflow:auto;border-left:1px solid #999}div.ganttview-grid-row-cell{width:20px;height:31px;border-right:1px solid #f0f0f0;border-top:1px solid #f0f0f0}div.ganttview-grid-row-cell.ganttview-weekend{background-color:#fafafa}div.ganttview-blocks{margin-top:40px}div.ganttview-block-container{height:28px;padding-top:4px}div.ganttview-block{position:relative;height:25px;background-color:#e5ecf9;border:1px solid silver;border-radius:3px}.ganttview-block-movable{cursor:move}div.ganttview-block-not-defined{border-color:#000;background-color:#000}div.ganttview-block-text{position:absolute;height:12px;font-size:.7em;color:#999;padding:2px 3px}div.ganttview-block div.ui-resizable-handle.ui-resizable-s{bottom:-0} \ No newline at end of file diff --git a/sources/assets/css/print.css b/sources/assets/css/print.css index db889a6..ff5570c 100644 --- a/sources/assets/css/print.css +++ b/sources/assets/css/print.css @@ -15,6 +15,7 @@ * Docs & License: http://fullcalendar.io/ * (c) 2015 Adam Shaw */.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 + * Font Awesome 4.4.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.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} +.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;min-height:150px;overflow:auto}.board-rotation{white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.board-column-title{cursor:pointer}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;line-height:70%}.board-add-icon a:focus,.board-add-icon a:hover{text-decoration:none;color:red}.board-column-header-task-count{color:#999;font-weight:normal}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;user-select:none}.draggable-placeholder{border:2px dashed #000;background:#fafafa;height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 3px rgba(0,0,0,0.2)}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-score{font-weight:bold}.task-board .task-score{font-size:1.1em}.task-show-details .task-score{position:absolute;bottom:5px;right:5px;font-size:2em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}.task-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/sources/assets/css/src/base.css b/sources/assets/css/src/base.css index 0077669..8b15ac1 100644 --- a/sources/assets/css/src/base.css +++ b/sources/assets/css/src/base.css @@ -52,7 +52,6 @@ hr { margin-right: 10px; } -/* datepicker */ #ui-datepicker-div { font-size: 0.8em; } @@ -62,3 +61,13 @@ hr { right: 3px; bottom: 3px; } + +.web-notification-icon { + color: #3366CC; +} + +.web-notification-icon:focus, +.web-notification-icon:hover { + color: #000; +} + diff --git a/sources/assets/css/src/board.css b/sources/assets/css/src/board.css index 2111b36..2cf7413 100644 --- a/sources/assets/css/src/board.css +++ b/sources/assets/css/src/board.css @@ -11,7 +11,7 @@ /* board table */ #board-container { - overflow-x: scroll; + overflow-x: auto; } #board { @@ -61,10 +61,12 @@ td.board-column-task-collapsed { .board-rotation-wrapper { position: relative; padding: 8px 4px; + min-height: 150px; + overflow: auto; } .board-rotation { - min-width: 250px; + white-space: nowrap; -webkit-backface-visibility: hidden; -webkit-transform: rotate(90deg); -moz-transform: rotate(90deg); @@ -77,6 +79,10 @@ td.board-column-task-collapsed { } /* column header */ +.board-column-title { + cursor: pointer; +} + .board-add-icon { float: left; padding: 0 5px; @@ -105,26 +111,22 @@ th.board-column-header-collapsed .board-column-header-task-count { } /* swimlanes */ -th.board-swimlane-header { - width: 120px; -} - a.board-swimlane-toggle { font-size: 0.95em; + text-decoration: none; } -.board-swimlane-toggle-title { - font-size: 0.85em; - display: none; -} - -.board-swimlane-title { - vertical-align: top; +a.board-swimlane-toggle:hover, +a.board-swimlane-toggle:focus { + color: #000; + text-decoration: none; + border: none; } /* board task list */ .board-task-list { overflow: auto; + min-height: 60px; } .board-task-list-limit { diff --git a/sources/assets/css/src/dropdown.css b/sources/assets/css/src/dropdown.css index 462a38a..30e5c22 100644 --- a/sources/assets/css/src/dropdown.css +++ b/sources/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/sources/assets/css/src/filters.css b/sources/assets/css/src/filters.css index c92b275..8f88955 100644 --- a/sources/assets/css/src/filters.css +++ b/sources/assets/css/src/filters.css @@ -6,12 +6,13 @@ .views { display: inline-block; margin-right: 10px; + font-size: 0.9em; } .views li { border: 1px solid #eee; - padding-left: 12px; - padding-right: 12px; + padding-left: 8px; + padding-right: 8px; padding-top: 5px; padding-bottom: 5px; display: inline; @@ -40,11 +41,9 @@ display: inline-block; border: 1px solid #eee; border-radius: 5px; - padding-left: 10px; + padding: 5px; padding-right: 10px; - padding-top: 5px; - padding-bottom: 5px; - margin-left: 10px; + margin-left: 8px; } .filters ul { @@ -64,5 +63,6 @@ div.search { } .filter-dropdowns { + font-size: 0.9em; display: inline-block; } diff --git a/sources/assets/css/src/form.css b/sources/assets/css/src/form.css index 56506e2..79bdf7b 100644 --- a/sources/assets/css/src/form.css +++ b/sources/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 { @@ -176,10 +173,6 @@ input.form-input-large { line-height: 25px; } -.form-checkbox-group label { - display: inline; -} - /* preview tabs */ label + .form-tabs { margin-top: 10px; @@ -190,8 +183,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/sources/assets/css/src/gantt.css b/sources/assets/css/src/gantt.css index 06349e8..def5bf3 100644 --- a/sources/assets/css/src/gantt.css +++ b/sources/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/sources/assets/css/src/popover.css b/sources/assets/css/src/popover.css index ab49942..37af5b6 100644 --- a/sources/assets/css/src/popover.css +++ b/sources/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%; + overflow: auto; + max-height: 85%; } diff --git a/sources/assets/css/src/table.css b/sources/assets/css/src/table.css index 21b8ccc..51d6ecd 100644 --- a/sources/assets/css/src/table.css +++ b/sources/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/sources/assets/css/src/task.css b/sources/assets/css/src/task.css index 2dfaca8..7bfb63e 100644 --- a/sources/assets/css/src/task.css +++ b/sources/assets/css/src/task.css @@ -1,7 +1,7 @@ /* task inside the board */ .task-board { position: relative; - margin-bottom: 2px; + margin-bottom: 4px; border: 1px solid #000; padding: 2px; font-size: 0.85em; @@ -9,7 +9,7 @@ } div.task-board-recent { - box-shadow: 2px 2px 5px rgba(0,0,0,0.25); + box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.2); } div.task-board-status-closed { @@ -42,8 +42,6 @@ a.task-board-collapsed-title { } .task-board .dropdown { - float: left; - margin-right: 5px; font-size: 1.1em; } @@ -305,3 +303,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/sources/assets/css/vendor/font-awesome.min.css b/sources/assets/css/vendor/font-awesome.min.css index 24fcc04..ee4e978 100644 --- a/sources/assets/css/vendor/font-awesome.min.css +++ b/sources/assets/css/vendor/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.4.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"} \ No newline at end of file + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} diff --git a/sources/assets/fonts/FontAwesome.otf b/sources/assets/fonts/FontAwesome.otf index f7936cc..681bdd4 100644 Binary files a/sources/assets/fonts/FontAwesome.otf and b/sources/assets/fonts/FontAwesome.otf differ diff --git a/sources/assets/fonts/fontawesome-webfont.eot b/sources/assets/fonts/fontawesome-webfont.eot index 33b2bb8..a30335d 100644 Binary files a/sources/assets/fonts/fontawesome-webfont.eot and b/sources/assets/fonts/fontawesome-webfont.eot differ diff --git a/sources/assets/fonts/fontawesome-webfont.svg b/sources/assets/fonts/fontawesome-webfont.svg index 1ee89d4..6fd19ab 100644 --- a/sources/assets/fonts/fontawesome-webfont.svg +++ b/sources/assets/fonts/fontawesome-webfont.svg @@ -399,7 +399,7 @@ - + @@ -411,8 +411,8 @@ - - + + @@ -459,7 +459,7 @@ - + @@ -483,13 +483,13 @@ - + - + @@ -523,7 +523,7 @@ - + @@ -531,18 +531,18 @@ - + - + - + - + @@ -556,10 +556,85 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sources/assets/fonts/fontawesome-webfont.ttf b/sources/assets/fonts/fontawesome-webfont.ttf index ed9372f..d7994e1 100644 Binary files a/sources/assets/fonts/fontawesome-webfont.ttf and b/sources/assets/fonts/fontawesome-webfont.ttf differ diff --git a/sources/assets/fonts/fontawesome-webfont.woff b/sources/assets/fonts/fontawesome-webfont.woff index 8b280b9..6fd4ede 100644 Binary files a/sources/assets/fonts/fontawesome-webfont.woff and b/sources/assets/fonts/fontawesome-webfont.woff differ diff --git a/sources/assets/fonts/fontawesome-webfont.woff2 b/sources/assets/fonts/fontawesome-webfont.woff2 index 3311d58..5560193 100644 Binary files a/sources/assets/fonts/fontawesome-webfont.woff2 and b/sources/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/sources/assets/js/app.js b/sources/assets/js/app.js index 460fd3d..02cafe0 100644 --- a/sources/assets/js/app.js +++ b/sources/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;eel 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:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fi",{buttonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fr",{buttonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la
    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:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("it",{buttonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il
    giorno",eventLimitText:function(a){return"+altri "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(a){return"午後"===a},meridiem:function(a,b,c){return 12>a?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),a.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("ja",{buttonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(a){return"他 "+a+" 件"}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),d="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(b.defineLocale||b.lang).call(b,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nl","nl",{closeText:"Sluiten",prevText:"←",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nl",{buttonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tirs_ons_tors_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"H.mm",LTS:"LT.ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i går kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en måned",MM:"%d måneder",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nb","nb",{closeText:"Lukk",prevText:"«Forrige",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["søn","man","tir","ons","tor","fre","lør"],dayNames:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],dayNamesMin:["sø","ma","ti","on","to","fr","lø"],weekHeader:"Uke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nb",{buttonText:{month:"Måned",week:"Uke",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"til"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return 5>a%10&&a%10>1&&~~(a/10)%10!==1}function d(a,b,d){var e=a+" ";switch(d){case"m":return b?"minuta":"minutę";case"mm":return e+(c(a)?"minuty":"minut");case"h":return b?"godzina":"godzinę";case"hh":return e+(c(a)?"godziny":"godzin");case"MM":return e+(c(a)?"miesiące":"miesięcy");case"yy":return e+(c(a)?"lata":"lat")}}var e="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),f="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(b.defineLocale||b.lang).call(b,"pl",{months:function(a,b){return/D MMMM/.test(b)?f[a.month()]:e[a.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:d,mm:d,h:d,hh:d,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:d,y:"rok",yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pl",{buttonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pt","pt",{closeText:"Fechar",prevText:"Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Agenda"},allDayText:"Todo o dia",eventLimitText:"mais"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),a.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt-br",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(a){return"mais +"+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function d(a,b,d){var e={mm:b?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===d?b?"минута":"минуту":a+" "+c(e[d],+a)}function e(a,b){var c={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function f(a,b){var c={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function g(a,b){var c={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},d=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}(b.defineLocale||b.lang).call(b,"ru",{months:e,monthsShort:f,weekdays:g,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(a){if(a.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:d,mm:d,h:"час",hh:d,d:"день",dd:d,M:"месяц",MM:d,y:"год",yy:d},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(a){return/^(дня|вечера)$/.test(a)},meridiem:function(a,b,c){return 4>a?"ночи":12>a?"утра":17>a?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":return a+"-й";case"D":return a+"-го";case"w":case"W":return a+"-я";default:return a}},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("ru",{buttonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(a){return"+ ещё "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"e":1===b?"a":2===b?"a":"e";return a+c},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sv",{buttonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,b,d){var e=c.words[d];return 1===d.length?b?e[0]:e[1]:a+" "+c.correctGrammaticalCase(a,e)}};(b.defineLocale||b.lang).call(b,"sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var a=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:c.translate,mm:c.translate,h:c.translate,hh:c.translate,d:"dan",dd:c.translate,M:"mesec",MM:c.translate,y:"godinu",yy:c.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sr",{buttonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(a){return"+ још "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(a){return"หลังเที่ยง"===a},meridiem:function(a,b,c){return 12>a?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),a.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("th",{buttonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(b.defineLocale||b.lang).call(b,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(a){if(0===a)return a+"'ıncı";var b=a%10,d=a%100-b,e=a>=100?100:null;return a+(c[b]||c[d]||c[e])},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("tr",{buttonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(a,b){return 12===a&&(a=0),"凌晨"===b||"早上"===b||"上午"===b?a:"下午"===b||"晚上"===b?a+12:a>=11?a:a+12},meridiem:function(a,b,c){var d=100*a+b;return 600>d?"凌晨":900>d?"早上":1130>d?"上午":1230>d?"中午":1800>d?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()-a.unix()>=604800?"[下]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},lastWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()0};r.prototype.open=function(w){var v=this;v.app.dropdown.close();$.get(w,function(x){$("body").append('
    '+x+"
    ");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;$("
    ").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'
    '+$(this).attr("title")+"
    "}$.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''}}).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(' ')};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;z0){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",{"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("
    ",{"class":"ganttview-vtheader"});var w=jQuery("
    ",{"class":"ganttview-vtheader-item"});var y=jQuery("
    ",{"class":"ganttview-vtheader-series"});for(var v=0;v").append(jQuery("",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[v])})).append(" ");if(this.data[v].type=="task"){x.append(jQuery("",{href:this.data[v].link,target:"_blank"}).append(this.data[v].title))}else{x.append(jQuery("",{href:this.data[v].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('')).append(" ").append(jQuery("",{href:this.data[v].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('')).append(" ").append(jQuery("",{href:this.data[v].link,target:"_blank"}).append(this.data[v].title))}y.append(jQuery("
    ",{"class":"ganttview-vtheader-series-name"}).append(x))}w.append(y);z.append(w);return z};c.prototype.renderSlider=function(w,y){var v=jQuery("
    ",{"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("
    ",{"class":"ganttview-hzheader"});var B=jQuery("
    ",{"class":"ganttview-hzheader-months"});var A=jQuery("
    ",{"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("
    ",{"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("
    ",{"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("
    ",{"class":"ganttview-grid"});var A=jQuery("
    ",{"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("
    ",{"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",{"class":"ganttview-blocks"});for(var v=0;v",{"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",{"class":"ganttview-block-text"});var y=jQuery("
    ",{"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=""+w.column_title+" ("+w.progress+")
    "+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("