diff options
167 files changed, 7596 insertions, 2357 deletions
diff --git a/README.markdown b/README.markdown index 2731b8a3..94424af7 100644 --- a/README.markdown +++ b/README.markdown @@ -31,7 +31,7 @@ Features - Host anywhere (shared hosting, VPS, Raspberry Pi or localhost) - No external dependencies - **Super easy setup**, copy and paste files and you are done! -- Translations in English, French, Brazilian Portuguese, Spanish, German, Polish, Swedish, Finnish, Chinese... +- Translations in English, French, Brazilian Portuguese, Spanish, German, Polish, Swedish, Finnish, Italian, Chinese... Known bugs and feature requests ------------------------------- @@ -61,6 +61,7 @@ Documentation - [Installation on Ubuntu](docs/ubuntu-installation.markdown) - [Installation on Debian](docs/debian-installation.markdown) - [Installation on Centos](docs/centos-installation.markdown) +- [Installation on Windows Server with IIS](docs/windows-iis-installation.markdown) - [Upgrade Kanboard to a new version](docs/update.markdown) - [Secure connections (HTTPS)](docs/secure-connections.markdown) @@ -107,6 +108,7 @@ Original author: [Frédéric Guillot](http://fredericguillot.com/) Contributors: - Alex Butum: https://github.com/dZkF9RWJT6wN8ux +- Ashish Kulkarni: https://github.com/ashkulz - Claudio Lobo - Cmer: https://github.com/chncsu - Floaltvater: https://github.com/floaltvater @@ -118,6 +120,7 @@ Contributors: - Levlaz: https://github.com/levlaz - Mathgl67: https://github.com/mathgl67 - Matthieu Keller: https://github.com/maggick +- Mauro Mariño: https://github.com/moromarino - Maxime: https://github.com/EpocDotFr - Moraxy: https://github.com/moraxy - Nala Ginrut: https://github.com/NalaGinrut @@ -130,6 +133,8 @@ Contributors: - Sebastien pacilly: https://github.com/spacilly - Sylvain Veyrié: https://github.com/turb - Toomyem: https://github.com/Toomyem +- Tony G. Bolaño: https://github.com/tonybolanyo +- Torsten: https://github.com/misterfu - Troloo: https://github.com/troloo - Typz: https://github.com/Typz - Ybarc: https://github.com/ybarc diff --git a/app/Action/TaskDuplicateAnotherProject.php b/app/Action/TaskDuplicateAnotherProject.php index 7ef0f6ab..0f14cbed 100644 --- a/app/Action/TaskDuplicateAnotherProject.php +++ b/app/Action/TaskDuplicateAnotherProject.php @@ -73,7 +73,8 @@ class TaskDuplicateAnotherProject extends Base { if ($data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id')) { - $this->task->duplicateToAnotherProject($data['task_id'], $this->getParam('project_id')); + $task = $this->task->getById($data['task_id']); + $this->task->duplicateToAnotherProject($this->getParam('project_id'), $task); return true; } diff --git a/app/Action/TaskMoveAnotherProject.php b/app/Action/TaskMoveAnotherProject.php new file mode 100644 index 00000000..8091053e --- /dev/null +++ b/app/Action/TaskMoveAnotherProject.php @@ -0,0 +1,84 @@ +<?php + +namespace Action; + +use Model\Task; + +/** + * Move a task to another project + * + * @package action + * @author Frederic Guillot + */ +class TaskMoveAnotherProject extends Base +{ + /** + * Task model + * + * @accesss private + * @var \Model\Task + */ + private $task; + + /** + * Constructor + * + * @access public + * @param integer $project_id Project id + * @param \Model\Task $task Task model instance + */ + public function __construct($project_id, Task $task) + { + parent::__construct($project_id); + $this->task = $task; + } + + /** + * Get the required parameter for the action (defined by the user) + * + * @access public + * @return array + */ + public function getActionRequiredParameters() + { + return array( + 'column_id' => t('Column'), + 'project_id' => t('Project'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'column_id', + 'project_id', + ); + } + + /** + * Execute the action + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + if ($data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id')) { + + $task = $this->task->getById($data['task_id']); + $this->task->moveToAnotherProject($this->getParam('project_id'), $task); + + return true; + } + + return false; + } +} diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php index bb17653d..97d4d0e3 100644 --- a/app/Auth/Ldap.php +++ b/app/Auth/Ldap.php @@ -96,8 +96,21 @@ class Ldap extends Base ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); - if (! @ldap_bind($ldap, LDAP_USERNAME, LDAP_PASSWORD)) { - die('Unable to bind to the LDAP server: "'.LDAP_SERVER.'"'); + if (LDAP_BIND_TYPE === 'user') { + $ldap_username = sprintf(LDAP_USERNAME, $username); + $ldap_password = $password; + } + else if (LDAP_BIND_TYPE === 'proxy') { + $ldap_username = LDAP_USERNAME; + $ldap_password = LDAP_PASSWORD; + } + else { + $ldap_username = null; + $ldap_password = null; + } + + if (! @ldap_bind($ldap, $ldap_username, $ldap_password)) { + return false; } $sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, sprintf(LDAP_USER_PATTERN, $username), array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL)); diff --git a/app/Auth/RememberMe.php b/app/Auth/RememberMe.php index 3cf6fc86..50e0bcef 100644 --- a/app/Auth/RememberMe.php +++ b/app/Auth/RememberMe.php @@ -3,6 +3,7 @@ namespace Auth; use Core\Security; +use Core\Tool; /** * RememberMe model @@ -309,7 +310,7 @@ class RememberMe extends Base $expiration, BASE_URL_DIRECTORY, null, - ! empty($_SERVER['HTTPS']), + Tool::isHTTPS(), true ); } @@ -342,7 +343,7 @@ class RememberMe extends Base time() - 3600, BASE_URL_DIRECTORY, null, - ! empty($_SERVER['HTTPS']), + Tool::isHTTPS(), true ); } diff --git a/app/Auth/ReverseProxy.php b/app/Auth/ReverseProxy.php index e23ee24f..6880f5fa 100644 --- a/app/Auth/ReverseProxy.php +++ b/app/Auth/ReverseProxy.php @@ -63,8 +63,14 @@ class ReverseProxy extends Base */ private function createUser($login) { + $email = strpos($login, '@') !== false ? $login : ''; + + if (REVERSE_PROXY_DEFAULT_DOMAIN !== '' && empty($email)) { + $email = $login.'@'.REVERSE_PROXY_DEFAULT_DOMAIN; + } + return $this->user->create(array( - 'email' => strpos($login, '@') !== false ? $login : '', + 'email' => $email, 'username' => $login, 'is_admin' => REVERSE_PROXY_DEFAULT_ADMIN === $login, 'is_ldap_user' => 1, diff --git a/app/Controller/Action.php b/app/Controller/Action.php index 797bbfa2..b2f80009 100644 --- a/app/Controller/Action.php +++ b/app/Controller/Action.php @@ -17,15 +17,9 @@ class Action extends Base */ public function index() { - $project_id = $this->request->getIntegerParam('project_id'); - $project = $this->project->getById($project_id); + $project = $this->getProject(); - if (! $project) { - $this->session->flashError(t('Project not found.')); - $this->response->redirect('?controller=project'); - } - - $this->response->html($this->template->layout('action_index', array( + $this->response->html($this->projectLayout('action_index', array( 'values' => array('project_id' => $project['id']), 'project' => $project, 'actions' => $this->action->getAllByProject($project['id']), @@ -49,18 +43,11 @@ class Action extends Base */ public function params() { - $project_id = $this->request->getIntegerParam('project_id'); - $project = $this->project->getById($project_id); - - if (! $project) { - $this->session->flashError(t('Project not found.')); - $this->response->redirect('?controller=project'); - } - + $project = $this->getProject(); $values = $this->request->getValues(); $action = $this->action->load($values['action_name'], $values['project_id']); - $this->response->html($this->template->layout('action_params', array( + $this->response->html($this->projectLayout('action_params', array( 'values' => $values, 'action_params' => $action->getActionRequiredParameters(), 'columns_list' => $this->board->getColumnsList($project['id']), @@ -81,14 +68,7 @@ class Action extends Base */ public function create() { - $project_id = $this->request->getIntegerParam('project_id'); - $project = $this->project->getById($project_id); - - if (! $project) { - $this->session->flashError(t('Project not found.')); - $this->response->redirect('?controller=project'); - } - + $project = $this->getProject(); $values = $this->request->getValues(); list($valid,) = $this->action->validateCreation($values); @@ -113,10 +93,13 @@ class Action extends Base */ public function confirm() { - $this->response->html($this->template->layout('action_remove', array( + $project = $this->getProject(); + + $this->response->html($this->projectLayout('action_remove', array( 'action' => $this->action->getById($this->request->getIntegerParam('action_id')), 'available_events' => $this->action->getAvailableEvents(), 'available_actions' => $this->action->getAvailableActions(), + 'project' => $project, 'menu' => 'projects', 'title' => t('Remove an action') ))); diff --git a/app/Controller/Base.php b/app/Controller/Base.php index ed8a6b3b..00bfb79b 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -27,7 +27,11 @@ use Model\LastLogin; * @property \Model\Project $project * @property \Model\SubTask $subTask * @property \Model\Task $task + * @property \Model\TaskHistory $taskHistory + * @property \Model\CommentHistory $commentHistory + * @property \Model\SubtaskHistory $subtaskHistory * @property \Model\User $user + * @property \Model\Webhook $webhook */ abstract class Base { @@ -129,30 +133,57 @@ abstract class Base } // Attach events - $this->action->attachEvents(); - $this->project->attachEvents(); - $this->webhook->attachEvents(); - $this->notification->attachEvents(); + $this->attachEvents(); + } + + /** + * Attach events + * + * @access private + */ + private function attachEvents() + { + $models = array( + 'action', + 'project', + 'webhook', + 'notification', + 'taskHistory', + 'commentHistory', + 'subtaskHistory', + ); + + foreach ($models as $model) { + $this->$model->attachEvents(); + } } /** * Application not found page (404 error) * * @access public + * @param boolean $no_layout Display the layout or not */ - public function notfound() + public function notfound($no_layout = false) { - $this->response->html($this->template->layout('app_notfound', array('title' => t('Page not found')))); + $this->response->html($this->template->layout('app_notfound', array( + 'title' => t('Page not found'), + 'no_layout' => $no_layout, + ))); } /** * Application forbidden page * * @access public + * @param boolean $no_layout Display the layout or not */ - public function forbidden() + public function forbidden($no_layout = false) { - $this->response->html($this->template->layout('app_forbidden', array('title' => t('Access Forbidden')))); + $this->response->html($this->template->layout('app_forbidden', array( + 'title' => t('Access Forbidden'), + 'no_layout' => $no_layout, + ))); } /** @@ -211,6 +242,22 @@ abstract class Base } /** + * Common layout for project views + * + * @access protected + * @param string $template Template name + * @param array $params Template parameters + * @return string + */ + protected function projectLayout($template, array $params) + { + $content = $this->template->load($template, $params); + $params['project_content_for_layout'] = $content; + + return $this->template->layout('project_layout', $params); + } + + /** * Common method to get a task for task views * * @access protected diff --git a/app/Controller/Board.php b/app/Controller/Board.php index 37a8d118..d7afb4eb 100644 --- a/app/Controller/Board.php +++ b/app/Controller/Board.php @@ -51,39 +51,27 @@ class Board extends Base * * @access public */ - public function assign() + public function changeAssignee() { - $task = $this->task->getById($this->request->getIntegerParam('task_id')); + $task = $this->getTask(); $project = $this->project->getById($task['project_id']); - $projects = $this->project->getListByStatus(ProjectModel::ACTIVE); - - if ($this->acl->isRegularUser()) { - $projects = $this->project->filterListByAccess($projects, $this->acl->getUserId()); - } - - if (! $project) $this->notfound(); - $this->checkProjectPermissions($project['id']); + $projects = $this->project->getAvailableList($this->acl->getUserId()); + $params = array( + 'errors' => array(), + 'values' => $task, + 'users_list' => $this->project->getUsersList($project['id']), + 'projects' => $projects, + 'current_project_id' => $project['id'], + 'current_project_name' => $project['name'], + ); if ($this->request->isAjax()) { - $this->response->html($this->template->load('board_assign', array( - 'errors' => array(), - 'values' => $task, - 'users_list' => $this->project->getUsersList($project['id']), - 'projects' => $projects, - 'current_project_id' => $project['id'], - 'current_project_name' => $project['name'], - ))); + $this->response->html($this->template->load('board_assignee', $params)); } else { - $this->response->html($this->template->layout('board_assign', array( - 'errors' => array(), - 'values' => $task, - 'users_list' => $this->project->getUsersList($project['id']), - 'projects' => $projects, - 'current_project_id' => $project['id'], - 'current_project_name' => $project['name'], + $this->response->html($this->template->layout('board_assignee', $params + array( 'menu' => 'boards', 'title' => t('Change assignee').' - '.$task['title'], ))); @@ -95,7 +83,7 @@ class Board extends Base * * @access public */ - public function assignTask() + public function updateAssignee() { $values = $this->request->getValues(); $this->checkProjectPermissions($values['project_id']); @@ -113,6 +101,60 @@ class Board extends Base } /** + * Change a task category directly from the board + * + * @access public + */ + public function changeCategory() + { + $task = $this->getTask(); + $project = $this->project->getById($task['project_id']); + $projects = $this->project->getAvailableList($this->acl->getUserId()); + $params = array( + 'errors' => array(), + 'values' => $task, + 'categories_list' => $this->category->getList($project['id']), + 'projects' => $projects, + 'current_project_id' => $project['id'], + 'current_project_name' => $project['name'], + ); + + if ($this->request->isAjax()) { + + $this->response->html($this->template->load('board_category', $params)); + } + else { + + $this->response->html($this->template->layout('board_category', $params + array( + 'menu' => 'boards', + 'title' => t('Change category').' - '.$task['title'], + ))); + } + } + + /** + * Validate a category modification + * + * @access public + */ + public function updateCategory() + { + $values = $this->request->getValues(); + $this->checkProjectPermissions($values['project_id']); + + list($valid,) = $this->task->validateCategoryModification($values); + + if ($valid && $this->task->update($values)) { + $this->session->flash(t('Task updated successfully.')); + } + else { + $this->session->flashError(t('Unable to update your task.')); + } + + $this->response->redirect('?controller=board&action=show&project_id='.$values['project_id']); + } + + /** * Display the public version of a board * Access checked by a simple token, no user login, read only, auto-refresh * @@ -125,7 +167,7 @@ class Board extends Base // Token verification if (! $project) { - $this->response->text('Not Authorized', 401); + $this->forbidden(true); } // Display the board with a specific layout @@ -198,6 +240,9 @@ class Board extends Base $this->notfound(); } + $board_selector = $projects; + unset($board_selector[$project_id]); + $this->response->html($this->template->layout('board_index', array( 'users' => $this->project->getUsersList($project_id, true, true), 'filters' => array('user_id' => $user_id), @@ -208,7 +253,7 @@ class Board extends Base 'categories' => $this->category->getList($project_id, true, true), 'menu' => 'boards', 'title' => $projects[$project_id], - 'board_selector' => $projects, + 'board_selector' => $board_selector, ))); } @@ -219,12 +264,8 @@ class Board extends Base */ public function edit() { - $project_id = $this->request->getIntegerParam('project_id'); - $project = $this->project->getById($project_id); - - if (! $project) $this->notfound(); - - $columns = $this->board->getColumns($project_id); + $project = $this->getProject(); + $columns = $this->board->getColumns($project['id']); $values = array(); foreach ($columns as $column) { @@ -232,9 +273,9 @@ class Board extends Base $values['task_limit['.$column['id'].']'] = $column['task_limit'] ?: null; } - $this->response->html($this->template->layout('board_edit', array( + $this->response->html($this->projectLayout('board_edit', array( 'errors' => array(), - 'values' => $values + array('project_id' => $project_id), + 'values' => $values + array('project_id' => $project['id']), 'columns' => $columns, 'project' => $project, 'menu' => 'projects', @@ -249,12 +290,8 @@ class Board extends Base */ public function update() { - $project_id = $this->request->getIntegerParam('project_id'); - $project = $this->project->getById($project_id); - - if (! $project) $this->notfound(); - - $columns = $this->board->getColumns($project_id); + $project = $this->getProject(); + $columns = $this->board->getColumns($project['id']); $data = $this->request->getValues(); $values = $columns_list = array(); @@ -277,9 +314,9 @@ class Board extends Base } } - $this->response->html($this->template->layout('board_edit', array( + $this->response->html($this->projectLayout('board_edit', array( 'errors' => $errors, - 'values' => $values + array('project_id' => $project_id), + 'values' => $values + array('project_id' => $project['id']), 'columns' => $columns, 'project' => $project, 'menu' => 'projects', @@ -294,11 +331,7 @@ class Board extends Base */ public function add() { - $project_id = $this->request->getIntegerParam('project_id'); - $project = $this->project->getById($project_id); - - if (! $project) $this->notfound(); - + $project = $this->getProject(); $columns = $this->board->getColumnsList($project_id); $data = $this->request->getValues(); $values = array(); @@ -320,7 +353,7 @@ class Board extends Base } } - $this->response->html($this->template->layout('board_edit', array( + $this->response->html($this->projectLayout('board_edit', array( 'errors' => $errors, 'values' => $values + $data, 'columns' => $columns, @@ -337,8 +370,11 @@ class Board extends Base */ public function confirm() { - $this->response->html($this->template->layout('board_remove', array( + $project = $this->getProject(); + + $this->response->html($this->projectLayout('board_remove', array( 'column' => $this->board->getColumn($this->request->getIntegerParam('column_id')), + 'project' => $project, 'menu' => 'projects', 'title' => t('Remove a column from a board') ))); @@ -370,27 +406,31 @@ class Board extends Base */ public function save() { - if ($this->request->isAjax()) { + $project_id = $this->request->getIntegerParam('project_id'); - $project_id = $this->request->getIntegerParam('project_id'); - $values = $this->request->getValues(); + if ($project_id > 0 && $this->request->isAjax()) { - if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) { - $this->response->text('Not Authorized', 401); + if (! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) { + $this->response->status(401); } - if (isset($values['positions'])) { - $this->board->saveTasksPosition($values['positions'], $values['selected_task_id']); + $values = $this->request->getValues(); + + if ($this->task->movePosition($project_id, $values['task_id'], $values['column_id'], $values['position'])) { + + $this->response->html( + $this->template->load('board_show', array( + 'current_project_id' => $project_id, + 'board' => $this->board->get($project_id), + 'categories' => $this->category->getList($project_id, false), + )), + 201 + ); } + else { - $this->response->html( - $this->template->load('board_show', array( - 'current_project_id' => $project_id, - 'board' => $this->board->get($project_id), - 'categories' => $this->category->getList($project_id, false), - )), - 201 - ); + $this->response->status(400); + } } else { $this->response->status(401); diff --git a/app/Controller/Category.php b/app/Controller/Category.php index 5fd59c0a..3c9d0523 100644 --- a/app/Controller/Category.php +++ b/app/Controller/Category.php @@ -38,7 +38,7 @@ class Category extends Base { $project = $this->getProject(); - $this->response->html($this->template->layout('category_index', array( + $this->response->html($this->projectLayout('category_index', array( 'categories' => $this->category->getList($project['id'], false), 'values' => array('project_id' => $project['id']), 'errors' => array(), @@ -71,7 +71,7 @@ class Category extends Base } } - $this->response->html($this->template->layout('category_index', array( + $this->response->html($this->projectLayout('category_index', array( 'categories' => $this->category->getList($project['id'], false), 'values' => $values, 'errors' => $errors, @@ -91,7 +91,7 @@ class Category extends Base $project = $this->getProject(); $category = $this->getCategory($project['id']); - $this->response->html($this->template->layout('category_edit', array( + $this->response->html($this->projectLayout('category_edit', array( 'values' => $category, 'errors' => array(), 'project' => $project, @@ -123,7 +123,7 @@ class Category extends Base } } - $this->response->html($this->template->layout('category_edit', array( + $this->response->html($this->projectLayout('category_edit', array( 'values' => $values, 'errors' => $errors, 'project' => $project, @@ -142,7 +142,7 @@ class Category extends Base $project = $this->getProject(); $category = $this->getCategory($project['id']); - $this->response->html($this->template->layout('category_remove', array( + $this->response->html($this->projectLayout('category_remove', array( 'project' => $project, 'category' => $category, 'menu' => 'projects', diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php index a0a11fc8..a9032ed8 100644 --- a/app/Controller/Comment.php +++ b/app/Controller/Comment.php @@ -25,26 +25,16 @@ class Comment extends Base } if (! $this->acl->isAdminUser() && $comment['user_id'] != $this->acl->getUserId()) { - $this->forbidden(); + $this->response->html($this->template->layout('comment_forbidden', array( + 'menu' => 'tasks', + 'title' => t('Access Forbidden') + ))); } return $comment; } /** - * Forbidden page for comments - * - * @access public - */ - public function forbidden() - { - $this->response->html($this->template->layout('comment_forbidden', array( - 'menu' => 'tasks', - 'title' => t('Access Forbidden') - ))); - } - - /** * Add comment form * * @access public diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 48bfb9cf..a364c5f4 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -19,27 +19,15 @@ class Config extends Base { $this->response->html($this->template->layout('config_index', array( 'db_size' => $this->config->getDatabaseSize(), - 'user' => $_SESSION['user'], - 'user_projects' => $this->project->getAvailableList($this->acl->getUserId()), - 'notifications' => $this->notification->readSettings($this->acl->getUserId()), 'languages' => $this->config->getLanguages(), 'values' => $this->config->getAll(), 'errors' => array(), 'menu' => 'config', 'title' => t('Settings'), 'timezones' => $this->config->getTimezones(), - 'remember_me_sessions' => $this->authentication->backend('rememberMe')->getAll($this->acl->getUserId()), - 'last_logins' => $this->lastLogin->getAll($this->acl->getUserId()), ))); } - public function notifications() - { - $values = $this->request->getValues(); - $this->notification->saveSettings($this->acl->getUserId(), $values); - $this->response->redirect('?controller=config#notifications'); - } - /** * Validate and save settings * @@ -64,17 +52,12 @@ class Config extends Base $this->response->html($this->template->layout('config_index', array( 'db_size' => $this->config->getDatabaseSize(), - 'user' => $_SESSION['user'], - 'user_projects' => $this->project->getAvailableList($this->acl->getUserId()), - 'notifications' => $this->notification->readSettings($this->acl->getUserId()), 'languages' => $this->config->getLanguages(), 'values' => $values, 'errors' => $errors, 'menu' => 'config', 'title' => t('Settings'), 'timezones' => $this->config->getTimezones(), - 'remember_me_sessions' => $this->authentication->backend('rememberMe')->getAll($this->acl->getUserId()), - 'last_logins' => $this->lastLogin->getAll($this->acl->getUserId()), ))); } @@ -115,16 +98,4 @@ class Config extends Base $this->session->flash(t('All tokens have been regenerated.')); $this->response->redirect('?controller=config'); } - - /** - * Remove a "RememberMe" token - * - * @access public - */ - public function removeRememberMeToken() - { - $this->checkCSRFParam(); - $this->authentication->backend('rememberMe')->remove($this->request->getIntegerParam('id')); - $this->response->redirect('?controller=config&action=index#remember-me'); - } } diff --git a/app/Controller/Project.php b/app/Controller/Project.php index 0d430b44..ef9eac6b 100644 --- a/app/Controller/Project.php +++ b/app/Controller/Project.php @@ -13,26 +13,52 @@ use Core\Translator; */ class Project extends Base { + /** + * List of projects + * + * @access public + */ + public function index() + { + $projects = $this->project->getAll($this->acl->isRegularUser()); + $nb_projects = count($projects); + $active_projects = array(); + $inactive_projects = array(); - /** - * Clone Project - * - * @author Antonio Rabelo - * @access public - */ - public function duplicate() - { - $this->checkCSRFParam(); - $project_id = $this->request->getIntegerParam('project_id'); + foreach ($projects as $project) { + if ($project['is_active'] == 1) { + $active_projects[] = $project; + } + else { + $inactive_projects[] = $project; + } + } - if ($project_id && $this->project->duplicate($project_id)) { - $this->session->flash(t('Project cloned successfully.')); - } else { - $this->session->flashError(t('Unable to clone this project.')); - } + $this->response->html($this->template->layout('project_index', array( + 'active_projects' => $active_projects, + 'inactive_projects' => $inactive_projects, + 'nb_projects' => $nb_projects, + 'menu' => 'projects', + 'title' => t('Projects').' ('.$nb_projects.')' + ))); + } - $this->response->redirect('?controller=project'); - } + /** + * Show the project information page + * + * @access public + */ + public function show() + { + $project = $this->getProject(); + + $this->response->html($this->projectLayout('project_show', array( + 'project' => $project, + 'stats' => $this->project->getStats($project['id']), + 'menu' => 'projects', + 'title' => $project['name'], + ))); + } /** * Task export @@ -46,13 +72,12 @@ class Project extends Base $to = $this->request->getStringParam('to'); if ($from && $to) { - Translator::disableEscaping(); $data = $this->task->export($project['id'], $from, $to); $this->response->forceDownload('Export_'.date('Y_m_d_H_i_S').'.csv'); $this->response->csv($data); } - $this->response->html($this->template->layout('project_export', array( + $this->response->html($this->projectLayout('project_export', array( 'values' => array( 'controller' => 'project', 'action' => 'export', @@ -68,182 +93,175 @@ class Project extends Base } /** - * Task search for a given project + * Public access management * * @access public */ - public function search() + public function share() { $project = $this->getProject(); - $search = $this->request->getStringParam('search'); - $tasks = array(); - $nb_tasks = 0; - - if ($search !== '') { - $filters = array( - array('column' => 'project_id', 'operator' => 'eq', 'value' => $project['id']), - 'or' => array( - array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'), - //array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'), - ) - ); - - $tasks = $this->task->find($filters); - $nb_tasks = count($tasks); - } - - $this->response->html($this->template->layout('project_search', array( - 'tasks' => $tasks, - 'nb_tasks' => $nb_tasks, - 'values' => array( - 'search' => $search, - 'controller' => 'project', - 'action' => 'search', - 'project_id' => $project['id'], - ), - 'menu' => 'projects', + $this->response->html($this->projectLayout('project_share', array( 'project' => $project, - 'columns' => $this->board->getColumnsList($project['id']), - 'categories' => $this->category->getList($project['id'], false), - 'title' => $project['name'].($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '') + 'menu' => 'projects', + 'title' => t('Public access'), ))); } /** - * List of completed tasks for a given project + * Enable public access for a project * * @access public */ - public function tasks() + public function enablePublic() { - $project = $this->getProject(); - - $filters = array( - array('column' => 'project_id', 'operator' => 'eq', 'value' => $project['id']), - array('column' => 'is_active', 'operator' => 'eq', 'value' => TaskModel::STATUS_CLOSED), - ); + $this->checkCSRFParam(); + $project_id = $this->request->getIntegerParam('project_id'); - $tasks = $this->task->find($filters); - $nb_tasks = count($tasks); + if ($project_id && $this->project->enablePublicAccess($project_id)) { + $this->session->flash(t('Project updated successfully.')); + } else { + $this->session->flashError(t('Unable to update this project.')); + } - $this->response->html($this->template->layout('project_tasks', array( - 'menu' => 'projects', - 'project' => $project, - 'columns' => $this->board->getColumnsList($project['id']), - 'categories' => $this->category->getList($project['id'], false), - 'tasks' => $tasks, - 'nb_tasks' => $nb_tasks, - 'title' => $project['name'].' ('.$nb_tasks.')' - ))); + $this->response->redirect('?controller=project&action=share&project_id='.$project_id); } /** - * List of projects + * Disable public access for a project * * @access public */ - public function index() + public function disablePublic() { - $projects = $this->project->getAll(true, $this->acl->isRegularUser()); - $nb_projects = count($projects); + $this->checkCSRFParam(); + $project_id = $this->request->getIntegerParam('project_id'); - $this->response->html($this->template->layout('project_index', array( - 'projects' => $projects, - 'nb_projects' => $nb_projects, - 'menu' => 'projects', - 'title' => t('Projects').' ('.$nb_projects.')' - ))); + if ($project_id && $this->project->disablePublicAccess($project_id)) { + $this->session->flash(t('Project updated successfully.')); + } else { + $this->session->flashError(t('Unable to update this project.')); + } + + $this->response->redirect('?controller=project&action=share&project_id='.$project_id); } /** - * Display a form to create a new project + * Display a form to edit a project * * @access public */ - public function create() + public function edit() { - $this->response->html($this->template->layout('project_new', array( + $project = $this->getProject(); + + $this->response->html($this->projectLayout('project_edit', array( 'errors' => array(), - 'values' => array(), + 'values' => $project, + 'project' => $project, 'menu' => 'projects', - 'title' => t('New project') + 'title' => t('Edit project') ))); } /** - * Validate and save a new project + * Validate and update a project * * @access public */ - public function save() + public function update() { - $values = $this->request->getValues(); - list($valid, $errors) = $this->project->validateCreation($values); + $project = $this->getProject(); + $values = $this->request->getValues() + array('is_active' => 0); + list($valid, $errors) = $this->project->validateModification($values); if ($valid) { - if ($this->project->create($values)) { - $this->session->flash(t('Your project have been created successfully.')); - $this->response->redirect('?controller=project'); + if ($this->project->update($values)) { + $this->session->flash(t('Project updated successfully.')); + $this->response->redirect('?controller=project&action=edit&project_id='.$project['id']); } else { - $this->session->flashError(t('Unable to create your project.')); + $this->session->flashError(t('Unable to update this project.')); } } - $this->response->html($this->template->layout('project_new', array( + $this->response->html($this->projectLayout('project_edit', array( 'errors' => $errors, 'values' => $values, + 'project' => $project, 'menu' => 'projects', - 'title' => t('New Project') + 'title' => t('Edit Project') ))); } - /** - * Display a form to edit a project + /** + * Users list for the selected project * * @access public */ - public function edit() + public function users() { $project = $this->getProject(); - $this->response->html($this->template->layout('project_edit', array( - 'errors' => array(), - 'values' => $project, + $this->response->html($this->projectLayout('project_users', array( + 'project' => $project, + 'users' => $this->project->getAllUsers($project['id']), 'menu' => 'projects', - 'title' => t('Edit project') + 'title' => t('Edit project access list') ))); } /** - * Validate and update a project + * Allow a specific user for the selected project * * @access public */ - public function update() + public function allow() { - $values = $this->request->getValues() + array('is_active' => 0); - list($valid, $errors) = $this->project->validateModification($values); + $values = $this->request->getValues(); + list($valid,) = $this->project->validateUserAccess($values); if ($valid) { - if ($this->project->update($values)) { + if ($this->project->allowUser($values['project_id'], $values['user_id'])) { $this->session->flash(t('Project updated successfully.')); - $this->response->redirect('?controller=project'); } else { $this->session->flashError(t('Unable to update this project.')); } } - $this->response->html($this->template->layout('project_edit', array( - 'errors' => $errors, - 'values' => $values, - 'menu' => 'projects', - 'title' => t('Edit Project') - ))); + $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']); + } + + /** + * Revoke user access + * + * @access public + */ + public function revoke() + { + $this->checkCSRFParam(); + + $values = array( + 'project_id' => $this->request->getIntegerParam('project_id'), + 'user_id' => $this->request->getIntegerParam('user_id'), + ); + + list($valid,) = $this->project->validateUserAccess($values); + + if ($valid) { + + if ($this->project->revokeUser($values['project_id'], $values['user_id'])) { + $this->session->flash(t('Project updated successfully.')); + } + else { + $this->session->flashError(t('Unable to update this project.')); + } + } + + $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']); } /** @@ -251,11 +269,11 @@ class Project extends Base * * @access public */ - public function confirm() + public function confirmRemove() { $project = $this->getProject(); - $this->response->html($this->template->layout('project_remove', array( + $this->response->html($this->projectLayout('project_remove', array( 'project' => $project, 'menu' => 'projects', 'title' => t('Remove project') @@ -282,25 +300,58 @@ class Project extends Base } /** - * Enable a project + * Confirmation dialog before to clone a project * * @access public */ - public function enable() + public function confirmDuplicate() + { + $project = $this->getProject(); + + $this->response->html($this->projectLayout('project_duplicate', array( + 'project' => $project, + 'menu' => 'projects', + 'title' => t('Clone this project') + ))); + } + + /** + * Duplicate a project + * + * @author Antonio Rabelo + * @access public + */ + public function duplicate() { $this->checkCSRFParam(); $project_id = $this->request->getIntegerParam('project_id'); - if ($project_id && $this->project->enable($project_id)) { - $this->session->flash(t('Project activated successfully.')); + if ($project_id && $this->project->duplicate($project_id)) { + $this->session->flash(t('Project cloned successfully.')); } else { - $this->session->flashError(t('Unable to activate this project.')); + $this->session->flashError(t('Unable to clone this project.')); } $this->response->redirect('?controller=project'); } /** + * Confirmation dialog before to disable a project + * + * @access public + */ + public function confirmDisable() + { + $project = $this->getProject(); + + $this->response->html($this->projectLayout('project_disable', array( + 'project' => $project, + 'menu' => 'projects', + 'title' => t('Project activation') + ))); + } + + /** * Disable a project * * @access public @@ -316,75 +367,194 @@ class Project extends Base $this->session->flashError(t('Unable to disable this project.')); } - $this->response->redirect('?controller=project'); + $this->response->redirect('?controller=project&action=show&project_id='.$project_id); } /** - * Users list for the selected project + * Confirmation dialog before to enable a project * * @access public */ - public function users() + public function confirmEnable() { $project = $this->getProject(); - $this->response->html($this->template->layout('project_users', array( + $this->response->html($this->projectLayout('project_enable', array( 'project' => $project, - 'users' => $this->project->getAllUsers($project['id']), 'menu' => 'projects', - 'title' => t('Edit project access list') + 'title' => t('Project activation') ))); } /** - * Allow a specific user for the selected project + * Enable a project * * @access public */ - public function allow() + public function enable() { - $values = $this->request->getValues(); - list($valid,) = $this->project->validateUserAccess($values); + $this->checkCSRFParam(); + $project_id = $this->request->getIntegerParam('project_id'); - if ($valid) { + if ($project_id && $this->project->enable($project_id)) { + $this->session->flash(t('Project activated successfully.')); + } else { + $this->session->flashError(t('Unable to activate this project.')); + } - if ($this->project->allowUser($values['project_id'], $values['user_id'])) { - $this->session->flash(t('Project updated successfully.')); - } - else { - $this->session->flashError(t('Unable to update this project.')); - } + $this->response->redirect('?controller=project&action=show&project_id='.$project_id); + } + + /** + * RSS feed for a project + * + * @access public + */ + public function feed() + { + $token = $this->request->getStringParam('token'); + $project = $this->project->getByToken($token); + + // Token verification + if (! $project) { + $this->forbidden(true); } - $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']); + $this->response->xml($this->template->load('project_feed', array( + 'events' => $this->project->getActivity($project['id']), + 'project' => $project, + ))); } /** - * Revoke user access + * Activity page for a project * * @access public */ - public function revoke() + public function activity() { - $this->checkCSRFParam(); + $project = $this->getProject(); - $values = array( - 'project_id' => $this->request->getIntegerParam('project_id'), - 'user_id' => $this->request->getIntegerParam('user_id'), + $this->response->html($this->template->layout('project_activity', array( + 'events' => $this->project->getActivity($project['id']), + 'menu' => 'projects', + 'project' => $project, + 'title' => t('%s\'s activity', $project['name']) + ))); + } + + /** + * Task search for a given project + * + * @access public + */ + public function search() + { + $project = $this->getProject(); + $search = $this->request->getStringParam('search'); + $tasks = array(); + $nb_tasks = 0; + + if ($search !== '') { + + $filters = array( + array('column' => 'project_id', 'operator' => 'eq', 'value' => $project['id']), + 'or' => array( + array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'), + //array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'), + ) + ); + + $tasks = $this->task->find($filters); + $nb_tasks = count($tasks); + } + + $this->response->html($this->template->layout('project_search', array( + 'tasks' => $tasks, + 'nb_tasks' => $nb_tasks, + 'values' => array( + 'search' => $search, + 'controller' => 'project', + 'action' => 'search', + 'project_id' => $project['id'], + ), + 'menu' => 'projects', + 'project' => $project, + 'columns' => $this->board->getColumnsList($project['id']), + 'categories' => $this->category->getList($project['id'], false), + 'title' => $project['name'].($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '') + ))); + } + + /** + * List of completed tasks for a given project + * + * @access public + */ + public function tasks() + { + $project = $this->getProject(); + + $filters = array( + array('column' => 'project_id', 'operator' => 'eq', 'value' => $project['id']), + array('column' => 'is_active', 'operator' => 'eq', 'value' => TaskModel::STATUS_CLOSED), ); - list($valid,) = $this->project->validateUserAccess($values); + $tasks = $this->task->find($filters); + $nb_tasks = count($tasks); + + $this->response->html($this->template->layout('project_tasks', array( + 'menu' => 'projects', + 'project' => $project, + 'columns' => $this->board->getColumnsList($project['id']), + 'categories' => $this->category->getList($project['id'], false), + 'tasks' => $tasks, + 'nb_tasks' => $nb_tasks, + 'title' => $project['name'].' ('.$nb_tasks.')' + ))); + } + + /** + * Display a form to create a new project + * + * @access public + */ + public function create() + { + $this->response->html($this->template->layout('project_new', array( + 'errors' => array(), + 'values' => array(), + 'menu' => 'projects', + 'title' => t('New project') + ))); + } + + /** + * Validate and save a new project + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->project->validateCreation($values); if ($valid) { - if ($this->project->revokeUser($values['project_id'], $values['user_id'])) { - $this->session->flash(t('Project updated successfully.')); + if ($this->project->create($values)) { + $this->session->flash(t('Your project have been created successfully.')); + $this->response->redirect('?controller=project'); } else { - $this->session->flashError(t('Unable to update this project.')); + $this->session->flashError(t('Unable to create your project.')); } } - $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']); + $this->response->html($this->template->layout('project_new', array( + 'errors' => $errors, + 'values' => $values, + 'menu' => 'projects', + 'title' => t('New Project') + ))); } } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 7414f7f9..b0bb9d5b 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -30,17 +30,13 @@ class Task extends Base $values = array( 'title' => $this->request->getStringParam('title'), 'description' => $this->request->getStringParam('description'), - 'color_id' => $this->request->getStringParam('color_id', 'blue'), + 'color_id' => $this->request->getStringParam('color_id'), 'project_id' => $this->request->getIntegerParam('project_id', $defaultProject['id']), 'owner_id' => $this->request->getIntegerParam('owner_id'), 'column_id' => $this->request->getIntegerParam('column_id'), 'category_id' => $this->request->getIntegerParam('category_id'), ); - if ($values['column_id'] == 0) { - $values['column_id'] = $this->board->getFirstColumn($values['project_id']); - } - list($valid,) = $this->task->validateCreation($values); if ($valid && $this->task->create($values)) { @@ -51,6 +47,39 @@ class Task extends Base } /** + * Public access (display a task) + * + * @access public + */ + public function readonly() + { + $project = $this->project->getByToken($this->request->getStringParam('token')); + + // Token verification + if (! $project) { + $this->forbidden(true); + } + + $task = $this->task->getById($this->request->getIntegerParam('task_id'), true); + + if (! $task) { + $this->notfound(true); + } + + $this->response->html($this->template->layout('task_public', array( + 'project' => $project, + 'comments' => $this->comment->getAll($task['id']), + 'subtasks' => $this->subTask->getAll($task['id']), + 'task' => $task, + 'columns_list' => $this->board->getColumnsList($task['project_id']), + 'colors_list' => $this->task->getColors(), + 'title' => $task['title'], + 'no_layout' => true, + 'auto_refresh' => true, + ))); + } + + /** * Show a task * * @access public @@ -60,6 +89,7 @@ class Task extends Base $task = $this->getTask(); $this->response->html($this->taskLayout('task_show', array( + 'project' => $this->project->getById($task['project_id']), 'files' => $this->file->getAll($task['id']), 'comments' => $this->comment->getAll($task['id']), 'subtasks' => $this->subTask->getAll($task['id']), @@ -168,7 +198,6 @@ class Task extends Base 'values' => $task, 'errors' => array(), 'task' => $task, - 'columns_list' => $this->board->getColumnsList($task['project_id']), 'users_list' => $this->project->getUsersList($task['project_id']), 'colors_list' => $this->task->getColors(), 'categories_list' => $this->category->getList($task['project_id']), @@ -233,26 +262,20 @@ class Task extends Base */ public function close() { - $this->checkCSRFParam(); $task = $this->getTask(); - if ($this->task->close($task['id'])) { - $this->session->flash(t('Task closed successfully.')); - } else { - $this->session->flashError(t('Unable to close this task.')); - } + if ($this->request->getStringParam('confirmation') === 'yes') { - $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); - } + $this->checkCSRFParam(); - /** - * Confirmation dialog before to close a task - * - * @access public - */ - public function confirmClose() - { - $task = $this->getTask(); + if ($this->task->close($task['id'])) { + $this->session->flash(t('Task closed successfully.')); + } else { + $this->session->flashError(t('Unable to close this task.')); + } + + $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); + } $this->response->html($this->taskLayout('task_close', array( 'task' => $task, @@ -268,26 +291,20 @@ class Task extends Base */ public function open() { - $this->checkCSRFParam(); $task = $this->getTask(); - if ($this->task->open($task['id'])) { - $this->session->flash(t('Task opened successfully.')); - } else { - $this->session->flashError(t('Unable to open this task.')); - } + if ($this->request->getStringParam('confirmation') === 'yes') { - $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); - } + $this->checkCSRFParam(); - /** - * Confirmation dialog before to open a task - * - * @access public - */ - public function confirmOpen() - { - $task = $this->getTask(); + if ($this->task->open($task['id'])) { + $this->session->flash(t('Task opened successfully.')); + } else { + $this->session->flashError(t('Unable to open this task.')); + } + + $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); + } $this->response->html($this->taskLayout('task_open', array( 'task' => $task, @@ -303,26 +320,20 @@ class Task extends Base */ public function remove() { - $this->checkCSRFParam(); $task = $this->getTask(); - if ($this->task->remove($task['id'])) { - $this->session->flash(t('Task removed successfully.')); - } else { - $this->session->flashError(t('Unable to remove this task.')); - } + if ($this->request->getStringParam('confirmation') === 'yes') { - $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); - } + $this->checkCSRFParam(); - /** - * Confirmation dialog before removing a task - * - * @access public - */ - public function confirmRemove() - { - $task = $this->getTask(); + if ($this->task->remove($task['id'])) { + $this->session->flash(t('Task removed successfully.')); + } else { + $this->session->flashError(t('Unable to remove this task.')); + } + + $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + } $this->response->html($this->taskLayout('task_remove', array( 'task' => $task, @@ -332,7 +343,7 @@ class Task extends Base } /** - * Duplicate a task (fill the form for a new task) + * Duplicate a task * * @access public */ @@ -340,26 +351,24 @@ class Task extends Base { $task = $this->getTask(); - if (! empty($task['date_due'])) { - $task['date_due'] = date(t('m/d/Y'), $task['date_due']); - } - else { - $task['date_due'] = ''; - } + if ($this->request->getStringParam('confirmation') === 'yes') { - $task['score'] = $task['score'] ?: ''; + $this->checkCSRFParam(); + $task_id = $this->task->duplicateSameProject($task); - $this->response->html($this->template->layout('task_new', array( - 'errors' => array(), - 'values' => $task, - 'projects_list' => $this->project->getListByStatus(ProjectModel::ACTIVE), - 'columns_list' => $this->board->getColumnsList($task['project_id']), - 'users_list' => $this->project->getUsersList($task['project_id']), - 'colors_list' => $this->task->getColors(), - 'categories_list' => $this->category->getList($task['project_id']), - 'duplicate' => true, + if ($task_id) { + $this->session->flash(t('Task created successfully.')); + $this->response->redirect('?controller=task&action=show&task_id='.$task_id); + } else { + $this->session->flashError(t('Unable to create this task.')); + $this->response->redirect('?controller=task&action=duplicate&task_id='.$task['id']); + } + } + + $this->response->html($this->taskLayout('task_duplicate', array( + 'task' => $task, 'menu' => 'tasks', - 'title' => t('New task') + 'title' => t('Duplicate a task') ))); } @@ -368,19 +377,49 @@ class Task extends Base * * @access public */ - public function editDescription() + public function description() { $task = $this->getTask(); + $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); + + if ($this->request->isPost()) { + + $values = $this->request->getValues(); + + list($valid, $errors) = $this->task->validateDescriptionCreation($values); + + if ($valid) { + + if ($this->task->update($values)) { + $this->session->flash(t('Task updated successfully.')); + } + else { + $this->session->flashError(t('Unable to update your task.')); + } + + if ($ajax) { + $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + } + else { + $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); + } + } + } + else { + $values = $task; + $errors = array(); + } $params = array( - 'values' => $task, - 'errors' => array(), - 'task' => $task, - 'ajax' => $this->request->isAjax(), - 'menu' => 'tasks', - 'title' => t('Edit the description') - ); - if ($this->request->isAjax()) { + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'ajax' => $ajax, + 'menu' => 'tasks', + 'title' => t('Edit the description'), + ); + + if ($ajax) { $this->response->html($this->template->load('task_edit_description', $params)); } else { @@ -389,40 +428,63 @@ class Task extends Base } /** - * Save and validation the description + * Move a task to another project + * + * @access public + */ + public function move() + { + $this->toAnotherProject('move'); + } + + /** + * Duplicate a task to another project * * @access public */ - public function saveDescription() + public function copy() + { + $this->toAnotherProject('duplicate'); + } + + /** + * Common methods between the actions "move" and "copy" + * + * @access private + */ + private function toAnotherProject($action) { $task = $this->getTask(); - $values = $this->request->getValues(); + $values = $task; + $errors = array(); + $projects_list = $this->project->getAvailableList($this->acl->getUserId()); - list($valid, $errors) = $this->task->validateDescriptionCreation($values); + unset($projects_list[$task['project_id']]); - if ($valid) { + if ($this->request->isPost()) { - if ($this->task->update($values)) { - $this->session->flash(t('Task updated successfully.')); - } - else { - $this->session->flashError(t('Unable to update your task.')); - } + $values = $this->request->getValues(); + list($valid, $errors) = $this->task->validateProjectModification($values); - if ($this->request->getIntegerParam('ajax')) { - $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); - } - else { - $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); + if ($valid) { + $task_id = $this->task->{$action.'ToAnotherProject'}($values['project_id'], $task); + if ($task_id) { + $this->session->flash(t('Task created successfully.')); + $this->response->redirect('?controller=task&action=show&task_id='.$task_id); + } + else { + $this->session->flashError(t('Unable to create your task.')); + } } } - $this->response->html($this->taskLayout('task_edit_description', array( + $this->response->html($this->taskLayout('task_'.$action.'_project', array( 'values' => $values, 'errors' => $errors, 'task' => $task, + 'projects_list' => $projects_list, 'menu' => 'tasks', - 'title' => t('Edit the description') + 'title' => t(ucfirst($action).' the task to another project') ))); } } diff --git a/app/Controller/User.php b/app/Controller/User.php index 0bb7aec1..25402f03 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -65,6 +65,48 @@ class User extends Base } /** + * Common layout for project views + * + * @access private + * @param string $template Template name + * @param array $params Template parameters + * @return string + */ + private function layout($template, array $params) + { + $content = $this->template->load($template, $params); + $params['user_content_for_layout'] = $content; + $params['menu'] = 'users'; + + if (isset($params['user'])) { + $params['title'] = $params['user']['name'] ?: $params['user']['username']; + } + + return $this->template->layout('user_layout', $params); + } + + /** + * Common method to get the user + * + * @access private + * @return array + */ + private function getUser() + { + $user = $this->user->getById($this->request->getIntegerParam('user_id'), true); + + if (! $user) { + $this->notfound(); + } + + if ($this->acl->isRegularUser() && $this->acl->getUserId() != $user['id']) { + $this->forbidden(); + } + + return $user; + } + + /** * List all users * * @access public @@ -131,91 +173,180 @@ class User extends Base } /** - * Display a form to edit a user + * Display user information * * @access public */ - public function edit() + public function show() { - $user = $this->user->getById($this->request->getIntegerParam('user_id')); + $user = $this->getUser(); + $this->response->html($this->layout('user_show', array( + 'projects' => $this->project->getAvailableList($user['id']), + 'user' => $user, + ))); + } - if (! $user) $this->notfound(); + /** + * Display last connections + * + * @access public + */ + public function last() + { + $user = $this->getUser(); + $this->response->html($this->layout('user_last', array( + 'last_logins' => $this->lastLogin->getAll($user['id']), + 'user' => $user, + ))); + } - if ($this->acl->isRegularUser() && $this->acl->getUserId() != $user['id']) { - $this->forbidden(); - } + /** + * Display user sessions + * + * @access public + */ + public function sessions() + { + $user = $this->getUser(); + $this->response->html($this->layout('user_sessions', array( + 'sessions' => $this->authentication->backend('rememberMe')->getAll($user['id']), + 'user' => $user, + ))); + } - unset($user['password']); + /** + * Remove a "RememberMe" token + * + * @access public + */ + public function removeSession() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + $this->authentication->backend('rememberMe')->remove($this->request->getIntegerParam('id')); + $this->response->redirect('?controller=user&action=sessions&user_id='.$user['id']); + } - $this->response->html($this->template->layout('user_edit', array( - 'projects' => $this->project->filterListByAccess($this->project->getList(), $user['id']), - 'errors' => array(), - 'values' => $user, - 'menu' => 'users', - 'title' => t('Edit user') + /** + * Display user notifications + * + * @access public + */ + public function notifications() + { + $user = $this->getUser(); + + if ($this->request->isPost()) { + $values = $this->request->getValues(); + $this->notification->saveSettings($user['id'], $values); + $this->session->flash(t('User updated successfully.')); + $this->response->redirect('?controller=user&action=notifications&user_id='.$user['id']); + } + + $this->response->html($this->layout('user_notifications', array( + 'projects' => $this->project->getAvailableList($user['id']), + 'notifications' => $this->notification->readSettings($user['id']), + 'user' => $user, ))); } /** - * Validate and update a user + * Display external accounts * * @access public */ - public function update() + public function external() { - $values = $this->request->getValues(); + $user = $this->getUser(); + $this->response->html($this->layout('user_external', array( + 'last_logins' => $this->lastLogin->getAll($user['id']), + 'user' => $user, + ))); + } - if ($this->acl->isAdminUser()) { - $values += array('is_admin' => 0); - } - else { + /** + * Password modification + * + * @access public + */ + public function password() + { + $user = $this->getUser(); + $values = array('id' => $user['id']); + $errors = array(); - if ($this->acl->getUserId() != $values['id']) { - $this->forbidden(); - } + if ($this->request->isPost()) { - if (isset($values['is_admin'])) { - unset($values['is_admin']); // Regular users can't be admin - } - } + $values = $this->request->getValues(); + list($valid, $errors) = $this->user->validatePasswordModification($values); - list($valid, $errors) = $this->user->validateModification($values); + if ($valid) { - if ($valid) { + if ($this->user->update($values)) { + $this->session->flash(t('Password modified successfully.')); + } + else { + $this->session->flashError(t('Unable to change the password.')); + } - if ($this->user->update($values)) { - $this->session->flash(t('User updated successfully.')); - $this->response->redirect('?controller=user'); - } - else { - $this->session->flashError(t('Unable to update your user.')); + $this->response->redirect('?controller=user&action=show&user_id='.$user['id']); } } - $this->response->html($this->template->layout('user_edit', array( - 'projects' => $this->project->filterListByAccess($this->project->getList(), $values['id']), - 'errors' => $errors, + $this->response->html($this->layout('user_password', array( 'values' => $values, - 'menu' => 'users', - 'title' => t('Edit user') + 'errors' => $errors, + 'user' => $user, ))); } /** - * Confirmation dialog before to remove a user + * Display a form to edit a user * * @access public */ - public function confirm() + public function edit() { - $user = $this->user->getById($this->request->getIntegerParam('user_id')); + $user = $this->getUser(); + $values = $user; + $errors = array(); + + unset($values['password']); + + if ($this->request->isPost()) { + + $values = $this->request->getValues(); + + if ($this->acl->isAdminUser()) { + $values += array('is_admin' => 0); + } + else { + + if (isset($values['is_admin'])) { + unset($values['is_admin']); // Regular users can't be admin + } + } + + list($valid, $errors) = $this->user->validateModification($values); + + if ($valid) { + + if ($this->user->update($values)) { + $this->session->flash(t('User updated successfully.')); + } + else { + $this->session->flashError(t('Unable to update your user.')); + } - if (! $user) $this->notfound(); + $this->response->redirect('?controller=user&action=show&user_id='.$user['id']); + } + } - $this->response->html($this->template->layout('user_remove', array( + $this->response->html($this->layout('user_edit', array( + 'values' => $values, + 'errors' => $errors, + 'projects' => $this->project->filterListByAccess($this->project->getList(), $user['id']), 'user' => $user, - 'menu' => 'users', - 'title' => t('Remove user') ))); } @@ -226,16 +357,24 @@ class User extends Base */ public function remove() { - $this->checkCSRFParam(); - $user_id = $this->request->getIntegerParam('user_id'); + $user = $this->getUser(); + + if ($this->request->getStringParam('confirmation') === 'yes') { - if ($user_id && $this->user->remove($user_id)) { - $this->session->flash(t('User removed successfully.')); - } else { - $this->session->flashError(t('Unable to remove this user.')); + $this->checkCSRFParam(); + + if ($this->user->remove($user['id'])) { + $this->session->flash(t('User removed successfully.')); + } else { + $this->session->flashError(t('Unable to remove this user.')); + } + + $this->response->redirect('?controller=user'); } - $this->response->redirect('?controller=user'); + $this->response->html($this->layout('user_remove', array( + 'user' => $user, + ))); } /** @@ -263,7 +402,7 @@ class User extends Base $this->session->flashError(t('Unable to link your Google Account.')); } - $this->response->redirect('?controller=user'); + $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId()); } else if ($this->authentication->backend('google')->authenticate($profile['id'])) { $this->response->redirect('?controller=app'); @@ -297,7 +436,7 @@ class User extends Base $this->session->flashError(t('Unable to unlink your Google Account.')); } - $this->response->redirect('?controller=user'); + $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId()); } /** @@ -324,7 +463,7 @@ class User extends Base $this->session->flashError(t('Unable to link your GitHub Account.')); } - $this->response->redirect('?controller=user'); + $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId()); } else if ($this->authentication->backend('gitHub')->authenticate($profile['id'])) { $this->response->redirect('?controller=app'); @@ -361,6 +500,6 @@ class User extends Base $this->session->flashError(t('Unable to unlink your GitHub Account.')); } - $this->response->redirect('?controller=user'); + $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId()); } } diff --git a/app/Core/Response.php b/app/Core/Response.php index 1ccf9f5e..347cdde7 100644 --- a/app/Core/Response.php +++ b/app/Core/Response.php @@ -246,7 +246,7 @@ class Response */ public function hsts() { - if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') { + if (Tool::isHTTPS()) { header('Strict-Transport-Security: max-age=31536000'); } } diff --git a/app/Core/Session.php b/app/Core/Session.php index f072350d..c824ba64 100644 --- a/app/Core/Session.php +++ b/app/Core/Session.php @@ -13,9 +13,11 @@ class Session /** * Sesion lifetime * + * http://php.net/manual/en/session.configuration.php#ini.session.cookie-lifetime + * * @var integer */ - const SESSION_LIFETIME = 7200; // 2 hours + const SESSION_LIFETIME = 0; // Until the browser is closed /** * Open a session @@ -35,7 +37,7 @@ class Session self::SESSION_LIFETIME, $base_path ?: '/', null, - ! empty($_SERVER['HTTPS']), + Tool::isHTTPS(), true ); diff --git a/app/Core/Tool.php b/app/Core/Tool.php index 85b684e2..e54a0d3b 100644 --- a/app/Core/Tool.php +++ b/app/Core/Tool.php @@ -32,6 +32,15 @@ class Tool } } + /** + * Load and register a model + * + * @static + * @access public + * @param Core\Registry $registry DPI container + * @param string $name Model name + * @return mixed + */ public static function loadModel(Registry $registry, $name) { if (! isset($registry->$name)) { @@ -41,4 +50,18 @@ class Tool return $registry->shared($name); } + + /** + * Check if the page is requested through HTTPS + * + * Note: IIS return the value 'off' and other web servers an empty value when it's not HTTPS + * + * @static + * @access public + * @return boolean + */ + public static function isHTTPS() + { + return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== '' && $_SERVER['HTTPS'] !== 'off'; + } } diff --git a/app/Core/Translator.php b/app/Core/Translator.php index c34a40ba..43e934a9 100644 --- a/app/Core/Translator.php +++ b/app/Core/Translator.php @@ -27,58 +27,47 @@ class Translator private static $locales = array(); /** - * Flag to enable HTML escaping + * Get a translation * - * @static - * @access private - * @var boolean - */ - private static $enable_escaping = true; - - /** - * Disable HTML escaping for translations + * $translator->translate('I have %d kids', 5); * - * @static * @access public + * @param string $identifier Default string + * @return string */ - public static function disableEscaping() + public function translate($identifier) { - self::$enable_escaping = false; - } + $args = func_get_args(); - /** - * Enable HTML escaping for translations - * - * @static - * @access public - */ - public static function enableEscaping() - { - self::$enable_escaping = true; + array_shift($args); + array_unshift($args, $this->get($identifier, $identifier)); + + foreach ($args as &$arg) { + $arg = htmlspecialchars($arg, ENT_QUOTES, 'UTF-8', false); + } + + return call_user_func_array( + 'sprintf', + $args + ); } /** - * Get a translation + * Get a translation with no HTML escaping * - * $translator->translate('I have %d kids', 5); + * $translator->translateNoEscaping('I have %d kids', 5); * * @access public - * @param $identifier + * @param string $identifier Default string * @return string */ - public function translate($identifier) + public function translateNoEscaping($identifier) { $args = func_get_args(); array_shift($args); array_unshift($args, $this->get($identifier, $identifier)); - if (self::$enable_escaping) { - foreach ($args as &$arg) { - $arg = htmlspecialchars($arg, ENT_QUOTES, 'UTF-8', false); - } - } - return call_user_func_array( 'sprintf', $args diff --git a/app/Event/CommentHistoryListener.php b/app/Event/CommentHistoryListener.php new file mode 100644 index 00000000..a89ecbae --- /dev/null +++ b/app/Event/CommentHistoryListener.php @@ -0,0 +1,62 @@ +<?php + +namespace Event; + +use Core\Listener; +use Model\CommentHistory; + +/** + * Comment history listener + * + * @package event + * @author Frederic Guillot + */ +class CommentHistoryListener implements Listener +{ + /** + * Comment History model + * + * @accesss private + * @var \Model\CommentHistory + */ + private $model; + + /** + * Constructor + * + * @access public + * @param \Model\CommentHistory $model Comment History model instance + */ + public function __construct(CommentHistory $model) + { + $this->model = $model; + } + + /** + * Execute the action + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function execute(array $data) + { + $creator_id = $this->model->acl->getUserId(); + + if ($creator_id && isset($data['task_id']) && isset($data['id'])) { + + $task = $this->model->task->getById($data['task_id']); + + $this->model->create( + $task['project_id'], + $data['task_id'], + $data['id'], + $creator_id, + $this->model->event->getLastTriggeredEvent(), + $data['comment'] + ); + } + + return false; + } +} diff --git a/app/Event/SubtaskHistoryListener.php b/app/Event/SubtaskHistoryListener.php new file mode 100644 index 00000000..e859828c --- /dev/null +++ b/app/Event/SubtaskHistoryListener.php @@ -0,0 +1,62 @@ +<?php + +namespace Event; + +use Core\Listener; +use Model\SubtaskHistory; + +/** + * Subtask history listener + * + * @package event + * @author Frederic Guillot + */ +class SubtaskHistoryListener implements Listener +{ + /** + * Comment History model + * + * @accesss private + * @var \Model\SubtaskHistory + */ + private $model; + + /** + * Constructor + * + * @access public + * @param \Model\SubtaskHistory $model Subtask History model instance + */ + public function __construct(SubtaskHistory $model) + { + $this->model = $model; + } + + /** + * Execute the action + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function execute(array $data) + { + $creator_id = $this->model->acl->getUserId(); + + if ($creator_id && isset($data['task_id']) && isset($data['id'])) { + + $task = $this->model->task->getById($data['task_id']); + + $this->model->create( + $task['project_id'], + $data['task_id'], + $data['id'], + $creator_id, + $this->model->event->getLastTriggeredEvent(), + '' + ); + } + + return false; + } +} diff --git a/app/Event/TaskHistoryListener.php b/app/Event/TaskHistoryListener.php new file mode 100644 index 00000000..c8a880e8 --- /dev/null +++ b/app/Event/TaskHistoryListener.php @@ -0,0 +1,52 @@ +<?php + +namespace Event; + +use Core\Listener; +use Model\TaskHistory; + +/** + * Task history listener + * + * @package event + * @author Frederic Guillot + */ +class TaskHistoryListener implements Listener +{ + /** + * Task History model + * + * @accesss private + * @var \Model\TaskHistory + */ + private $model; + + /** + * Constructor + * + * @access public + * @param \Model\TaskHistory $model Task History model instance + */ + public function __construct(TaskHistory $model) + { + $this->model = $model; + } + + /** + * Execute the action + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function execute(array $data) + { + $creator_id = $this->model->acl->getUserId(); + + if ($creator_id && isset($data['task_id']) && isset($data['project_id'])) { + $this->model->create($data['project_id'], $data['task_id'], $creator_id, $this->model->event->getLastTriggeredEvent()); + } + + return false; + } +} diff --git a/app/Locales/de_DE/translations.php b/app/Locales/de_DE/translations.php index 48f365ff..fc36b235 100644 --- a/app/Locales/de_DE/translations.php +++ b/app/Locales/de_DE/translations.php @@ -29,7 +29,7 @@ return array( 'All users' => 'Alle Benutzer', 'Username' => 'Benutzername', 'Password' => 'Passwort', - 'Default Project' => 'Standardprojekt', + 'Default project' => 'Standardprojekt', 'Administrator' => 'Administrator', 'Sign in' => 'Anmelden', 'Users' => 'Benutzer', @@ -129,7 +129,7 @@ return array( 'The id is required' => 'Die ID ist anzugeben', 'The project id is required' => 'Die Projekt ID ist anzugeben', 'The project name is required' => 'Der Projektname ist anzugeben', - 'This project must be unique' => 'Der Projektname muss eindeutig sein', + 'This project must be unique' => 'Der Projektname muss eindeutig sein', 'The title is required' => 'Der Titel ist anzugeben', 'The language is required' => 'Die Sprache ist anzugeben', 'There is no active project, the first step is to create a new project.' => 'Es gibt kein aktives Projekt. Zunächst muss ein Projekt erstellt werden.', @@ -176,22 +176,21 @@ return array( 'List of projects' => 'Liste der Projekte', 'Completed tasks for "%s"' => 'Abgeschlossene Aufgaben für "%s"', '%d closed tasks' => '%d abgeschlossene Aufgaben', - 'no task for this project' => 'Keine Aufgaben in diesem Projekt', - 'Public link' => 'Öffentlicher Link', + 'No task for this project' => 'Keine Aufgaben in diesem Projekt', + 'Public link' => 'Öffentlicher Link', 'There is no column in your project!' => 'Es gibt keine Spalte in deinem Projekt!', 'Change assignee' => 'Zuständigkeit ändern', 'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: "%s"', 'Timezone' => 'Zeitzone', 'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!', 'Page not found' => 'Seite nicht gefunden', - 'Story Points' => 'Aufwand (Story Points)', + // 'Complexity' => '', 'limit' => 'Limit', 'Task limit' => 'Maximale Anzahl von Aufgaben', 'This value must be greater than %d' => 'Dieser Wert muss größer sein als %d', 'Edit project access list' => 'Zugriffsberechtigungen des Projektes bearbeiten', 'Edit users access' => 'Benutzerzugriff ändern', 'Allow this user' => 'Diesen Benutzer autorisieren', - 'Project access list for "%s"' => 'Zugriffsliste für Projekt "%s"', 'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugang zum Projekt:', 'Don\'t forget that administrators have access to everything.' => 'Nicht vergessen: Administratoren haben überall Zugang.', 'revoke' => 'entfernen', @@ -210,8 +209,8 @@ return array( 'The description is required' => 'Eine Beschreibung wird benötigt', 'Edit this task' => 'Aufgabe bearbeiten', 'Due Date' => 'Fällig am', - 'm/d/Y' => 'd.m.Y', // Date format parsed with php - 'month/day/year' => 'TT.MM.JJJJ', // Help shown to the user + 'm/d/Y' => 'd.m.Y', + 'month/day/year' => 'TT.MM.JJJJ', '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', @@ -274,7 +273,7 @@ return array( 'IP address' => 'IP Adresse', 'User agent' => 'User Agent', 'Persistent connections' => 'Bestehende Verbindungen', - 'No session' => 'Keine Sitzung', + 'No session.' => 'Keine Sitzung.', 'Expiration date' => 'Ablaufdatum', 'Remember Me' => 'Angemeldet bleiben', 'Creation date' => 'Erstellungsdatum', @@ -360,7 +359,6 @@ return array( 'The time must be a numeric value' => 'Zeit nur als nummerische Angabe', 'Todo' => 'Nicht gestartet', 'In progress' => 'In Bearbeitung', - 'Done' => 'Erledigt', 'Sub-task removed successfully.' => 'Unteraufgabe erfolgreich gelöscht.', 'Unable to remove this sub-task.' => 'Löschen der Unteraufgabe nicht möglich.', 'Sub-task updated successfully.' => 'Unteraufgabe erfolgreich aktualisiert.', @@ -377,7 +375,7 @@ return array( 'Unable to unlink your GitHub Account.' => 'Trennung der Verbindung zum GitHub Account nicht möglich.', 'Login with my GitHub Account' => 'Anmelden mit meinem GitHub Account', 'Link my GitHub Account' => 'Mit meinem GitHub Account verbinden', - 'Unlink my GitHub Account' => 'Verbindung mit meinem GitHub Account trennen', + 'Unlink my GitHub Account' => 'Verbindung mit meinem GitHub Account trennen', 'Created by %s' => 'Erstellt durch %s', 'Last modified on %B %e, %Y at %k:%M %p' => 'Letzte Änderung am %d.%m.%Y um %H:%M', 'Tasks Export' => 'Aufgaben exportieren', @@ -423,4 +421,77 @@ return array( // '[Kanboard] Notification' => '', // 'I want to receive notifications only for those projects:' => '', // 'view the task on Kanboard' => '', + // 'Public access' => '', + // 'Categories management' => '', + // 'Users management' => '', + // 'Active tasks' => '', + // 'Disable public access' => '', + // 'Enable public access' => '', + // 'Active projects' => '', + // 'Inactive projects' => '', + // 'Public access disabled' => '', + // 'Do you really want to disable this project: "%s"?' => '', + // 'Do you really want to duplicate this project: "%s"?' => '', + // 'Do you really want to enable this project: "%s"?' => '', + // 'Project activation' => '', + 'Move the task to another project' => 'Aufgabe in ein anderes Projekt verschieben', + 'Move to another project' => 'In anderes Projekt verschieben', + // 'Do you really want to duplicate this task?' => '', + // 'Duplicate a task' => '', + // 'External accounts' => '', + // 'Account type' => '', + // 'Local' => '', + // 'Remote' => '', + // 'Enabled' => '', + // 'Disabled' => '', + // 'Google account linked' => '', + // 'Github account linked' => '', + // 'Username:' => '', + // 'Name:' => '', + // 'Email:' => '', + // 'Default project:' => '', + // 'Notifications:' => '', + // 'Group:' => '', + // 'Regular user' => '', + // 'Account type:' => '', + // 'Edit profile' => '', + // 'Change password' => '', + // 'Password modification' => '', + // 'External authentications' => '', + // 'Google Account' => '', + // 'Github Account' => '', + // 'Never connected.' => '', + // 'No account linked.' => '', + // 'Account linked.' => '', + // 'No external authentication enabled.' => '', + // 'Password modified successfully.' => '', + // 'Unable to change the password.' => '', + // 'Change category for the task "%s"' => '', + // 'Change category' => '', + // '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '', + // '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // 'Assigned to %s with an estimate of %s/%sh' => '', + // 'Not assigned, estimate of %sh' => '', + // '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s\'s activity' => '', + // 'No activity.' => '', + // 'RSS feed' => '', + // '%s updated a comment on the task #%d' => '', + // '%s commented on the task #%d' => '', + // '%s updated a subtask for the task #%d' => '', + // '%s created a subtask for the task #%d' => '', + // '%s updated the task #%d' => '', + // '%s created the task #%d' => '', + // '%s closed the task #%d' => '', + // '%s open the task #%d' => '', + // '%s moved the task #%d to the column "%s"' => '', + // '%s moved the task #%d to the position %d in the column "%s"' => '', + // 'Activity' => '', ); diff --git a/app/Locales/es_ES/translations.php b/app/Locales/es_ES/translations.php index 4e86b766..2cb19e1b 100644 --- a/app/Locales/es_ES/translations.php +++ b/app/Locales/es_ES/translations.php @@ -29,7 +29,7 @@ return array( 'All users' => 'Todos los usuarios', 'Username' => 'Nombre de usuario', 'Password' => 'Contraseña', - 'Default Project' => 'Proyecto por defecto', + 'Default project' => 'Proyecto por defecto', 'Administrator' => 'Administrador', 'Sign in' => 'Iniciar sesión', 'Users' => 'Usuarios', @@ -51,6 +51,7 @@ return array( 'Status' => 'Estado', 'Tasks' => 'Tareas', 'Board' => 'Tablero', + 'Actions' => 'Acciones', 'Inactive' => 'Inactivo', 'Active' => 'Activo', 'Column %d' => 'Columna %d', @@ -82,7 +83,8 @@ return array( 'Settings' => 'Preferencias', 'Application settings' => 'Parámetros de la aplicación', 'Language' => 'Idioma', - 'Webhooks token:' => 'Identificador (token) para los webhooks :', + 'Webhooks token:' => 'Ficha de seguridad (token) para los webhooks :', + 'API token:' => 'Ficha de seguridad (token) para API:', 'More information' => 'Más informaciones', 'Database size:' => 'Tamaño de la base de datos:', 'Download the database' => 'Descargar la base de datos', @@ -119,7 +121,7 @@ return array( 'The username must be unique' => 'El nombre de usuario debe ser único', 'The username must be alphanumeric' => 'El nombre de usuario debe ser alfanumérico', 'The user id is required' => 'El identificador del usuario es obligatorio', - 'Passwords doesn\'t matches' => 'Las contraseñas no corresponden', + 'Passwords don\'t match' => 'Las contraseñas no coinciden', 'The confirmation is required' => 'La confirmación es obligatoria', 'The column is required' => 'La columna es obligatoria', 'The project is required' => 'El proyecto es obligatorio', @@ -162,7 +164,7 @@ return array( 'Ready' => 'Listo', 'Backlog' => 'En espera', 'Work in progress' => 'En curso', - 'Done' => 'Terminado', + 'Done' => 'Hecho', 'Application version:' => 'Versión de la aplicación:', 'Completed on %B %e, %Y at %k:%M %p' => 'Completado el %d/%m/%Y a las %H:%M', '%B %e, %Y at %k:%M %p' => '%d/%m/%Y a las %H:%M', @@ -174,7 +176,7 @@ return array( 'List of projects' => 'Lista de los proyectos', 'Completed tasks for "%s"' => 'Tarea completada por « %s »', '%d closed tasks' => '%d tareas completadas', - 'no task for this project' => 'ninguna tarea para este proyecto', + 'No task for this project' => 'Ninguna tarea para este proyecto', 'Public link' => 'Enlace público', 'There is no column in your project!' => '¡No hay ninguna columna para este proyecto!', 'Change assignee' => 'Cambiar la persona asignada', @@ -189,7 +191,6 @@ return array( 'Edit project access list' => 'Editar los permisos del proyecto', 'Edit users access' => 'Editar los permisos de usuario', 'Allow this user' => 'Autorizar este usuario', - 'Project access list for "%s"' => 'Permisos del proyecto « %s »', 'Only those users have access to this project:' => 'Solo estos usuarios tienen acceso a este proyecto:', 'Don\'t forget that administrators have access to everything.' => 'No olvide que los administradores tienen acceso a todo.', 'revoke' => 'revocar', @@ -208,8 +209,8 @@ return array( 'The description is required' => 'La descripción es obligatoria', 'Edit this task' => 'Editar esta tarea', 'Due Date' => 'Fecha límite', - 'm/d/Y' => 'd/m/Y', // Date format parsed with php - 'month/day/year' => 'día/mes/año', // Help shown to the user + 'm/d/Y' => 'd/m/Y', + 'month/day/year' => 'día/mes/año', 'Invalid date' => 'Fecha no válida', 'Must be done before %B %e, %Y' => 'Debe de estar hecho antes del %d/%m/%Y', '%B %e, %Y' => '%d/%m/%Y', @@ -221,6 +222,7 @@ return array( 'Action removed successfully.' => 'La acción ha sido borrada correctamente.', 'Automatic actions for the project "%s"' => 'Acciones automatizadas para este proyecto « %s »', 'Defined actions' => 'Acciones definidas', + 'Add an action' => 'Agregar una acción', 'Event name' => 'Nombre del evento', 'Action name' => 'Nombre de la acción', 'Action parameters' => 'Parámetros de la acción', @@ -271,7 +273,7 @@ return array( 'IP address' => 'Dirección IP', 'User agent' => 'Agente de usuario', 'Persistent connections' => 'Conexión persistente', - 'No session' => 'No existe sesión', + 'No session.' => 'No existe sesión.', 'Expiration date' => 'Fecha de expiración', 'Remember Me' => 'Recuérdame', 'Creation date' => 'Fecha de creación', @@ -357,7 +359,6 @@ return array( 'The time must be a numeric value' => 'El tiempo debe de ser un valor numérico', 'Todo' => 'Por hacer', 'In progress' => 'En progreso', - 'Done' => 'Hecho', 'Sub-task removed successfully.' => 'Sub-tarea suprimida correctamente.', 'Unable to remove this sub-task.' => 'No pude suprimir esta sub-tarea.', 'Sub-task updated successfully.' => 'Sub-tarea actualizada correctamente.', @@ -366,59 +367,131 @@ return array( 'Sub-task added successfully.' => 'Sub-tarea añadida correctamente.', 'Maximum size: ' => 'Tamaño máximo', 'Unable to upload the file.' => 'No pude cargar el fichero.', - 'Actions' => 'Acciones', - // 'Display another project' => '', - // 'Your GitHub account was successfully linked to your profile.' => '', - // 'Unable to link your GitHub Account.' => '', - // 'GitHub authentication failed' => '', - // 'Your GitHub account is no longer linked to your profile.' => '', - // 'Unable to unlink your GitHub Account.' => '', - // 'Login with my GitHub Account' => '', - // 'Link my GitHub Account' => '', - // 'Unlink my GitHub Account' => '', - // 'Created by %s' => 'Créé par %s', - // 'Last modified on %B %e, %Y at %k:%M %p' => '', - // 'Tasks Export' => '', - // 'Tasks exportation for "%s"' => '', - // 'Start Date' => '', - // 'End Date' => '', - // 'Execute' => '', - // 'Task Id' => '', - // 'Creator' => '', - // 'Modification date' => '', - // 'Completion date' => '', - // 'Webhook URL for task creation' => '', - // 'Webhook URL for task modification' => '', - // 'Clone' => '', - // 'Clone Project' => '', - // 'Project cloned successfully.' => '', - // 'Unable to clone this project.' => '', - // 'Email notifications' => '', - // 'Enable email notifications' => '', - // 'Task position:' => '', - // 'The task #%d have been opened.' => '', - // 'The task #%d have been closed.' => '', - // 'Sub-task updated' => '', - // 'Title:' => '', - // 'Status:' => '', - // 'Assignee:' => '', - // 'Time tracking:' => '', - // 'New sub-task' => '', - // 'New attachment added "%s"' => '', - // 'Comment updated' => '', - // 'New comment posted by %s' => '', - // 'List of due tasks for the project "%s"' => '', - // '[%s][New attachment] %s (#%d)' => '', - // '[%s][New comment] %s (#%d)' => '', - // '[%s][Comment updated] %s (#%d)' => '', - // '[%s][New subtask] %s (#%d)' => '', - // '[%s][Subtask updated] %s (#%d)' => '', - // '[%s][New task] %s (#%d)' => '', - // '[%s][Task updated] %s (#%d)' => '', - // '[%s][Task closed] %s (#%d)' => '', - // '[%s][Task opened] %s (#%d)' => '', - // '[%s][Due tasks]' => '', - // '[Kanboard] Notification' => '', - // 'I want to receive notifications only for those projects:' => '', - // 'view the task on Kanboard' => '', + 'Display another project' => 'Mostrar otro proyecto', + 'Your GitHub account was successfully linked to your profile.' => 'Tu cuenta de GitHub ha sido correctamente vinculada a tu perfil', + 'Unable to link your GitHub Account.' => 'Imposible vincular tu cuenta de GitHub', + 'GitHub authentication failed' => 'Falló la autenticación de GitHub', + 'Your GitHub account is no longer linked to your profile.' => 'Tu cuenta de GitHub ya no está vinculada a tu perfil', + 'Unable to unlink your GitHub Account.' => 'Imposible desvincular tu cuenta de GitHub', + 'Login with my GitHub Account' => 'Ingresar con mi cuenta de GitHub', + 'Link my GitHub Account' => 'Vincular mi cuenta de GitHub', + 'Unlink my GitHub Account' => 'Desvincular mi cuenta de GitHub', + 'Created by %s' => 'Creado por %s', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Última modificación %B %e, %Y a las %k:%M %p', + 'Tasks Export' => 'Exportar tareas', + 'Tasks exportation for "%s"' => 'Exportación de tareas para "%s"', + 'Start Date' => 'Fecha de inicio', + 'End Date' => 'Fecha final', + 'Execute' => 'Ejecutar', + 'Task Id' => 'ID de tarea', + 'Creator' => 'Creador', + 'Modification date' => 'Fecha de modificación', + 'Completion date' => 'Fecha de terminación', + 'Webhook URL for task creation' => 'Webhook para la creación de tareas', + 'Webhook URL for task modification' => 'Webhokk para la modificación de tareas', + 'Clone' => 'Clonar', + 'Clone Project' => 'Clonar proyecto', + 'Project cloned successfully.' => 'Proyecto clonado correctamente', + 'Unable to clone this project.' => 'Impsible clonar proyecto', + 'Email notifications' => 'Notificaciones correo electrónico', + 'Enable email notifications' => 'Habilitar notificaciones por correo electrónico', + 'Task position:' => 'Posición de la tarea', + 'The task #%d have been opened.' => 'La tarea #%d ha sido abierta', + 'The task #%d have been closed.' => 'La tarea #%d ha sido cerrada', + 'Sub-task updated' => 'Subtarea actualizada', + 'Title:' => 'Título:', + 'Status:' => 'Estado:', + 'Assignee:' => 'Asignada a:', + 'Time tracking:' => 'Control de tiempo:', + 'New sub-task' => 'Nueva subtarea', + 'New attachment added "%s"' => 'Nuevo adjunto agregado "%s"', + 'Comment updated' => 'Comentario actualizado', + 'New comment posted by %s' => 'Nuevo comentario agregado por %s', + 'List of due tasks for the project "%s"' => 'Lista de tareas para el proyecto "%s"', + '[%s][New attachment] %s (#%d)' => '[%s][uevo adjunto] %s (#%d)', + '[%s][New comment] %s (#%d)' => '[%s][Nuevo comentario] %s (#%d)', + '[%s][Comment updated] %s (#%d)' => '[%s][Comentario actualizado] %s (#%d)', + '[%s][New subtask] %s (#%d)' => '[%s][Nueva subtarea] %s (#%d)', + '[%s][Subtask updated] %s (#%d)' => '[%s][Subtarea actualizada] %s (#%d)', + '[%s][New task] %s (#%d)' => '[%s][Nueva tarea] %s (#%d)', + '[%s][Task updated] %s (#%d)' => '[%s][Tarea actualizada] %s (#%d)', + '[%s][Task closed] %s (#%d)' => '[%s][Tarea cerrada] %s (#%d)', + '[%s][Task opened] %s (#%d)' => '[%s][Tarea abierta] %s (#%d)', + '[%s][Due tasks]' => '[%s][Tareas vencidas]', + '[Kanboard] Notification' => '[Kanboard] Notificación', + 'I want to receive notifications only for those projects:' => 'Quiero recibir notificaciones sólo de estos proyectos:', + 'view the task on Kanboard' => 'ver la tarea en Kanboard', + 'Public access' => 'Acceso público', + 'Categories management' => 'Gestión de Categorías', + 'Users management' => 'Gestión de Usuarios', + 'Active tasks' => 'Tareas activas', + 'Disable public access' => 'Desactivar acceso público', + 'Enable public access' => 'Activar acceso público', + 'Active projects' => 'Proyectos activos', + 'Inactive projects' => 'Proyectos inactivos', + 'Public access disabled' => 'Acceso público desactivado', + 'Do you really want to disable this project: "%s"?' => '¿Realmente deseas desactivar este proyecto: "%s"?', + 'Do you really want to duplicate this project: "%s"?' => '¿Realmente deseas duplicar este proyecto: "%s"?', + 'Do you really want to enable this project: "%s"?' => '¿Realmente deseas activar este proyecto: "%s"?', + 'Project activation' => 'Activación de Proyecto', + 'Move the task to another project' => 'Mover la tarea a otro proyecto', + 'Move to another project' => 'Mover a otro proyecto', + 'Do you really want to duplicate this task?' => '¿Realmente deseas duplicar esta tarea?', + 'Duplicate a task' => 'Duplicar una tarea', + 'External accounts' => 'Cuentas externas', + 'Account type' => 'Tipo de Cuenta', + 'Local' => 'Local', + 'Remote' => 'Remota', + 'Enabled' => 'Activada', + 'Disabled' => 'Deactivada', + '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:', + 'Default project:' => 'Proyecto por defecto:', + 'Notifications:' => 'Notificaciones:', + 'Group:' => 'Grupo:', + 'Regular user' => 'Usuario regular:', + 'Account type:' => 'Tipo de Cuenta:', + 'Edit profile' => 'Editar perfil', + 'Change password' => 'Cambiar contraseña', + 'Password modification' => 'Modificacion de contraseña', + 'External authentications' => 'Autenticación externa', + 'Google Account' => 'Cuenta de Google', + 'Github Account' => 'Cuenta de Github', + 'Never connected.' => 'Nunca se ha conectado.', + 'No account linked.' => 'Sin enlaces con cuentas.', + 'Account linked.' => 'Vinculada con Cuenta.', + 'No external authentication enabled.' => 'Sin autenticación externa activa.', + 'Password modified successfully.' => 'Contraseña cambiada correctamente.', + 'Unable to change the password.' => 'No pude cambiar la contraseña.', + 'Change category for the task "%s"' => 'Cambiar la categoría de la tarea "%s"', + 'Change category' => 'Cambiar categoría', + // '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '', + // '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // 'Assigned to %s with an estimate of %s/%sh' => '', + // 'Not assigned, estimate of %sh' => '', + // '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s\'s activity' => '', + // 'No activity.' => '', + // 'RSS feed' => '', + // '%s updated a comment on the task #%d' => '', + // '%s commented on the task #%d' => '', + // '%s updated a subtask for the task #%d' => '', + // '%s created a subtask for the task #%d' => '', + // '%s updated the task #%d' => '', + // '%s created the task #%d' => '', + // '%s closed the task #%d' => '', + // '%s open the task #%d' => '', + // '%s moved the task #%d to the column "%s"' => '', + // '%s moved the task #%d to the position %d in the column "%s"' => '', + // 'Activity' => '', ); diff --git a/app/Locales/fi_FI/translations.php b/app/Locales/fi_FI/translations.php index 4a85630e..4481078d 100644 --- a/app/Locales/fi_FI/translations.php +++ b/app/Locales/fi_FI/translations.php @@ -29,7 +29,7 @@ return array( 'All users' => 'Kaikki käyttäjät', 'Username' => 'Käyttäjänimi', 'Password' => 'Salasana', - 'Default Project' => 'Oletusprojekti', + 'Default project' => 'Oletusprojekti', 'Administrator' => 'Ylläpitäjä', 'Sign in' => 'Kirjaudu sisään', 'Users' => 'Käyttäjät', @@ -51,6 +51,7 @@ return array( 'Status' => 'Status', 'Tasks' => 'Tehtävät', 'Board' => 'Taulu', + 'Actions' => 'Toiminnot', 'Inactive' => 'Ei aktiivinen', 'Active' => 'Aktiivinen', 'Column %d' => 'Sarake %d', @@ -83,6 +84,7 @@ return array( 'Application settings' => 'Ohjelman asetukset', 'Language' => 'Kieli', 'Webhooks token:' => 'Webhooks avain:', + // 'API token:' => '', 'More information' => 'Lisätietoja', 'Database size:' => 'Tietokannan koko:', 'Download the database' => 'Lataa tietokanta', @@ -119,7 +121,7 @@ return array( 'The username must be unique' => 'Käyttäjänimi täytyy olla uniikki', 'The username must be alphanumeric' => 'Käyttäjänimen täytyy olla alfanumeerinen', 'The user id is required' => 'Käyttäjän id on pakollinen', - 'Passwords doesn\'t matches' => 'Salasanat eivät täsmää', + // 'Passwords don\'t match' => '', 'The confirmation is required' => 'Varmistus vaaditaan', 'The column is required' => 'Sarake on pakollinen', 'The project is required' => 'Projekti on pakollinen', @@ -162,7 +164,7 @@ return array( 'Ready' => 'Valmis', 'Backlog' => 'Tehtäväjono', 'Work in progress' => 'Työnalla', - 'Done' => 'Valmis', + 'Done' => 'Tehty', 'Application version:' => 'Ohjelman versio:', 'Completed on %B %e, %Y at %k:%M %p' => 'Valmistunut %d.%m.%Y kello %H:%M', '%B %e, %Y at %k:%M %p' => '%d.%m.%Y kello %H:%M', @@ -174,7 +176,7 @@ return array( 'List of projects' => 'Projektit', 'Completed tasks for "%s"' => 'Suoritetut tehtävät projektille %s', '%d closed tasks' => '%d suljettua tehtävää', - 'no task for this project' => 'ei tehtävää tälle projektille', + 'No task for this project' => 'Ei tehtävää tälle projektille', 'Public link' => 'Julkinen linkki', 'There is no column in your project!' => 'Projektilta puuttuu sarakkeet!', 'Change assignee' => 'Vaihda suorittajaa', @@ -189,7 +191,6 @@ return array( 'Edit project access list' => 'Muuta projektin käyttäjiä', 'Edit users access' => 'Muuta käyttäjien pääsyä', 'Allow this user' => 'Salli tämä projekti', - 'Project access list for "%s"' => 'Projektin pääsylista "%s"', 'Only those users have access to this project:' => 'Vain näillä käyttäjillä on pääsy projektiin:', 'Don\'t forget that administrators have access to everything.' => 'Muista että ylläpitäjät pääsevät kaikkialle.', 'revoke' => 'poista', @@ -208,8 +209,8 @@ return array( 'The description is required' => 'Kuvaus vaaditaan', 'Edit this task' => 'Muokkaa tehtävää', 'Due Date' => 'Deadline', - 'm/d/Y' => 'd.m.Y', // Date format parsed with php - 'month/day/year' => 'päivä.kuukausi.vuosi', // Help shown to the user + 'm/d/Y' => 'd.m.Y', + 'month/day/year' => 'päivä.kuukausi.vuosi', 'Invalid date' => 'Virheellinen päiväys', 'Must be done before %B %e, %Y' => 'Täytyy suorittaa ennen %d.%m.%Y', '%B %e, %Y' => '%d.%m.%Y', @@ -221,6 +222,7 @@ return array( 'Action removed successfully.' => 'Toiminto poistettiin onnistuneesti.', 'Automatic actions for the project "%s"' => 'Automaattiset toiminnot projektille "%s"', 'Defined actions' => 'Määritellyt toiminnot', + // 'Add an action' => '', 'Event name' => 'Tapahtuman nimi', 'Action name' => 'Toiminnon nimi', 'Action parameters' => 'Toiminnon parametrit', @@ -271,7 +273,7 @@ return array( 'IP address' => 'IP-Osoite', 'User agent' => 'Selain', 'Persistent connections' => 'Voimassa olevat yhteydet', - 'No session' => 'Ei sessioita', + 'No session.' => 'Ei sessioita.', 'Expiration date' => 'Vanhentumispäivä', 'Remember Me' => 'Muista minut', 'Creation date' => 'Luomispäivä', @@ -357,7 +359,6 @@ return array( 'The time must be a numeric value' => 'Ajan pitää olla numero', 'Todo' => 'Todo', 'In progress' => 'Työnalla', - 'Done' => 'Tehty', 'Sub-task removed successfully.' => 'Alitehtävä poistettu onnistuneesti.', 'Unable to remove this sub-task.' => 'Alitehtävän poistaminen epäonnistui.', 'Sub-task updated successfully.' => 'Alitehtävä päivitettiin onnistuneesti.', @@ -366,7 +367,6 @@ return array( 'Sub-task added successfully.' => 'Alitehtävä luotiin onnistuneesti.', 'Maximum size: ' => 'Maksimikoko: ', 'Unable to upload the file.' => 'Tiedoston lataus epäonnistui.', - 'Actions' => 'Toiminnot', 'Display another project' => 'Näytä toinen projekti', // 'Your GitHub account was successfully linked to your profile.' => '', // 'Unable to link your GitHub Account.' => '', @@ -375,7 +375,7 @@ return array( // 'Unable to unlink your GitHub Account.' => '', // 'Login with my GitHub Account' => '', // 'Link my GitHub Account' => '', - // 'Unlink my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', 'Created by %s' => 'Luonut: %s', 'Last modified on %B %e, %Y at %k:%M %p' => 'Viimeksi muokattu %B %e, %Y kello %H:%M', 'Tasks Export' => 'Tehtävien vienti', @@ -421,4 +421,77 @@ return array( // '[Kanboard] Notification' => '', // 'I want to receive notifications only for those projects:' => '', // 'view the task on Kanboard' => '', + // 'Public access' => '', + // 'Categories management' => '', + // 'Users management' => '', + // 'Active tasks' => '', + // 'Disable public access' => '', + // 'Enable public access' => '', + // 'Active projects' => '', + // 'Inactive projects' => '', + // 'Public access disabled' => '', + // 'Do you really want to disable this project: "%s"?' => '', + // 'Do you really want to duplicate this project: "%s"?' => '', + // 'Do you really want to enable this project: "%s"?' => '', + // 'Project activation' => '', + // 'Move the task to another project' => '', + // 'Move to another project' => '', + // 'Do you really want to duplicate this task?' => '', + // 'Duplicate a task' => '', + // 'External accounts' => '', + // 'Account type' => '', + // 'Local' => '', + // 'Remote' => '', + // 'Enabled' => '', + // 'Disabled' => '', + // 'Google account linked' => '', + // 'Github account linked' => '', + // 'Username:' => '', + // 'Name:' => '', + // 'Email:' => '', + // 'Default project:' => '', + // 'Notifications:' => '', + // 'Group:' => '', + // 'Regular user' => '', + // 'Account type:' => '', + // 'Edit profile' => '', + // 'Change password' => '', + // 'Password modification' => '', + // 'External authentications' => '', + // 'Google Account' => '', + // 'Github Account' => '', + // 'Never connected.' => '', + // 'No account linked.' => '', + // 'Account linked.' => '', + // 'No external authentication enabled.' => '', + // 'Password modified successfully.' => '', + // 'Unable to change the password.' => '', + // 'Change category for the task "%s"' => '', + // 'Change category' => '', + // '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '', + // '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // 'Assigned to %s with an estimate of %s/%sh' => '', + // 'Not assigned, estimate of %sh' => '', + // '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s\'s activity' => '', + // 'No activity.' => '', + // 'RSS feed' => '', + // '%s updated a comment on the task #%d' => '', + // '%s commented on the task #%d' => '', + // '%s updated a subtask for the task #%d' => '', + // '%s created a subtask for the task #%d' => '', + // '%s updated the task #%d' => '', + // '%s created the task #%d' => '', + // '%s closed the task #%d' => '', + // '%s open the task #%d' => '', + // '%s moved the task #%d to the column "%s"' => '', + // '%s moved the task #%d to the position %d in the column "%s"' => '', + // 'Activity' => '', ); diff --git a/app/Locales/fr_FR/translations.php b/app/Locales/fr_FR/translations.php index 8952c2d1..1e725a7e 100644 --- a/app/Locales/fr_FR/translations.php +++ b/app/Locales/fr_FR/translations.php @@ -27,9 +27,9 @@ return array( 'Do you really want to remove this user: "%s"?' => 'Voulez-vous vraiment supprimer cet utilisateur : « %s » ?', 'New user' => 'Ajouter un utilisateur', 'All users' => 'Tous les utilisateurs', - 'Username' => 'Identifiant', + 'Username' => 'Nom d\'utilisateur', 'Password' => 'Mot de passe', - 'Default Project' => 'Projet par défaut', + 'Default project' => 'Projet par défaut', 'Administrator' => 'Administrateur', 'Sign in' => 'Connexion', 'Users' => 'Utilisateurs', @@ -51,6 +51,7 @@ return array( 'Status' => 'État', 'Tasks' => 'Tâches', 'Board' => 'Tableau', + 'Actions' => 'Actions', 'Inactive' => 'Inactif', 'Active' => 'Actif', 'Column %d' => 'Colonne %d', @@ -83,6 +84,7 @@ return array( 'Application settings' => 'Paramètres de l\'application', 'Language' => 'Langue', 'Webhooks token:' => 'Jeton de securité pour les webhooks :', + 'API token:' => 'Jeton de securité pour l\'API :', 'More information' => 'Plus d\'informations', 'Database size:' => 'Taille de la base de données :', 'Download the database' => 'Télécharger la base de données', @@ -174,8 +176,8 @@ return array( 'List of projects' => 'Liste des projets', 'Completed tasks for "%s"' => 'Tâches terminées pour « %s »', '%d closed tasks' => '%d tâches terminées', - 'no task for this project' => 'aucune tâche pour ce projet', - 'Public link' => 'Accès public', + '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 !', 'Change assignee' => 'Changer la personne assignée', 'Change assignee for the task "%s"' => 'Changer la personne assignée pour la tâche « %s »', @@ -189,7 +191,6 @@ return array( 'Edit project access list' => 'Modifier l\'accès au projet', 'Edit users access' => 'Modifier les utilisateurs autorisés', 'Allow this user' => 'Autoriser cet utilisateur', - 'Project access list for "%s"' => 'Liste des accès au projet « %s »', 'Only those users have access to this project:' => 'Seulement ces utilisateurs ont accès à ce projet :', 'Don\'t forget that administrators have access to everything.' => 'N\'oubliez pas que les administrateurs ont accès à tout.', 'revoke' => 'révoquer', @@ -221,6 +222,7 @@ return array( 'Action removed successfully.' => 'Action supprimée avec succès.', 'Automatic actions for the project "%s"' => 'Actions automatisées pour le projet « %s »', 'Defined actions' => 'Actions définies', + 'Add an action' => 'Ajouter une action', 'Event name' => 'Nom de l\'événement', 'Action name' => 'Nom de l\'action', 'Action parameters' => 'Paramètres de l\'action', @@ -271,7 +273,7 @@ return array( 'IP address' => 'Adresse IP', 'User agent' => 'Agent utilisateur', 'Persistent connections' => 'Connexions persistantes', - 'No session' => 'Aucune session', + 'No session.' => 'Aucune session.', 'Expiration date' => 'Date d\'expiration', 'Remember Me' => 'Connexion automatique', 'Creation date' => 'Date de création', @@ -419,4 +421,77 @@ return array( '[Kanboard] Notification' => '[Kanboard] Notification', 'I want to receive notifications only for those projects:' => 'Je souhaite reçevoir les notifications uniquement pour les projets sélectionnés :', 'view the task on Kanboard' => 'voir la tâche sur Kanboard', + 'Public access' => 'Accès public', + 'Categories management' => 'Gestion des catégories', + 'Users management' => 'Gestion des utilisateurs', + 'Active tasks' => 'Tâches actives', + 'Disable public access' => 'Désactiver l\'accès public', + 'Enable public access' => 'Activer l\'accès public', + 'Active projects' => 'Projets activés', + 'Inactive projects' => 'Projets désactivés', + 'Public access disabled' => 'Accès public désactivé', + 'Do you really want to disable this project: "%s"?' => 'Voulez-vous vraiment désactiver ce projet : « %s » ?', + 'Do you really want to duplicate this project: "%s"?' => 'Voulez-vous vraiment dupliquer ce projet : « %s » ?', + 'Do you really want to enable this project: "%s"?' => 'Voulez-vous vraiment activer ce projet : « %s » ?', + 'Project activation' => 'Activation du projet', + 'Move the task to another project' => 'Déplacer la tâche vers un autre projet', + 'Move to another project' => 'Déplacer vers un autre projet', + 'Do you really want to duplicate this task?' => 'Voulez-vous vraiment dupliquer cette tâche ?', + 'Duplicate a task' => 'Dupliquer une tâche', + 'External accounts' => 'Comptes externes', + 'Account type' => 'Type de compte', + 'Local' => 'Local', + '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 :', + 'Default project:' => 'Projet par défaut :', + 'Notifications:' => 'Notifications :', + 'Group:' => 'Groupe :', + 'Regular user' => 'Utilisateur normal', + 'Account type:' => 'Type de compte :', + 'Edit profile' => 'Modifier le profile', + 'Change password' => 'Changer le mot de passe', + 'Password modification' => 'Changement de mot de passe', + 'External authentications' => 'Authentifications externe', + 'Google Account' => 'Compte Google', + 'Github Account' => 'Compte Github', + 'Never connected.' => 'Jamais connecté.', + 'No account linked.' => 'Aucun compte attaché.', + 'Account linked.' => 'Compte attaché.', + 'No external authentication enabled.' => 'Aucune authentication externe activée.', + 'Password modified successfully.' => 'Mot de passe changé avec succès.', + 'Unable to change the password.' => 'Impossible de changer le mot de passe.', + 'Change category for the task "%s"' => 'Changer la catégorie pour la tâche « %s »', + 'Change category' => 'Changer de catégorie', + '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '%s a mis à jour la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a>', + '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '%s a ouvert la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a>', + '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '%s a déplacé la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a> à la position n°%d dans la colonne « %s »', + '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '%s a déplacé la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a> dans la colonne « %s »', + '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '%s a créé la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a>', + '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '%s a fermé la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a>', + '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '%s a créé une sous-tâche pour la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a>', + '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '%s a mis à jour une sous-tâche appartenant à la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a>', + 'Assigned to %s with an estimate of %s/%sh' => 'Assigné à %s avec un estimé de %s/%sh', + 'Not assigned, estimate of %sh' => 'Personne assigné, estimé de %sh', + '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '%s a mis à jour un commentaire appartenant à la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a>', + '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '%s a ajouté un commentaire sur la tâche <a href="?controller=task&action=show&task_id=%d">n°%d</a>', + '%s\'s activity' => 'Activité du projet %s', + 'No activity.' => 'Aucune activité.', + 'RSS feed' => 'Flux RSS', + '%s updated a comment on the task #%d' => '%s a mis à jour un commentaire sur la tâche n°%d', + '%s commented on the task #%d' => '%s a ajouté un commentaire sur la tâche n°%d', + '%s updated a subtask for the task #%d' => '%s a mis à jour une sous-tâche appartenant à la tâche n°%d', + '%s created a subtask for the task #%d' => '%s a créé une sous-tâche pour la tâche n°%d', + '%s updated the task #%d' => '%s a mis à jour la tâche n°%d', + '%s created the task #%d' => '%s a créé la tâche n°%d', + '%s closed the task #%d' => '%s a fermé la tâche n°%d', + '%s open the task #%d' => '%s a ouvert la tâche n°%d', + '%s moved the task #%d to the column "%s"' => '%s a déplacé la tâche n°%d dans la colonne « %s »', + '%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é', ); diff --git a/app/Locales/it_IT/translations.php b/app/Locales/it_IT/translations.php new file mode 100644 index 00000000..5834faf2 --- /dev/null +++ b/app/Locales/it_IT/translations.php @@ -0,0 +1,497 @@ +<?php + +return array( + 'None' => 'Nessuno', + 'edit' => 'modificare', + 'Edit' => 'Modificare', + 'remove' => 'cancellare', + 'Remove' => 'Cancellare', + 'Update' => 'Aggiornare', + 'Yes' => 'Si', + 'No' => 'No', + 'cancel' => 'annullare', + 'or' => 'o', + 'Yellow' => 'Giallo', + 'Blue' => 'Blu', + 'Green' => 'Verde', + 'Purple' => 'Porpora', + 'Red' => 'Rosso', + 'Orange' => 'Arancione', + 'Grey' => 'Grigio', + 'Save' => 'Salvare', + 'Login' => 'Ingressare', + 'Official website:' => 'Sito web ufficiale :', + 'Unassigned' => 'Non assegnato', + 'View this task' => 'Vedere questo compito', + 'Remove user' => 'Cancellare un utente', + 'Do you really want to remove this user: "%s"?' => 'Veramente vuoi cancellare questo utente: « %s » ?', + 'New user' => 'Aggiungere un utente', + 'All users' => 'Tutti gli utenti', + 'Username' => 'Nome di utente', + 'Password' => 'Password', + 'Default project' => 'Progetto predefinito', + 'Administrator' => 'Amministratore', + 'Sign in' => 'Iscriversi', + 'Users' => 'Utenti', + 'No user' => 'Nessun utente', + 'Forbidden' => 'Vietato', + 'Access Forbidden' => 'Accesso vietato', + 'Only administrators can access to this page.' => 'Solo gli amministratori possono accedere a questa pagina.', + 'Edit user' => 'Modificare un utente', + 'Logout' => 'Uscire', + 'Bad username or password' => 'Utente o password sbagliato', + 'users' => 'utenti', + 'projects' => 'progetti', + 'Edit project' => 'Modificare progetto', + 'Name' => 'Nome', + 'Activated' => 'Attivo', + 'Projects' => 'Progetti', + 'No project' => 'Nessun progetto', + 'Project' => 'Progetto', + 'Status' => 'Stato', + 'Tasks' => 'Compiti', + 'Board' => 'Bacheca', + // 'Actions' => '', + 'Inactive' => 'Inattivo', + 'Active' => 'Attivo', + 'Column %d' => 'Colonna %d', + 'Add this column' => 'Aggiungere questa colonna', + '%d tasks on the board' => '%d compiti sulla bacheca', + '%d tasks in total' => '%d compiti in totale', + 'Unable to update this board.' => 'Non si può aggiornare questa bacheca.', + 'Edit board' => 'Modificare questa bacheca', + 'Disable' => 'Disattivare', + 'Enable' => 'Attivare', + 'New project' => 'Nuovo progetto', + 'Do you really want to remove this project: "%s"?' => '¿veramente que deseas eliminar questo progetto: « %s » ?', + 'Remove project' => 'Cancellare el progetto', + 'Boards' => 'Tableros', + 'Edit the board for "%s"' => 'Modificar el tablero por « %s »', + 'All projects' => 'Tutti i progetti', + 'Change columns' => 'Cambiare le colonne', + 'Add a new column' => 'Aggiungere una nuova colonna', + 'Title' => 'Titolo', + 'Add Column' => 'Aggiungere colonna', + 'Project "%s"' => 'progetto « %s »', + 'Nobody assigned' => 'Nessuno assegnato', + 'Assigned to %s' => 'Assegnato a %s', + 'Remove a column' => 'Cancellare questa colonna', + 'Remove a column from a board' => 'Cancellare una colonna di una bacheca', + 'Unable to remove this column.' => 'Non si può cancellare questa colonna.', + 'Do you really want to remove this column: "%s"?' => 'Veramente desideri cancellare questa colonna : « %s » ?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Questa azione cancellerà TUTTI I COMPITI legati a questa colonna!', + 'Settings' => 'Impostazioni', + 'Application settings' => 'Impostazioni dell\'applicazione', + 'Language' => 'Lingua', + 'Webhooks token:' => 'Identificatore (token) per i webhooks :', + // 'API token:' => '', + 'More information' => 'Più informazione', + 'Database size:' => 'Dimensioni della base dati:', + 'Download the database' => 'Scaricare la base dati', + 'Optimize the database' => 'Ottimizare la base dati', + '(VACUUM command)' => '(Comando VACUUM)', + '(Gzip compressed Sqlite file)' => '(File Sqlite compresso in Gzip)', + 'User settings' => 'Impostazioni di utente', + 'My default project:' => 'Il mio progetto predefinito: ', + 'Close a task' => 'Chiudere un compito', + 'Do you really want to close this task: "%s"?' => 'Veramente desidera chiudere questo compito: « %s » ?', + 'Edit a task' => 'Modificare un compito', + 'Column' => 'colonna', + // 'Color' => '', + 'Assignee' => 'Persona assegnata', + 'Create another task' => 'Creare un nuovo compito', + 'New task' => 'Nuovo compito', + 'Open a task' => 'Aprire un compito', + 'Do you really want to open this task: "%s"?' => 'Veramente desidera aprire questo compito: « %s » ?', + 'Back to the board' => 'Tornare alla bacheca', + // 'Created on %B %e, %Y at %k:%M %p' => '', + 'There is nobody assigned' => 'Non c\'è nessuno assegnato a questo compito', + 'Column on the board:' => 'Colonna sulla bacheca: ', + 'Status is open' => 'Stato aperto', + 'Status is closed' => 'stato chiuso', + 'Close this task' => 'Chiudere questo compito', + 'Open this task' => 'Aprire questo compito', + 'There is no description.' => 'Non c\'è descrizione.', + 'Add a new task' => 'Aggiungere un nuovo compito', + 'The username is required' => 'Si richiede un nome di utente', + 'The maximum length is %d characters' => 'La lunghezza massima è di %d caratteri', + 'The minimum length is %d characters' => 'La lunghezza minima è di %d caratteri', + 'The password is required' => 'Si richiede una password', + 'This value must be an integer' => 'questo valore deve essere un intero', + 'The username must be unique' => 'Il nome di utente deve essere unico', + 'The username must be alphanumeric' => 'Il nome di utente deve essere alfanumerico', + 'The user id is required' => 'Si richiede l\'identificatore dell\'utente', + // 'Passwords don\'t match' => '', + 'The confirmation is required' => 'Si richiede una conferma', + 'The column is required' => 'Si richiede una colonna', + 'The project is required' => 'Si richiede il progetto', + 'The color is required' => 'Si richiede il colore', + 'The id is required' => 'Si richiede l\'identificatore', + 'The project id is required' => 'Si richiede l\'identificatore del progetto', + 'The project name is required' => 'Si richiede il nome del progetto', + 'This project must be unique' => 'Il nome del progetto deve essere unico', + 'The title is required' => 'Si richiede un titolo', + 'The language is required' => 'Si richiede una lingua', + 'There is no active project, the first step is to create a new project.' => 'Non ci sono progetti attivi, il primo passo consiste in creare un nuovo progetto.', + 'Settings saved successfully.' => 'Impostazioni salvati correttamente.', + 'Unable to save your settings.' => 'Non si possono salvare gli impostazioni.', + 'Database optimization done.' => 'Ottimizzazione della base dati conclusa.', + 'Your project have been created successfully.' => 'Il suo progetto è stato creato correttamente.', + 'Unable to create your project.' => 'Non si può creare il progetto.', + 'Project updated successfully.' => 'Progetto aggiornato correttamente.', + 'Unable to update this project.' => 'Non si può aggiornare il progetto.', + 'Unable to remove this project.' => 'Non si può cancellare questo progetto.', + 'Project removed successfully.' => 'Progetto cancellato correttamente.', + 'Project activated successfully.' => 'Progetto attivato correttamente.', + 'Unable to activate this project.' => 'Non si può attivare il progetto.', + 'Project disabled successfully.' => 'Progetto disattivato correttamente.', + 'Unable to disable this project.' => 'Non si può disattivare il progetto.', + 'Unable to open this task.' => 'Non si può aprire questo compito.', + 'Task opened successfully.' => 'Il compito è stato aperto correttamente.', + 'Unable to close this task.' => 'Non si può chiudere questo compito.', + 'Task closed successfully.' => 'Compito chiuso correttamente.', + 'Unable to update your task.' => 'Non si può modificare questo compito.', + 'Task updated successfully.' => 'Compito modificato correttamente.', + 'Unable to create your task.' => 'Non si può creare questo compito.', + 'Task created successfully.' => 'Compito creato correttamente.', + 'User created successfully.' => 'Utente creato correttamente.', + 'Unable to create your user.' => 'Non si può creare l\'utente.', + 'User updated successfully.' => 'Utente aggiornato correttamente.', + 'Unable to update your user.' => 'Non si può aggiornare questo utente.', + 'User removed successfully.' => 'Utente cancellato correttamente.', + 'Unable to remove this user.' => 'Non si può cancellare questo utente.', + 'Board updated successfully.' => 'Bacheca aggiornata correttamente.', + 'Ready' => 'Pronto', + 'Backlog' => 'In attesa', + 'Work in progress' => 'In corso', + 'Done' => 'Fatto', + 'Application version:' => 'Versione dell\'applicazione:', + // 'Completed on %B %e, %Y at %k:%M %p' => '', + // '%B %e, %Y at %k:%M %p' => '', + 'Date created' => 'Data di creazione', + 'Date completed' => 'Data di termine', + 'Id' => 'Identificatore', + 'No task' => 'Nessun compito', + 'Completed tasks' => 'Compiti fatti', + 'List of projects' => 'Lista di progetti', + 'Completed tasks for "%s"' => 'Compiti fatti da « %s »', + '%d closed tasks' => '%d compiti chiusi', + 'No task for this project' => 'Nessun compito per questo progetto', + 'Public link' => 'Link pubblico', + 'There is no column in your project!' => 'Non c\'è nessuna colonna per questo progetto!', + 'Change assignee' => 'Cambiare la persona assegnata', + 'Change assignee for the task "%s"' => 'Cambiare la persona assegnata per il compito « %s »', + 'Timezone' => 'Fuso orario', + 'Sorry, I didn\'t found this information in my database!' => 'Mi dispiace, non ho trovato questa informazione sulla base dati!', + 'Page not found' => 'Página non trovata', + // 'Complexity' => '', + 'limit' => 'limite', + 'Task limit' => 'Numero massimo di compiti', + 'This value must be greater than %d' => 'questo valore deve essere maggiore di %d', + 'Edit project access list' => 'Modificare i permessi del progetto', + 'Edit users access' => 'Modificare i permessi degli utenti', + 'Allow this user' => 'Permettere a questo utente', + 'Only those users have access to this project:' => 'Solo questi utenti hanno accesso a questo progetto:', + 'Don\'t forget that administrators have access to everything.' => 'Non dimenticare che gli amministratori hanno accesso a tutto.', + 'revoke' => 'revocare', + 'List of authorized users' => 'Lista di utenti autorizzati', + 'User' => 'Utente', + 'Everybody have access to this project.' => 'Tutti hanno accesso a questo progetto.', + 'You are not allowed to access to this project.' => 'Non hai l\'accesso a questo progetto.', + 'Comments' => 'Commenti', + 'Post comment' => 'Mandare commento', + 'Write your text in Markdown' => 'Redatta il testo in Markdown', + 'Leave a comment' => 'Lasciare un commento', + 'Comment is required' => 'Si richiede un commento', + 'Leave a description' => 'Lasciare una descrizione', + 'Comment added successfully.' => 'Commenti aggiunti correttamente.', + 'Unable to create your comment.' => 'Non si può creare questo commento.', + 'The description is required' => 'Si richiede una descrizione', + 'Edit this task' => 'Modificare questo compito', + 'Due Date' => 'Data di scadenza', + 'm/d/Y' => 'd/m/Y', + 'month/day/year' => 'giorno/mese/anno', + 'Invalid date' => 'Data sbagliata', + // 'Must be done before %B %e, %Y' => '', + // '%B %e, %Y' => '', + 'Automatic actions' => 'Azioni automatiche', + 'Your automatic action have been created successfully.' => 'l\'azione automatica è stata creata correttamente.', + 'Unable to create your automatic action.' => 'Non si può creare quest\'azione automatica.', + 'Remove an action' => 'Cancellare un\'azione', + 'Unable to remove this action.' => 'Non si può cancellare questa azione.', + 'Action removed successfully.' => 'Azione cancellata correttamente.', + 'Automatic actions for the project "%s"' => 'Azioni automatiche per questo progetto « %s »', + 'Defined actions' => 'Azioni definite', + // 'Add an action' => '', + 'Event name' => 'Nome dell\'evento', + 'Action name' => 'Nome dell\'azione', + 'Action parameters' => 'Parametri d\'azione', + 'Action' => 'Azione', + 'Event' => 'Evento', + 'When the selected event occurs execute the corresponding action.' => 'Quando accade l\'evento selezionato, eseguire l\'azione corrispondente.', + 'Next step' => 'Passo seguente', + 'Define action parameters' => 'Definire i parametri dell\'azione', + 'Save this action' => 'Salvare questa azione', + 'Do you really want to remove this action: "%s"?' => 'Veramente vuole cancellare questa azione « %s » ?', + 'Remove an automatic action' => 'Cancellare un\'azione automatica', + 'Close the task' => 'Chiudere questo compito', + 'Assign the task to a specific user' => 'Assegnare quesot compito a un utente specifico', + 'Assign the task to the person who does the action' => 'Assegnare il compito all\'utente che svolge l\'azione', + 'Duplicate the task to another project' => 'Duplicare il compito in altro progetto', + 'Move a task to another column' => 'Muovere un compito ad un altra colonna', + 'Move a task to another position in the same column' => 'Muovere un compito ad altra posizione sulla stessa colonna', + 'Task modification' => 'Modifica di un compito', + 'Task creation' => 'Creazione di un compito', + 'Open a closed task' => 'Riaprire un compito', + 'Closing a task' => 'Chiudere un compito', + // 'Assign a color to a specific user' => '', + 'Column title' => 'Titolo della colonna', + 'Position' => 'Posizione', + 'Move Up' => 'Alzare', + 'Move Down' => 'Abassare', + 'Duplicate to another project' => 'Duplicare in un altro progetto', + 'Duplicate' => 'Duplicare', + 'link' => 'link', + 'Update this comment' => 'Aggiornare questo commento', + 'Comment updated successfully.' => 'Commento aggiornato correttamente.', + 'Unable to update your comment.' => 'Non si può aggiornare questo commento.', + 'Remove a comment' => 'Cancellare un commento', + 'Comment removed successfully.' => 'Commento cancellato correttamente.', + 'Unable to remove this comment.' => 'Non si può cancellare questo commento.', + 'Do you really want to remove this comment?' => 'Desidera cancellare questo commento?', + 'Only administrators or the creator of the comment can access to this page.' => 'Solo gli amministratori o l\'autore del commento hanno accesso a questa pagina.', + 'Details' => 'Dettagli', + 'Current password for the user "%s"' => 'Password attuale per l\'utente: « %s »', + 'The current password is required' => 'Si richiede la password attuale', + 'Wrong password' => 'password sbagliata', + 'Reset all tokens' => 'Azzerare gli identificatori (tokens) di sicurezza ', + 'All tokens have been regenerated.' => 'Tutti gli identificatori (tokens) sono stati rigenerati.', + 'Unknown' => 'Sconociuto', + 'Last logins' => 'Ultimi ingressi', + 'Login date' => 'Data di ingresso', + 'Authentication method' => 'Metodo di autenticazzione', + 'IP address' => 'Indirizzo IP', + 'User agent' => 'Navigatore', + 'Persistent connections' => 'Conessioni persistenti', + 'No session.' => 'Non essiste sessione.', + 'Expiration date' => 'Data di scadenza', + 'Remember Me' => 'Riccordami', + 'Creation date' => 'Data di creazione', + 'Filter by user' => 'Filtrado mediante utente', + 'Filter by due date' => 'Filtrare attraverso data di scadenza', + 'Everybody' => 'Tutti', + 'Open' => 'Aperto', + 'Closed' => 'Chiuso', + 'Search' => 'Cercare', + 'Nothing found.' => 'Non si è trovato nulla.', + 'Search in the project "%s"' => 'Cercare sul progetto "%s"', + 'Due date' => 'Data di scadenza', + 'Others formats accepted: %s and %s' => 'Altri0 formati accettati: %s y %s', + 'Description' => 'Descrizione', + '%d comments' => '%d commenti', + '%d comment' => '%d commento', + 'Email address invalid' => 'Indirizzo e-mail sbagliato', + 'Your Google Account is not linked anymore to your profile.' => 'Il suo account Google non i più collegato col suo profilo', + 'Unable to unlink your Google Account.' => 'Non si può svincolare l\'account di Google.', + 'Google authentication failed' => 'Non si è riuscito ad ingressare su Google', + 'Unable to link your Google Account.' => 'Non si può collegare con il suo account di Google.', + 'Your Google Account is linked to your profile successfully.' => 'Il suo account di Google è stato collegato correttamente al suo profilo.', + 'Email' => 'E-mail', + 'Link my Google Account' => 'Collegare con il mio Account di Google', + 'Unlink my Google Account' => 'Svincolare con il mio account di Google', + 'Login with my Google Account' => 'Ingressa con il mio Account di Google', + 'Project not found.' => 'progetto non trovato.', + 'Task #%d' => 'Compito numero %d', + 'Task removed successfully.' => 'Compito cancellato correttamente.', + 'Unable to remove this task.' => 'Non si può cancellare questo compito.', + 'Remove a task' => 'Cancellare un compito', + 'Do you really want to remove this task: "%s"?' => 'Veramente vuoi cancellare questo compito: "%s"?', + 'Assign automatically a color based on a category' => 'Assegnare un colore in modo automatica basandosi sulla categoria', + 'Assign automatically a category based on a color' => 'Assegnare una categoria in modo automatico basandosi sul colore', + 'Task creation or modification' => 'Creazione o Modifica di compito', + 'Category' => 'Categoria', + 'Category:' => 'Categoria:', + 'Categories' => 'Categorie', + 'Category not found.' => 'Categoria non trovata.', + 'Your category have been created successfully.' => 'È stata creata la sua categoria correttamente.', + 'Unable to create your category.' => 'Non si può creare la sua categoria.', + 'Your category have been updated successfully.' => 'La sua categoria è stata aggiornata correttamente.', + 'Unable to update your category.' => 'Non si può aggiornare la sua categoria.', + 'Remove a category' => 'Cancellare una categoria', + 'Category removed successfully.' => 'Categoria cancellata correttamente.', + 'Unable to remove this category.' => 'Non si può cancellare questa categoria.', + 'Category modification for the project "%s"' => 'Modifica di categoria per il progetto "%s"', + 'Category Name' => 'Nome di categoria', + 'Categories for the project "%s"' => 'Categorie per il progetto', + 'Add a new category' => 'Aggiungere una nuova categoria', + 'Do you really want to remove this category: "%s"?' => 'veramente vuoi cancellare questa categoria: "%s"?', + 'Filter by category' => 'Filtrare attraverso categoria', + 'All categories' => 'Tutte le categorie', + 'No category' => 'Senza categoria', + 'The name is required' => 'Si richiede un nome', + 'Remove a file' => 'Cancellare un file', + 'Unable to remove this file.' => 'Non si può cancellare questo file.', + 'File removed successfully.' => 'File cancellato correttamente.', + 'Attach a document' => 'Allegare un documento', + 'Do you really want to remove this file: "%s"?' => 'Veramente vuoi cancellare questo file: "%s"?', + 'open' => 'aprire', + 'Attachments' => 'Allegati', + 'Edit the task' => 'Modificare il compito', + 'Edit the description' => 'Modificare la descrizione', + 'Add a comment' => 'Aggiungere un commento', + 'Edit a comment' => 'Modificare un commento', + 'Summary' => 'Sommario', + 'Time tracking' => 'Inseguimento temporale', + 'Estimate:' => 'Stimate:', + 'Spent:' => 'Trascorse:', + 'Do you really want to remove this sub-task?' => 'Veramente vuoi cancellare questo sub-compito?', + 'Remaining:' => 'Rimanendo', + 'hours' => 'ore', + 'spent' => 'trascorse', + 'estimated' => 'Stimate', + 'Sub-Tasks' => 'Sub-Compiti', + 'Add a sub-task' => 'Aggiungere un sub-compito', + 'Original Estimate' => 'Stimazione originale', + 'Create another sub-task' => 'Crear un altro sub-compito', + 'Time Spent' => 'Tiempo Trascorso', + 'Edit a sub-task' => 'Modificare un sub-compito', + 'Remove a sub-task' => 'Cancellare un sub-compito', + 'The time must be a numeric value' => 'Il tempo deve essere un valore numerico', + 'Todo' => 'Da fare', + 'In progress' => 'In corso', + 'Sub-task removed successfully.' => 'Sub-compito cancellato correttamente.', + 'Unable to remove this sub-task.' => 'Non si può cancellare questo sub-compito.', + 'Sub-task updated successfully.' => 'Sub-compito aggiornato correttamente.', + 'Unable to update your sub-task.' => 'Non si può aggiornare il suo sub-compito.', + 'Unable to create your sub-task.' => 'Non si può creare il suo sub-compito.', + 'Sub-task added successfully.' => 'Sub-compito aggiunto correttamente.', + 'Maximum size: ' => 'Dimensioni massime', + 'Unable to upload the file.' => 'Non si può caricare il file.', + 'Display another project' => 'Mostrare un altro progetto', + 'Your GitHub account was successfully linked to your profile.' => 'Il suo account di Github è stato collegato correttamente col suo profilo.', + 'Unable to link your GitHub Account.' => 'Non si può collegarre col suo account di Github.', + 'GitHub authentication failed' => 'L\'autenticazione non è stata possibile', + 'Your GitHub account is no longer linked to your profile.' => 'Il suo account di Github non è più vincolato al suo profilo.', + 'Unable to unlink your GitHub Account.' => 'Non si può svincolare il suo account di Github.', + 'Login with my GitHub Account' => 'Ingressare col suo account di Github', + 'Link my GitHub Account' => 'Lier mon compte Github', + 'Unlink my GitHub Account' => 'Non impiegare più l\'account di Github', + 'Created by %s' => 'Creato da %s', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Ultima modifica il %d/%m/%Y alle %H:%M', + 'Tasks Export' => 'Esportazione di compiti', + 'Tasks exportation for "%s"' => 'Esportazione di compiti per « %s »', + 'Start Date' => 'Data d\'inizio', + 'End Date' => 'Data di fine', + 'Execute' => 'Essecutare', + 'Task Id' => 'Identificatore del compito', + 'Creator' => 'Creatore', + 'Modification date' => 'Data di modifica', + 'Completion date' => 'Data di termine', + // 'Webhook URL for task creation' => '', + // 'Webhook URL for task modification' => '', + // 'Clone' => '', + // 'Clone Project' => '', + // 'Project cloned successfully.' => '', + // 'Unable to clone this project.' => '', + // 'Email notifications' => '', + // 'Enable email notifications' => '', + // 'Task position:' => '', + // 'The task #%d have been opened.' => '', + // 'The task #%d have been closed.' => '', + // 'Sub-task updated' => '', + // 'Title:' => '', + // 'Status:' => '', + // 'Assignee:' => '', + // 'Time tracking:' => '', + // 'New sub-task' => '', + 'New attachment added "%s"' => 'Nuovo allegato aggiunto « %s »', + 'Comment updated' => 'Commento aggiornato', + 'New comment posted by %s' => 'Nouveau commentaire ajouté par « %s »', + 'List of due tasks for the project "%s"' => 'Lista dei compiti scaduti dal progetto « %s »', + '[%s][New attachment] %s (#%d)' => '[%s][Nuovo allegato] %s (#%d)', + '[%s][New comment] %s (#%d)' => '[%s][Nuovo commento] %s (#%d)', + '[%s][Comment updated] %s (#%d)' => '[%s][Commento aggiornato] %s (#%d)', + '[%s][New subtask] %s (#%d)' => '[%s][Nuovo sub-compito] %s (#%d)', + '[%s][Subtask updated] %s (#%d)' => '[%s][Sub-compito aggiornato] %s (#%d)', + '[%s][New task] %s (#%d)' => '[%s][Nuovo compito] %s (#%d)', + '[%s][Task updated] %s (#%d)' => '[%s][Compito aggiornato] %s (#%d)', + '[%s][Task closed] %s (#%d)' => '[%s][Compito chiuso] %s (#%d)', + '[%s][Task opened] %s (#%d)' => '[%s][Compito aperto] %s (#%d)', + '[%s][Due tasks]' => '[%s][Compiti scaduti]', + '[Kanboard] Notification' => '[Kanboard] Notification', + 'I want to receive notifications only for those projects:' => 'Vorrei ricevere le notifiche solo da questi progetti:', + 'view the task on Kanboard' => 'vedere il compito su Kanboard', + // 'Public access' => '', + // 'Categories management' => '', + // 'Users management' => '', + // 'Active tasks' => '', + // 'Disable public access' => '', + // 'Enable public access' => '', + // 'Active projects' => '', + // 'Inactive projects' => '', + // 'Public access disabled' => '', + // 'Do you really want to disable this project: "%s"?' => '', + // 'Do you really want to duplicate this project: "%s"?' => '', + // 'Do you really want to enable this project: "%s"?' => '', + // 'Project activation' => '', + // 'Move the task to another project' => '', + // 'Move to another project' => '', + // 'Do you really want to duplicate this task?' => '', + // 'Duplicate a task' => '', + // 'External accounts' => '', + // 'Account type' => '', + // 'Local' => '', + // 'Remote' => '', + // 'Enabled' => '', + // 'Disabled' => '', + // 'Google account linked' => '', + // 'Github account linked' => '', + // 'Username:' => '', + // 'Name:' => '', + // 'Email:' => '', + // 'Default project:' => '', + // 'Notifications:' => '', + // 'Group:' => '', + // 'Regular user' => '', + // 'Account type:' => '', + // 'Edit profile' => '', + // 'Change password' => '', + // 'Password modification' => '', + // 'External authentications' => '', + // 'Google Account' => '', + // 'Github Account' => '', + // 'Never connected.' => '', + // 'No account linked.' => '', + // 'Account linked.' => '', + // 'No external authentication enabled.' => '', + // 'Password modified successfully.' => '', + // 'Unable to change the password.' => '', + // 'Change category for the task "%s"' => '', + // 'Change category' => '', + // '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '', + // '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // 'Assigned to %s with an estimate of %s/%sh' => '', + // 'Not assigned, estimate of %sh' => '', + // '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s\'s activity' => '', + // 'No activity.' => '', + // 'RSS feed' => '', + // '%s updated a comment on the task #%d' => '', + // '%s commented on the task #%d' => '', + // '%s updated a subtask for the task #%d' => '', + // '%s created a subtask for the task #%d' => '', + // '%s updated the task #%d' => '', + // '%s created the task #%d' => '', + // '%s closed the task #%d' => '', + // '%s open the task #%d' => '', + // '%s moved the task #%d to the column "%s"' => '', + // '%s moved the task #%d to the position %d in the column "%s"' => '', + // 'Activity' => '', +); diff --git a/app/Locales/pl_PL/translations.php b/app/Locales/pl_PL/translations.php index 11885399..ca48f2e6 100644 --- a/app/Locales/pl_PL/translations.php +++ b/app/Locales/pl_PL/translations.php @@ -29,7 +29,7 @@ return array( 'All users' => 'Wszyscy użytkownicy', 'Username' => 'Nazwa użytkownika', 'Password' => 'Hasło', - 'Default Project' => 'Domyślny projekt', + 'Default project' => 'Domyślny projekt', 'Administrator' => 'Administrator', 'Sign in' => 'Zaloguj', 'Users' => 'Użytkownicy', @@ -51,6 +51,7 @@ return array( 'Status' => 'Status', 'Tasks' => 'Zadania', 'Board' => 'Tablica', + 'Actions' => 'Akcje', 'Inactive' => 'Nieaktywny', 'Active' => 'Aktywny', 'Column %d' => 'Kolumna %d', @@ -83,6 +84,7 @@ return array( 'Application settings' => 'Ustawienia aplikacji', 'Language' => 'Język', 'Webhooks token:' => 'Token :', + // 'API token:' => '', 'More information' => 'Więcej informacji', 'Database size:' => 'Rozmiar bazy danych :', 'Download the database' => 'Pobierz bazę danych', @@ -174,15 +176,12 @@ return array( 'List of projects' => 'Lista projektów', 'Completed tasks for "%s"' => 'Zadania zakończone dla "%s"', '%d closed tasks' => '%d zamkniętych zadań', - 'no task for this project' => 'brak zadań dla tego projektu', + 'No task for this project' => 'Brak zadań dla tego projektu', 'Public link' => 'Link publiczny', 'There is no column in your project!' => 'Brak kolumny w Twoim projekcie', 'Change assignee' => 'Zmień odpowiedzialną osobę', 'Change assignee for the task "%s"' => 'Zmień odpowiedzialną osobę dla zadania "%s"', 'Timezone' => 'Strefa czasowa', - 'Actions' => 'Akcje', - 'Confirmation' => 'Powtórzenie hasła', - 'Description' => 'Opis', 'Sorry, I didn\'t found this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych', 'Page not found' => 'Strona nie istnieje', 'Complexity' => 'Poziom trudności', @@ -192,7 +191,6 @@ return array( 'Edit project access list' => 'Edycja list dostępu dla projektu', 'Edit users access' => 'Edytuj dostęp', 'Allow this user' => 'Dodaj użytkownika', - 'Project access list for "%s"' => 'Lista uprawnionych dla projektu "%s"', 'Only those users have access to this project:' => 'Użytkownicy mający dostęp:', 'Don\'t forget that administrators have access to everything.' => 'Pamiętaj: Administratorzy mają zawsze dostęp do wszystkiego!', 'revoke' => 'odbierz dostęp', @@ -205,13 +203,14 @@ return array( 'Write your text in Markdown' => 'Możesz użyć Markdown', 'Leave a comment' => 'Zostaw komentarz', 'Comment is required' => 'Komentarz jest wymagany', + // 'Leave a description' => '', 'Comment added successfully.' => 'Komentarz dodany', 'Unable to create your comment.' => 'Nie udało się dodać komentarza', 'The description is required' => 'Opis jest wymagany', 'Edit this task' => 'Edytuj zadanie', 'Due Date' => 'Termin', - 'm/d/Y' => 'd/m/Y', // Date format parsed with php - 'month/day/year' => 'dzień/miesiąc/rok', // Help shown to the user + 'm/d/Y' => 'd/m/Y', + 'month/day/year' => 'dzień/miesiąc/rok', 'Invalid date' => 'Błędna data', 'Must be done before %B %e, %Y' => 'Termin do %e %B %Y', '%B %e, %Y' => '%e %B %Y', @@ -223,6 +222,7 @@ return array( 'Action removed successfully.' => 'Akcja usunięta', 'Automatic actions for the project "%s"' => 'Akcje automatyczne dla projektu "%s"', 'Defined actions' => 'Zdefiniowane akcje', + 'Add an action' => 'Nowa akcja', 'Event name' => 'Nazwa zdarzenia', 'Action name' => 'Nazwa akcji', 'Action parameters' => 'Parametry akcji', @@ -245,7 +245,6 @@ return array( 'Open a closed task' => 'Otwarcie zamkniętego zadania', 'Closing a task' => 'Zamknięcie zadania', 'Assign a color to a specific user' => 'Przypisz kolor do wybranego użytkownika', - 'Add an action' => 'Nowa akcja', 'Column title' => 'Tytuł kolumny', 'Position' => 'Pozycja', 'Move Up' => 'Przenieś wyżej', @@ -274,12 +273,12 @@ return array( 'IP address' => 'Adres IP', 'User agent' => 'Przeglądarka', 'Persistent connections' => 'Stałe połączenia', - 'No session' => 'Brak sesji', + 'No session.' => 'Brak sesji.', 'Expiration date' => 'Data zakończenia', 'Remember Me' => 'Pamiętaj mnie', 'Creation date' => 'Data utworzenia', // 'Filter by user' => '', - // 'Filter by due date' => ', + // 'Filter by due date' => '', // 'Everybody' => '', // 'Open' => '', // 'Closed' => '', @@ -288,7 +287,7 @@ return array( // 'Search in the project "%s"' => '', // 'Due date' => '', // 'Others formats accepted: %s and %s' => '', - // 'Description' => '', + 'Description' => 'Opis', // '%d comments' => '', // '%d comment' => '', // 'Email address invalid' => '', @@ -360,7 +359,6 @@ return array( // 'The time must be a numeric value' => '', // 'Todo' => '', // 'In progress' => '', - // 'Done' => '', // 'Sub-task removed successfully.' => '', // 'Unable to remove this sub-task.' => '', // 'Sub-task updated successfully.' => '', @@ -377,8 +375,8 @@ return array( // 'Unable to unlink your GitHub Account.' => '', // 'Login with my GitHub Account' => '', // 'Link my GitHub Account' => '', - // 'Unlink my GitHub Account' => '', - // 'Created by %s' => 'Créé par %s', + // 'Unlink my GitHub Account' => '', + // 'Created by %s' => '', // 'Last modified on %B %e, %Y at %k:%M %p' => '', // 'Tasks Export' => '', // 'Tasks exportation for "%s"' => '', @@ -423,4 +421,77 @@ return array( // '[Kanboard] Notification' => '', // 'I want to receive notifications only for those projects:' => '', // 'view the task on Kanboard' => '', + // 'Public access' => '', + // 'Categories management' => '', + // 'Users management' => '', + // 'Active tasks' => '', + // 'Disable public access' => '', + // 'Enable public access' => '', + // 'Active projects' => '', + // 'Inactive projects' => '', + // 'Public access disabled' => '', + // 'Do you really want to disable this project: "%s"?' => '', + // 'Do you really want to duplicate this project: "%s"?' => '', + // 'Do you really want to enable this project: "%s"?' => '', + // 'Project activation' => '', + // 'Move the task to another project' => '', + // 'Move to another project' => '', + // 'Do you really want to duplicate this task?' => '', + // 'Duplicate a task' => '', + // 'External accounts' => '', + // 'Account type' => '', + // 'Local' => '', + // 'Remote' => '', + // 'Enabled' => '', + // 'Disabled' => '', + // 'Google account linked' => '', + // 'Github account linked' => '', + // 'Username:' => '', + // 'Name:' => '', + // 'Email:' => '', + // 'Default project:' => '', + // 'Notifications:' => '', + // 'Group:' => '', + // 'Regular user' => '', + // 'Account type:' => '', + // 'Edit profile' => '', + // 'Change password' => '', + // 'Password modification' => '', + // 'External authentications' => '', + // 'Google Account' => '', + // 'Github Account' => '', + // 'Never connected.' => '', + // 'No account linked.' => '', + // 'Account linked.' => '', + // 'No external authentication enabled.' => '', + // 'Password modified successfully.' => '', + // 'Unable to change the password.' => '', + // 'Change category for the task "%s"' => '', + // 'Change category' => '', + // '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '', + // '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // 'Assigned to %s with an estimate of %s/%sh' => '', + // 'Not assigned, estimate of %sh' => '', + // '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s\'s activity' => '', + // 'No activity.' => '', + // 'RSS feed' => '', + // '%s updated a comment on the task #%d' => '', + // '%s commented on the task #%d' => '', + // '%s updated a subtask for the task #%d' => '', + // '%s created a subtask for the task #%d' => '', + // '%s updated the task #%d' => '', + // '%s created the task #%d' => '', + // '%s closed the task #%d' => '', + // '%s open the task #%d' => '', + // '%s moved the task #%d to the column "%s"' => '', + // '%s moved the task #%d to the position %d in the column "%s"' => '', + // 'Activity' => '', ); diff --git a/app/Locales/pt_BR/translations.php b/app/Locales/pt_BR/translations.php index 696512f4..b72fbb62 100644 --- a/app/Locales/pt_BR/translations.php +++ b/app/Locales/pt_BR/translations.php @@ -29,7 +29,7 @@ return array( 'All users' => 'Todos os usuários', 'Username' => 'Nome do usuário', 'Password' => 'Senha', - 'Default Project' => 'Projeto default', + 'Default project' => 'Projeto default', 'Administrator' => 'Administrador', 'Sign in' => 'Logar', 'Users' => 'Usuários', @@ -176,24 +176,21 @@ return array( 'List of projects' => 'Lista de projetos', 'Completed tasks for "%s"' => 'Tarefas completadas por "%s"', '%d closed tasks' => '%d tarefas encerradas', - 'no task for this project' => 'nenhuma tarefa para este projeto', + 'No task for this project' => 'Nenhuma tarefa para este projeto', 'Public link' => 'Link público', 'There is no column in your project!' => 'Não há colunas no seu projeto!', 'Change assignee' => 'Mudar a designação', 'Change assignee for the task "%s"' => 'Modificar designação para a tarefa "%s"', 'Timezone' => 'Fuso horário', - 'Confirmation' => 'Confirmação', - 'Description' => 'Descrição', 'Sorry, I didn\'t found this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!', 'Page not found' => 'Página não encontrada', 'Complexity' => 'Complexidade', 'limit' => 'limite', 'Task limit' => 'Limite da tarefa', 'This value must be greater than %d' => 'Este valor deve ser maior que %d', - 'Edit project access list' => 'Editar lista de acesso ao projeto', // new translations to brazilian portuguese starts here + 'Edit project access list' => 'Editar lista de acesso ao projeto', 'Edit users access' => 'Editar acesso de usuários', 'Allow this user' => 'Permitir esse usuário', - 'Project access list for "%s"' => 'Lista de acesso ao projeto para "%s"', 'Only those users have access to this project:' => 'Somente estes usuários têm acesso a este projeto:', 'Don\'t forget that administrators have access to everything.' => 'Não esqueça que administradores têm acesso a tudo.', 'revoke' => 'revogar', @@ -212,8 +209,8 @@ return array( 'The description is required' => 'A descrição é obrigatória', 'Edit this task' => 'Editar esta tarefa', 'Due Date' => 'Data de vencimento', - 'm/d/Y' => 'd/m/Y', // Date format parsed with php - 'month/day/year' => 'dia/mês/ano', // Help shown to the user + 'm/d/Y' => 'd/m/Y', + 'month/day/year' => 'dia/mês/ano', 'Invalid date' => 'Data inválida', 'Must be done before %B %e, %Y' => 'Deve ser feito antes de %d %B %Y', '%B %e, %Y' => '%d %B %Y', @@ -276,7 +273,7 @@ return array( 'IP address' => 'Endereço IP', 'User agent' => 'Agente usuário', 'Persistent connections' => 'Conexões persistentes', - 'No session' => 'Sem sessão', + 'No session.' => 'Sem sessão.', 'Expiration date' => 'Data de expiração', 'Remember Me' => 'Lembre-se de mim', 'Creation date' => 'Data de criação', @@ -378,7 +375,7 @@ return array( 'Unable to unlink your GitHub Account.' => 'Não foi possível desvincular sua conta GitHub.', 'Login with my GitHub Account' => 'Entrar com minha conta do GitHub', 'Link my GitHub Account' => 'Vincular minha conta GitHub', - 'Unlink my GitHub Account' => 'Desvincular minha conta do GitHub', + 'Unlink my GitHub Account' => 'Desvincular minha conta do GitHub', 'Created by %s' => 'Criado por %s', 'Last modified on %B %e, %Y at %k:%M %p' => 'Última modificação em %B %e, %Y às %k: %M %p', 'Tasks Export' => 'Tarefas Export', @@ -424,4 +421,77 @@ return array( // '[Kanboard] Notification' => '', // 'I want to receive notifications only for those projects:' => '', // 'view the task on Kanboard' => '', + // 'Public access' => '', + // 'Categories management' => '', + // 'Users management' => '', + // 'Active tasks' => '', + // 'Disable public access' => '', + // 'Enable public access' => '', + // 'Active projects' => '', + // 'Inactive projects' => '', + // 'Public access disabled' => '', + // 'Do you really want to disable this project: "%s"?' => '', + // 'Do you really want to duplicate this project: "%s"?' => '', + // 'Do you really want to enable this project: "%s"?' => '', + // 'Project activation' => '', + // 'Move the task to another project' => '', + // 'Move to another project' => '', + // 'Do you really want to duplicate this task?' => '', + // 'Duplicate a task' => '', + // 'External accounts' => '', + // 'Account type' => '', + // 'Local' => '', + // 'Remote' => '', + // 'Enabled' => '', + // 'Disabled' => '', + // 'Google account linked' => '', + // 'Github account linked' => '', + // 'Username:' => '', + // 'Name:' => '', + // 'Email:' => '', + // 'Default project:' => '', + // 'Notifications:' => '', + // 'Group:' => '', + // 'Regular user' => '', + // 'Account type:' => '', + // 'Edit profile' => '', + // 'Change password' => '', + // 'Password modification' => '', + // 'External authentications' => '', + // 'Google Account' => '', + // 'Github Account' => '', + // 'Never connected.' => '', + // 'No account linked.' => '', + // 'Account linked.' => '', + // 'No external authentication enabled.' => '', + // 'Password modified successfully.' => '', + // 'Unable to change the password.' => '', + // 'Change category for the task "%s"' => '', + // 'Change category' => '', + // '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '', + // '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // 'Assigned to %s with an estimate of %s/%sh' => '', + // 'Not assigned, estimate of %sh' => '', + // '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s\'s activity' => '', + // 'No activity.' => '', + // 'RSS feed' => '', + // '%s updated a comment on the task #%d' => '', + // '%s commented on the task #%d' => '', + // '%s updated a subtask for the task #%d' => '', + // '%s created a subtask for the task #%d' => '', + // '%s updated the task #%d' => '', + // '%s created the task #%d' => '', + // '%s closed the task #%d' => '', + // '%s open the task #%d' => '', + // '%s moved the task #%d to the column "%s"' => '', + // '%s moved the task #%d to the position %d in the column "%s"' => '', + // 'Activity' => '', ); diff --git a/app/Locales/sv_SE/translations.php b/app/Locales/sv_SE/translations.php index 6d656d30..aa63f6aa 100644 --- a/app/Locales/sv_SE/translations.php +++ b/app/Locales/sv_SE/translations.php @@ -29,7 +29,7 @@ return array( 'All users' => 'Alla användare', 'Username' => 'Användarnamn', 'Password' => 'Lösenord', - 'Default Project' => 'Standardprojekt', + 'Default project' => 'Standardprojekt', 'Administrator' => 'Administratör', 'Sign in' => 'Logga in', 'Users' => 'Användare', @@ -51,7 +51,7 @@ return array( 'Status' => 'Status', 'Tasks' => 'Uppgifter', 'Board' => 'Tavla', - 'Actions' => 'Åtgärder', + 'Actions' => 'Åtgärder', 'Inactive' => 'Inaktiv', 'Active' => 'Aktiv', 'Column %d' => 'Kolumn %d', @@ -84,6 +84,7 @@ return array( 'Application settings' => 'Applikationsinställningar', 'Language' => 'Språk', 'Webhooks token:' => 'Token för webhooks:', + 'API token:' => 'API token:', 'More information' => 'Mer information', 'Database size:' => 'Databasstorlek:', 'Download the database' => 'Ladda ner databasen', @@ -120,7 +121,7 @@ return array( 'The username must be unique' => 'Användarnamnet måste vara unikt', 'The username must be alphanumeric' => 'Användarnamnet måste vara alfanumeriskt', 'The user id is required' => 'Användar-ID måste anges', - 'Passwords doesn\'t matches' => 'Fel lösenord', + 'Passwords don\'t match' => 'Lösenorden matchar inte', 'The confirmation is required' => 'Bekräftelse behövs.', 'The column is required' => 'Kolumnen måste anges', 'The project is required' => 'Projektet måste anges', @@ -163,7 +164,7 @@ return array( 'Ready' => 'Denna månad', 'Backlog' => 'Att göra', 'Work in progress' => 'Pågående', - 'Done' => 'Klart', + 'Done' => 'Slutfört', 'Application version:' => 'Version:', 'Completed on %B %e, %Y at %k:%M %p' => 'Slutfört %d %B %Y kl %H:%M', '%B %e, %Y at %k:%M %p' => '%d %B %Y kl %H:%M', @@ -175,7 +176,7 @@ return array( 'List of projects' => 'Lista med projekt', 'Completed tasks for "%s"' => 'Slutföra uppgifter för "%s"', '%d closed tasks' => '%d stängda uppgifter', - 'no task for this project' => 'inga uppgifter i detta projekt', + 'No task for this project' => 'Inga uppgifter i detta projekt', 'Public link' => 'Publik länk', 'There is no column in your project!' => 'Det saknas kolumner i ditt projekt!', 'Change assignee' => 'Ändra uppdragsinnehavare', @@ -190,7 +191,6 @@ return array( 'Edit project access list' => 'Ändra projektåtkomst lista', 'Edit users access' => 'Användaråtkomst', 'Allow this user' => 'Tillåt användare', - 'Project access list for "%s"' => 'Behörighetslista för "%s"', 'Only those users have access to this project:' => 'Bara de användarna har tillgång till detta projekt.', 'Don\'t forget that administrators have access to everything.' => 'Glöm inte att administratörerna har rätt att göra allt.', 'revoke' => 'Dra tillbaka behörighet', @@ -198,7 +198,6 @@ return array( 'User' => 'Användare', 'Everybody have access to this project.' => 'Alla har tillgång till detta projekt.', 'You are not allowed to access to this project.' => 'Du har inte tillgång till detta projekt.', - '%B %e, %Y at %k:%M %p' => '%d %B %Y kl %H:%M', 'Comments' => 'Kommentarer', 'Post comment' => 'Ladda upp kommentar', 'Write your text in Markdown' => 'Exempelsyntax för text', @@ -210,8 +209,8 @@ return array( 'The description is required' => 'En beskrivning måste lämnas', 'Edit this task' => 'Ändra denna uppgift', 'Due Date' => 'Måldatum', - 'm/d/Y' => 'd/m/Y', // Date format parsed with php - 'month/day/year' => 'dag/månad/år', // Help shown to the user + 'm/d/Y' => 'd/m/Y', + 'month/day/year' => 'dag/månad/år', 'Invalid date' => 'Ej tillåtet datum', 'Must be done before %B %e, %Y' => 'Måste vara klart innan %B %e, %Y', '%B %e, %Y' => '%d %B %Y', @@ -223,6 +222,7 @@ return array( 'Action removed successfully.' => 'Åtgärden har tagits bort.', 'Automatic actions for the project "%s"' => 'Automatiska åtgärder för projektet "%s"', 'Defined actions' => 'Definierade åtgärder', + 'Add an action' => 'Lägg till en åtgärd', 'Event name' => 'Händelsenamn', 'Action name' => 'Åtgärdsnamn', 'Action parameters' => 'Åtgärdsparametrar', @@ -249,7 +249,7 @@ return array( 'Position' => 'Position', 'Move Up' => 'Flytta upp', 'Move Down' => 'Flytta ned', - 'Kopiera till ett annat projekt' => '', + 'Duplicate to another project' => 'Kopiera till ett annat projekt', 'Duplicate' => 'Kopiera uppgiften', 'link' => 'länk', 'Update this comment' => 'Uppdatera kommentaren', @@ -273,7 +273,7 @@ return array( 'IP address' => 'IP-adress', 'User agent' => 'Användaragent/webbläsare', 'Persistent connections' => 'Beständiga anslutningar', - 'No session' => 'Ingen session', + 'No session.' => 'Ingen session.', 'Expiration date' => 'Förfallodatum', 'Remember Me' => 'Kom ihåg mig', 'Creation date' => 'Skapatdatum', @@ -359,7 +359,6 @@ return array( 'The time must be a numeric value' => 'Tiden måste ha ett numeriskt värde', 'Todo' => 'Att göra', 'In progress' => 'Pågående', - 'Done' => 'Slutfört', 'Sub-task removed successfully.' => 'Deluppgiften har tagits bort.', 'Unable to remove this sub-task.' => 'Kunde inte ta bort denna deluppgift.', 'Sub-task updated successfully.' => 'Deluppgiften har uppdaterats.', @@ -376,39 +375,39 @@ return array( 'Unable to unlink your GitHub Account.' => 'Kunde inte koppla ifrån ditt GitHub-konto.', 'Login with my GitHub Account' => 'Logga in med mitt GitHub-konto', 'Link my GitHub Account' => 'Anslut mitt GitHub-konto', - 'Unlink my GitHub Account' => 'Koppla ifrån mitt GitHub-konto', + 'Unlink my GitHub Account' => 'Koppla ifrån mitt GitHub-konto', 'Created by %s' => 'Skapad av %s', 'Last modified on %B %e, %Y at %k:%M %p' => 'Senaste ändring %B %e, %Y kl %k:%M %p', - // 'Tasks Export' => '', - // 'Tasks exportation for "%s"' => '', - // 'Start Date' => '', - // 'End Date' => '', - // 'Execute' => '', - // 'Task Id' => '', - // 'Creator' => '', - // 'Modification date' => '', - // 'Completion date' => '', - // 'Webhook URL for task creation' => '', - // 'Webhook URL for task modification' => '', - // 'Clone' => '', - // 'Clone Project' => '', - // 'Project cloned successfully.' => '', - // 'Unable to clone this project.' => '', - // 'Email notifications' => '', - // 'Enable email notifications' => '', - // 'Task position:' => '', - // 'The task #%d have been opened.' => '', - // 'The task #%d have been closed.' => '', - // 'Sub-task updated' => '', - // 'Title:' => '', - // 'Status:' => '', - // 'Assignee:' => '', - // 'Time tracking:' => '', - // 'New sub-task' => '', - // 'New attachment added "%s"' => '', - // 'Comment updated' => '', - // 'New comment posted by %s' => '', - // 'List of due tasks for the project "%s"' => '', + 'Tasks Export' => 'Exportera uppgifter', + 'Tasks exportation for "%s"' => 'Exportera uppgifter för "%s"', + 'Start Date' => 'Startdatum', + 'End Date' => 'Slutdatum', + 'Execute' => 'Utför', + 'Task Id' => 'Uppgift ID', + 'Creator' => 'Skapare', + 'Modification date' => 'Ändringsdatum', + 'Completion date' => 'Slutfört datum', + 'Webhook URL for task creation' => 'Webhook URL för att skapa uppgift', + 'Webhook URL for task modification' => 'Webhook URL för att ändra uppgift', + 'Clone' => 'Klona', + 'Clone Project' => 'Klona projekt', + 'Project cloned successfully.' => 'Projektet har klonats.', + 'Unable to clone this project.' => 'Kunde inte klona projektet.', + 'Email notifications' => 'Epostnotiser', + 'Enable email notifications' => 'Aktivera epostnotiser', + 'Task position:' => 'Uppgiftsposition:', + 'The task #%d have been opened.' => 'Uppgiften #%d har öppnats.', + 'The task #%d have been closed.' => 'Uppgiften #%d har stängts.', + 'Sub-task updated' => 'Deluppgift uppdaterad', + 'Title:' => 'Titel:', + 'Status:' => 'Status:', + 'Assignee:' => 'Tilldelad:', + 'Time tracking:' => 'Tidsspårning', + 'New sub-task' => 'Ny deluppgift', + 'New attachment added "%s"' => 'Ny bifogning tillagd "%s"', + 'Comment updated' => 'Kommentaren har uppdaterats', + 'New comment posted by %s' => 'Ny kommentar postad av %s', + 'List of due tasks for the project "%s"' => 'Lista med uppgifter för projektet "%s"', // '[%s][New attachment] %s (#%d)' => '', // '[%s][New comment] %s (#%d)' => '', // '[%s][Comment updated] %s (#%d)' => '', @@ -419,7 +418,80 @@ return array( // '[%s][Task closed] %s (#%d)' => '', // '[%s][Task opened] %s (#%d)' => '', // '[%s][Due tasks]' => '', - // '[Kanboard] Notification' => '', - // 'I want to receive notifications only for those projects:' => '', - // 'view the task on Kanboard' => '', + '[Kanboard] Notification' => '[Kanboard] Notis', + 'I want to receive notifications only for those projects:' => 'Jag vill endast få notiser för dessa projekt:', + 'view the task on Kanboard' => 'Visa uppgiften på Kanboard', + // 'Public access' => '', + // 'Categories management' => '', + // 'Users management' => '', + // 'Active tasks' => '', + // 'Disable public access' => '', + // 'Enable public access' => '', + // 'Active projects' => '', + // 'Inactive projects' => '', + // 'Public access disabled' => '', + // 'Do you really want to disable this project: "%s"?' => '', + // 'Do you really want to duplicate this project: "%s"?' => '', + // 'Do you really want to enable this project: "%s"?' => '', + // 'Project activation' => '', + // 'Move the task to another project' => '', + // 'Move to another project' => '', + // 'Do you really want to duplicate this task?' => '', + // 'Duplicate a task' => '', + // 'External accounts' => '', + // 'Account type' => '', + // 'Local' => '', + // 'Remote' => '', + // 'Enabled' => '', + // 'Disabled' => '', + // 'Google account linked' => '', + // 'Github account linked' => '', + // 'Username:' => '', + // 'Name:' => '', + // 'Email:' => '', + // 'Default project:' => '', + // 'Notifications:' => '', + // 'Group:' => '', + // 'Regular user' => '', + // 'Account type:' => '', + // 'Edit profile' => '', + // 'Change password' => '', + // 'Password modification' => '', + // 'External authentications' => '', + // 'Google Account' => '', + // 'Github Account' => '', + // 'Never connected.' => '', + // 'No account linked.' => '', + // 'Account linked.' => '', + // 'No external authentication enabled.' => '', + // 'Password modified successfully.' => '', + // 'Unable to change the password.' => '', + // 'Change category for the task "%s"' => '', + // 'Change category' => '', + // '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '', + // '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // 'Assigned to %s with an estimate of %s/%sh' => '', + // 'Not assigned, estimate of %sh' => '', + // '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s\'s activity' => '', + // 'No activity.' => '', + // 'RSS feed' => '', + // '%s updated a comment on the task #%d' => '', + // '%s commented on the task #%d' => '', + // '%s updated a subtask for the task #%d' => '', + // '%s created a subtask for the task #%d' => '', + // '%s updated the task #%d' => '', + // '%s created the task #%d' => '', + // '%s closed the task #%d' => '', + // '%s open the task #%d' => '', + // '%s moved the task #%d to the column "%s"' => '', + // '%s moved the task #%d to the position %d in the column "%s"' => '', + // 'Activity' => '', ); diff --git a/app/Locales/zh_CN/translations.php b/app/Locales/zh_CN/translations.php index 9509fc52..9c45809a 100644 --- a/app/Locales/zh_CN/translations.php +++ b/app/Locales/zh_CN/translations.php @@ -23,17 +23,13 @@ return array( 'Official website:' => '官方网站:', 'Unassigned' => '未指定', 'View this task' => '查看该任务', - 'Add a sub-task' => '添加一个子任务', - 'Original Estimate' => '初步预计耗时', - 'hours' => '小时', - 'Create another sub-task' => '创建另一个子任务', 'Remove user' => '移除用户', 'Do you really want to remove this user: "%s"?' => '你确定要移除这个用户吗:"%s"?', 'New user' => '新用户', 'All users' => '所有用户', 'Username' => '用户名', 'Password' => '密码', - 'Default Project' => '默认项目', + 'Default project' => '默认项目', 'Administrator' => '管理员', 'Sign in' => '登录', 'Users' => '用户组', @@ -55,6 +51,7 @@ return array( 'Status' => '状态', 'Tasks' => '任务群', 'Board' => '看板', + 'Actions' => '行为', 'Inactive' => '未激活', 'Active' => '激活', 'Column %d' => '第%d栏目', @@ -87,6 +84,7 @@ return array( 'Application settings' => '应用设置', 'Language' => '语言', 'Webhooks token:' => '页面钩子令牌:', + // 'API token:' => '', 'More information' => '更多信息', 'Database size:' => '数据库大小:', 'Download the database' => '下载数据库', @@ -123,7 +121,7 @@ return array( 'The username must be unique' => '用户名必须唯一', 'The username must be alphanumeric' => '用户名必须是英文字符或数字组成', 'The user id is required' => '用户id是必须的', - 'Passwords doesn\'t matches' => '密码不匹配', + // 'Passwords don\'t match' => '', 'The confirmation is required' => '需要确认', 'The column is required' => '需要指定栏目', 'The project is required' => '需要指定项目', @@ -178,7 +176,7 @@ return array( 'List of projects' => '项目列表', 'Completed tasks for "%s"' => '任务因"%s"原因完成', '%d closed tasks' => '%d个已关闭任务', - 'no task for this project' => '该项目尚无任务', + 'No task for this project' => '该项目尚无任务', 'Public link' => '公开链接', 'There is no column in your project!' => '该项目尚无栏目项!', 'Change assignee' => '被指派人变更', @@ -193,7 +191,6 @@ return array( 'Edit project access list' => '编辑项目存取列表', 'Edit users access' => '编辑用户存取权限', 'Allow this user' => '允许该用户', - 'Project access list for "%s"' => '"%s"的项目存取列表', 'Only those users have access to this project:' => '只有这些用户有该项目的存取权限:', 'Don\'t forget that administrators have access to everything.' => '别忘了管理员有一切的权限。', 'revoke' => '撤销', @@ -212,15 +209,12 @@ return array( 'The description is required' => '必须得有描述', 'Edit this task' => '编辑该任务', 'Due Date' => '到期', - 'm/d/Y' => 'Y/m/d', // Date format parsed with php - 'month/day/year' => '年/月/日', // Help shown to the user + 'm/d/Y' => 'Y/m/d', + 'month/day/year' => '年/月/日', 'Invalid date' => '无效日期', 'Must be done before %B %e, %Y' => '必须在%Y年%m月%d日前完成', '%B %e, %Y' => '%Y/%m/%d', 'Automatic actions' => '自动行为', - 'Add an action' => '添加一个行为', - 'Assign automatically a color based on a category' => '基于一个分类自动指派颜色', - 'Assign automatically a category based on a color' => '基于一种颜色自动指派分类', 'Your automatic action have been created successfully.' => '您的自动行为已成功创建', 'Unable to create your automatic action.' => '无法为您创建自动行为。', 'Remove an action' => '移除一个行为。', @@ -228,11 +222,11 @@ return array( 'Action removed successfully.' => '成功移除行为。', 'Automatic actions for the project "%s"' => '项目"%s"的自动行为', 'Defined actions' => '已定义的行为', + 'Add an action' => '添加一个行为', 'Event name' => '事件名称', 'Action name' => '行为名称', 'Action parameters' => '行为参数', 'Action' => '行为', - 'Actions' => '行为', 'Event' => '事件', 'When the selected event occurs execute the corresponding action.' => '当所选事件发生时执行相应行为。', 'Next step' => '下一步', @@ -270,7 +264,6 @@ return array( 'Current password for the user "%s"' => '用户"%s"的当前密码', 'The current password is required' => '需要输入当前密码', 'Wrong password' => '密码错误', - 'Confirmation' => '再输一次新密码', 'Reset all tokens' => '重置所有令牌', 'All tokens have been regenerated.' => '所有令牌都重新生成了。', 'Unknown' => '未知', @@ -280,7 +273,7 @@ return array( 'IP address' => 'IP地址', 'User agent' => '用户代理', 'Persistent connections' => '持续连接', - 'No session' => '无会话', + 'No session.' => '无会话', 'Expiration date' => '过期', 'Remember Me' => '记住我', 'Creation date' => '创建日期', @@ -313,7 +306,8 @@ return array( 'Unable to remove this task.' => '无法移除该任务。', 'Remove a task' => '移除一个任务', 'Do you really want to remove this task: "%s"?' => '确定要溢出该任务"%s"吗?', - 'Assign a color to a specific category' => '指派颜色给一个特定分类', + 'Assign automatically a color based on a category' => '基于一个分类自动指派颜色', + 'Assign automatically a category based on a color' => '基于一种颜色自动指派分类', 'Task creation or modification' => '任务创建或修改', 'Category' => '分类', 'Category:' => '分类:', @@ -352,20 +346,19 @@ return array( // 'Spent:' => '', // 'Do you really want to remove this sub-task?' => '', // 'Remaining:' => '', - // 'hours' => '', + 'hours' => '小时', // 'spent' => '', // 'estimated' => '', // 'Sub-Tasks' => '', - // 'Add a sub-task' => '', - // 'Original Estimate' => '', - // 'Create another sub-task' => '', + 'Add a sub-task' => '添加一个子任务', + 'Original Estimate' => '初步预计耗时', + 'Create another sub-task' => '创建另一个子任务', // 'Time Spent' => '', // 'Edit a sub-task' => '', // 'Remove a sub-task' => '', // 'The time must be a numeric value' => '', // 'Todo' => '', // 'In progress' => '', - // 'Done' => '', // 'Sub-task removed successfully.' => '', // 'Unable to remove this sub-task.' => '', // 'Sub-task updated successfully.' => '', @@ -382,8 +375,8 @@ return array( // 'Unable to unlink your GitHub Account.' => '', // 'Login with my GitHub Account' => '', // 'Link my GitHub Account' => '', - // 'Unlink my GitHub Account' => '', - // 'Created by %s' => 'Créé par %s', + // 'Unlink my GitHub Account' => '', + // 'Created by %s' => '', // 'Last modified on %B %e, %Y at %k:%M %p' => '', // 'Tasks Export' => '', // 'Tasks exportation for "%s"' => '', @@ -426,5 +419,79 @@ return array( // '[%s][Task opened] %s (#%d)' => '', // '[%s][Due tasks]' => '', // '[Kanboard] Notification' => '', + // 'I want to receive notifications only for those projects:' => '', // 'view the task on Kanboard' => '', + // 'Public access' => '', + // 'Categories management' => '', + // 'Users management' => '', + // 'Active tasks' => '', + // 'Disable public access' => '', + // 'Enable public access' => '', + // 'Active projects' => '', + // 'Inactive projects' => '', + // 'Public access disabled' => '', + // 'Do you really want to disable this project: "%s"?' => '', + // 'Do you really want to duplicate this project: "%s"?' => '', + // 'Do you really want to enable this project: "%s"?' => '', + // 'Project activation' => '', + // 'Move the task to another project' => '', + // 'Move to another project' => '', + // 'Do you really want to duplicate this task?' => '', + // 'Duplicate a task' => '', + // 'External accounts' => '', + // 'Account type' => '', + // 'Local' => '', + // 'Remote' => '', + // 'Enabled' => '', + // 'Disabled' => '', + // 'Google account linked' => '', + // 'Github account linked' => '', + // 'Username:' => '', + // 'Name:' => '', + // 'Email:' => '', + // 'Default project:' => '', + // 'Notifications:' => '', + // 'Group:' => '', + // 'Regular user' => '', + // 'Account type:' => '', + // 'Edit profile' => '', + // 'Change password' => '', + // 'Password modification' => '', + // 'External authentications' => '', + // 'Google Account' => '', + // 'Github Account' => '', + // 'Never connected.' => '', + // 'No account linked.' => '', + // 'Account linked.' => '', + // 'No external authentication enabled.' => '', + // 'Password modified successfully.' => '', + // 'Unable to change the password.' => '', + // 'Change category for the task "%s"' => '', + // 'Change category' => '', + // '%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"' => '', + // '%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"' => '', + // '%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // 'Assigned to %s with an estimate of %s/%sh' => '', + // 'Not assigned, estimate of %sh' => '', + // '%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>' => '', + // '%s\'s activity' => '', + // 'No activity.' => '', + // 'RSS feed' => '', + // '%s updated a comment on the task #%d' => '', + // '%s commented on the task #%d' => '', + // '%s updated a subtask for the task #%d' => '', + // '%s created a subtask for the task #%d' => '', + // '%s updated the task #%d' => '', + // '%s created the task #%d' => '', + // '%s closed the task #%d' => '', + // '%s open the task #%d' => '', + // '%s moved the task #%d to the column "%s"' => '', + // '%s moved the task #%d to the position %d in the column "%s"' => '', + // 'Activity' => '', ); diff --git a/app/Model/Acl.php b/app/Model/Acl.php index c4b31079..aea13e8c 100644 --- a/app/Model/Acl.php +++ b/app/Model/Acl.php @@ -18,8 +18,9 @@ class Acl extends Base */ private $public_actions = array( 'user' => array('login', 'check', 'google', 'github'), - 'task' => array('add'), + 'task' => array('add', 'readonly'), 'board' => array('readonly'), + 'project' => array('feed'), ); /** @@ -30,29 +31,13 @@ class Acl extends Base */ private $user_actions = array( 'app' => array('index'), - 'board' => array('index', 'show', 'assign', 'assigntask', 'save', 'check'), - 'project' => array('tasks', 'index', 'forbidden', 'search'), - 'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index', 'unlinkgoogle', 'unlinkgithub'), - 'config' => array('index', 'removeremembermetoken', 'notifications'), + 'board' => array('index', 'show', 'save', 'check', 'changeassignee', 'updateassignee', 'changecategory', 'updatecategory'), + 'project' => array('tasks', 'index', 'forbidden', 'search', 'export', 'show', 'activity'), + 'user' => array('index', 'edit', 'forbidden', 'logout', 'index', 'show', 'external', 'unlinkgoogle', 'unlinkgithub', 'sessions', 'removesession', 'last', 'notifications', 'password'), 'comment' => array('create', 'save', 'confirm', 'remove', 'update', 'edit', 'forbidden'), 'file' => array('create', 'save', 'download', 'confirm', 'remove', 'open', 'image'), 'subtask' => array('create', 'save', 'edit', 'update', 'confirm', 'remove'), - 'task' => array( - 'show', - 'create', - 'save', - 'edit', - 'update', - 'close', - 'confirmclose', - 'open', - 'confirmopen', - 'duplicate', - 'remove', - 'confirmremove', - 'editdescription', - 'savedescription', - ), + 'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'open', 'duplicate', 'remove', 'description', 'move', 'copy'), ); /** diff --git a/app/Model/Action.php b/app/Model/Action.php index effe8707..ad995991 100644 --- a/app/Model/Action.php +++ b/app/Model/Action.php @@ -41,6 +41,7 @@ class Action extends Base 'TaskAssignSpecificUser' => t('Assign the task to a specific user'), 'TaskAssignCurrentUser' => t('Assign the task to the person who does the action'), 'TaskDuplicateAnotherProject' => t('Duplicate the task to another project'), + 'TaskMoveAnotherProject' => t('Move the task to another project'), 'TaskAssignColorUser' => t('Assign a color to a specific user'), 'TaskAssignColorCategory' => t('Assign automatically a color based on a category'), 'TaskAssignCategoryColor' => t('Assign automatically a category based on a color'), @@ -217,34 +218,81 @@ class Action extends Base * @param integer $project_id Project id * @throws \LogicException * @return \Core\Listener Action Instance - * @throw LogicException */ public function load($name, $project_id) { - switch ($name) { - case 'TaskClose': - $className = '\Action\TaskClose'; - return new $className($project_id, new Task($this->registry)); - case 'TaskAssignCurrentUser': - $className = '\Action\TaskAssignCurrentUser'; - return new $className($project_id, new Task($this->registry), new Acl($this->registry)); - case 'TaskAssignSpecificUser': - $className = '\Action\TaskAssignSpecificUser'; - return new $className($project_id, new Task($this->registry)); - case 'TaskDuplicateAnotherProject': - $className = '\Action\TaskDuplicateAnotherProject'; - return new $className($project_id, new Task($this->registry)); - case 'TaskAssignColorUser': - $className = '\Action\TaskAssignColorUser'; - return new $className($project_id, new Task($this->registry)); - case 'TaskAssignColorCategory': - $className = '\Action\TaskAssignColorCategory'; - return new $className($project_id, new Task($this->registry)); - case 'TaskAssignCategoryColor': - $className = '\Action\TaskAssignCategoryColor'; - return new $className($project_id, new Task($this->registry)); + $className = '\Action\\'.$name; + + if ($name === 'TaskAssignCurrentUser') { + return new $className($project_id, new Task($this->registry), new Acl($this->registry)); + } + else { + return new $className($project_id, new Task($this->registry)); + } + } + + /** + * Copy Actions and related Actions Parameters from a project to another one + * + * @author Antonio Rabelo + * @param integer $project_from Project Template + * @return integer $project_to Project that receives the copy + * @return boolean + */ + public function duplicate($project_from, $project_to) + { + $actionTemplate = $this->action->getAllByProject($project_from); + + foreach ($actionTemplate as $action) { + + unset($action['id']); + $action['project_id'] = $project_to; + $actionParams = $action['params']; + unset($action['params']); + + if (! $this->db->table(self::TABLE)->save($action)) { + return false; + } + + $action_clone_id = $this->db->getConnection()->getLastId(); + + foreach ($actionParams as $param) { + unset($param['id']); + $param['value'] = $this->resolveDuplicatedParameters($param, $project_to); + $param['action_id'] = $action_clone_id; + + if (! $this->db->table(self::TABLE_PARAMS)->save($param)) { + return false; + } + } + } + + return true; + } + + /** + * Resolve type of action value from a project to the respective value in another project + * + * @author Antonio Rabelo + * @param integer $param An action parameter + * @return integer $project_to Project to find the corresponding values + * @return mixed The corresponding values from $project_to + */ + private function resolveDuplicatedParameters($param, $project_to) + { + switch($param['name']) { + case 'project_id': + return $project_to; + case 'category_id': + $categoryTemplate = $this->category->getById($param['value']); + $categoryFromNewProject = $this->db->table(Category::TABLE)->eq('project_id', $project_to)->eq('name', $categoryTemplate['name'])->findOne(); + return $categoryFromNewProject['id']; + case 'column_id': + $boardTemplate = $this->board->getColumn($param['value']); + $boardFromNewProject = $this->db->table(Board::TABLE)->eq('project_id', $project_to)->eq('title', $boardTemplate['title'])->findOne(); + return $boardFromNewProject['id']; default: - throw new LogicException('Action not found: '.$name); + return $param['value']; } } diff --git a/app/Model/Authentication.php b/app/Model/Authentication.php index 4c8aad82..6efc5687 100644 --- a/app/Model/Authentication.php +++ b/app/Model/Authentication.php @@ -71,6 +71,27 @@ class Authentication extends Base } /** + * Authenticate a user by different methods + * + * @access public + * @param string $username Username + * @param string $password Password + * @return boolean + */ + public function authenticate($username, $password) + { + // Try first the database auth and then LDAP if activated + if ($this->backend('database')->authenticate($username, $password)) { + return true; + } + else if (LDAP_AUTH && $this->backend('ldap')->authenticate($username, $password)) { + return true; + } + + return false; + } + + /** * Validate user login form * * @access public @@ -90,17 +111,7 @@ class Authentication extends Base if ($result) { - $authenticated = false; - - // Try first the database auth and then LDAP if activated - if ($this->backend('database')->authenticate($values['username'], $values['password'])) { - $authenticated = true; - } - else if (LDAP_AUTH && $this->backend('ldap')->authenticate($values['username'], $values['password'])) { - $authenticated = true; - } - - if ($authenticated) { + if ($this->authenticate($values['username'], $values['password'])) { // Setup the remember me feature if (! empty($values['remember_me'])) { diff --git a/app/Model/Base.php b/app/Model/Base.php index 306cf854..9cf0b766 100644 --- a/app/Model/Base.php +++ b/app/Model/Base.php @@ -15,6 +15,7 @@ use PicoDb\Database; * * @property \Model\Acl $acl * @property \Model\Action $action + * @property \Model\Authentication $authentication * @property \Model\Board $board * @property \Model\Category $category * @property \Model\Comment $comment @@ -25,6 +26,7 @@ use PicoDb\Database; * @property \Model\Project $project * @property \Model\SubTask $subTask * @property \Model\Task $task + * @property \Model\TaskHistory $taskHistory * @property \Model\User $user * @property \Model\Webhook $webhook */ @@ -41,10 +43,10 @@ abstract class Base /** * Event dispatcher instance * - * @access protected + * @access public * @var \Core\Event */ - protected $event; + public $event; /** * Registry instance diff --git a/app/Model/BaseHistory.php b/app/Model/BaseHistory.php new file mode 100644 index 00000000..31578a3b --- /dev/null +++ b/app/Model/BaseHistory.php @@ -0,0 +1,70 @@ +<?php + +namespace Model; + +use PDO; +use Core\Template; + +/** + * Task history model + * + * @package model + * @author Frederic Guillot + */ +abstract class BaseHistory extends Base +{ + /** + * SQL table name + * + * @access protected + * @var string + */ + protected $table = ''; + + /** + * Remove old event entries to avoid a large table + * + * @access public + * @param integer $max Maximum number of items to keep in the table + */ + public function cleanup($max) + { + if ($this->db->table($this->table)->count() > $max) { + + $this->db->execute(' + DELETE FROM '.$this->table.' + WHERE id <= ( + SELECT id FROM ( + SELECT id FROM '.$this->table.' ORDER BY id DESC LIMIT 1 OFFSET '.$max.' + ) foo + )'); + } + } + + /** + * Get all events for a given project + * + * @access public + * @return array + */ + public function getAllByProjectId($project_id) + { + return $this->db->table($this->table) + ->eq('project_id', $project_id) + ->desc('id') + ->findAll(); + } + + /** + * Get the event html content + * + * @access public + * @param array $params Event properties + * @return string + */ + public function getContent(array $params) + { + $tpl = new Template; + return $tpl->load('event_'.str_replace('.', '_', $params['event_name']), $params); + } +} diff --git a/app/Model/Board.php b/app/Model/Board.php index 168b185f..07020600 100644 --- a/app/Model/Board.php +++ b/app/Model/Board.php @@ -21,49 +21,24 @@ class Board extends Base const TABLE = 'columns'; /** - * Save task positions for each column - * - * @access public - * @param array $positions [['task_id' => X, 'column_id' => X, 'position' => X], ...] - * @param integer $selected_task_id The selected task id - * @return boolean - */ - public function saveTasksPosition(array $positions, $selected_task_id) - { - $this->db->startTransaction(); - - foreach ($positions as $value) { - - // We trigger events only for the selected task - if (! $this->task->move($value['task_id'], $value['column_id'], $value['position'], $value['task_id'] == $selected_task_id)) { - $this->db->cancelTransaction(); - return false; - } - } - - $this->db->closeTransaction(); - - return true; - } - - /** * Create a board with default columns, must be executed inside a transaction * * @access public * @param integer $project_id Project id - * @param array $columns List of columns title ['column1', 'column2', ...] + * @param array $columns Column parameters [ 'title' => 'boo', 'task_limit' => 2 ... ] * @return boolean */ public function create($project_id, array $columns) { $position = 0; - foreach ($columns as $title) { + foreach ($columns as $column) { $values = array( - 'title' => $title, + 'title' => $column['title'], 'position' => ++$position, 'project_id' => $project_id, + 'task_limit' => $column['task_limit'], ); if (! $this->db->table(self::TABLE)->save($values)) { @@ -75,6 +50,25 @@ class Board extends Base } /** + * Copy board columns from a project to another one + * + * @author Antonio Rabelo + * @param integer $project_from Project Template + * @return integer $project_to Project that receives the copy + * @return boolean + */ + public function duplicate($project_from, $project_to) + { + $columns = $this->db->table(Board::TABLE) + ->columns('title', 'task_limit') + ->eq('project_id', $project_from) + ->asc('position') + ->findAll(); + + return $this->board->create($project_to, $columns); + } + + /** * Add a new column to the board * * @access public diff --git a/app/Model/Category.php b/app/Model/Category.php index f86abe58..4f296944 100644 --- a/app/Model/Category.php +++ b/app/Model/Category.php @@ -21,6 +21,19 @@ class Category extends Base const TABLE = 'project_has_categories'; /** + * Return true if a category exists for a given project + * + * @access public + * @param integer $category_id Category id + * @param integer $project_id Project id + * @return boolean + */ + public function exists($category_id, $project_id) + { + return $this->db->table(self::TABLE)->eq('id', $category_id)->eq('project_id', $project_id)->count() > 0; + } + + /** * Get a category by the id * * @access public @@ -118,6 +131,34 @@ class Category extends Base } /** + * Duplicate categories from a project to another one + * + * @author Antonio Rabelo + * @param integer $project_from Project Template + * @return integer $project_to Project that receives the copy + * @return boolean + */ + public function duplicate($project_from, $project_to) + { + $categories = $this->db->table(self::TABLE) + ->columns('name') + ->eq('project_id', $project_from) + ->asc('name') + ->findAll(); + + foreach ($categories as $category) { + + $category['project_id'] = $project_to; + + if (! $this->category->create($category)) { + return false; + } + } + + return true; + } + + /** * Validate category creation * * @access public diff --git a/app/Model/CommentHistory.php b/app/Model/CommentHistory.php new file mode 100644 index 00000000..5988c026 --- /dev/null +++ b/app/Model/CommentHistory.php @@ -0,0 +1,152 @@ +<?php + +namespace Model; + +use PDO; +use Core\Registry; +use Event\CommentHistoryListener; + +/** + * Comment history model + * + * @package model + * @author Frederic Guillot + */ +class CommentHistory extends BaseHistory +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'comment_has_events'; + + /** + * Maximum number of events + * + * @var integer + */ + const MAX_EVENTS = 5000; + + /** + * Constructor + * + * @access public + * @param \Core\Registry $registry Registry instance + */ + public function __construct(Registry $registry) + { + parent::__construct($registry); + $this->table = self::TABLE; + } + + /** + * Create a new event + * + * @access public + * @param integer $project_id Project id + * @param integer $task_id Task id + * @param integer $comment_id Comment id + * @param integer $creator_id Author of the event (user id) + * @param string $event_name Task event name + * @param string $data Current comment + * @return boolean + */ + public function create($project_id, $task_id, $comment_id, $creator_id, $event_name, $data) + { + $values = array( + 'project_id' => $project_id, + 'task_id' => $task_id, + 'comment_id' => $comment_id, + 'creator_id' => $creator_id, + 'event_name' => $event_name, + 'date_creation' => time(), + 'data' => $data, + ); + + $this->db->startTransaction(); + + $this->cleanup(self::MAX_EVENTS - 1); + $result = $this->db->table(self::TABLE)->insert($values); + + $this->db->closeTransaction(); + + return $result; + } + + /** + * Get all necessary content to display activity feed + * + * $author_name + * $author_username + * $task['id', 'title', 'position', 'column_name'] + */ + public function getAllContentByProjectId($project_id, $limit = 50) + { + $sql = ' + SELECT + comment_has_events.id, + comment_has_events.date_creation, + comment_has_events.event_name, + comment_has_events.data as comment, + comment_has_events.task_id, + tasks.title as task_title, + users.username as author_username, + users.name as author_name + FROM comment_has_events + LEFT JOIN users ON users.id=comment_has_events.creator_id + LEFT JOIN tasks ON tasks.id=comment_has_events.task_id + WHERE comment_has_events.project_id = ? + ORDER BY comment_has_events.id DESC + LIMIT '.$limit.' OFFSET 0 + '; + + $rq = $this->db->execute($sql, array($project_id)); + $events = $rq->fetchAll(PDO::FETCH_ASSOC); + + foreach ($events as &$event) { + $event['author'] = $event['author_name'] ?: $event['author_username']; + $event['event_title'] = $this->getTitle($event); + $event['event_content'] = $this->getContent($event); + $event['event_type'] = 'comment'; + } + + return $events; + } + + /** + * Get the event title (translated) + * + * @access public + * @param array $event Event properties + * @return string + */ + public function getTitle(array $event) + { + $titles = array( + Comment::EVENT_UPDATE => t('%s updated a comment on the task #%d', $event['author'], $event['task_id']), + Comment::EVENT_CREATE => t('%s commented on the task #%d', $event['author'], $event['task_id']), + ); + + return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : ''; + } + + /** + * Attach events to be able to record the history + * + * @access public + */ + public function attachEvents() + { + $events = array( + Comment::EVENT_UPDATE, + Comment::EVENT_CREATE, + ); + + $listener = new CommentHistoryListener($this); + + foreach ($events as $event_name) { + $this->event->attach($event_name, $listener); + } + } +} diff --git a/app/Model/Config.php b/app/Model/Config.php index 173a6d83..11f334b1 100644 --- a/app/Model/Config.php +++ b/app/Model/Config.php @@ -42,21 +42,19 @@ class Config extends Base */ public function getLanguages() { - $languages = array( + // Sorted by value + return array( 'de_DE' => 'Deutsch', 'en_US' => 'English', 'es_ES' => 'Español', 'fr_FR' => 'Français', + 'it_IT' => 'Italiano', 'pl_PL' => 'Polski', 'pt_BR' => 'Português (Brasil)', + 'fi_FI' => 'Suomi', 'sv_SE' => 'Svenska', 'zh_CN' => '中文(简体)', - 'fi_FI' => 'Suomi', ); - - asort($languages); - - return $languages; } /** diff --git a/app/Model/Notification.php b/app/Model/Notification.php index a92ce73e..0cb17076 100644 --- a/app/Model/Notification.php +++ b/app/Model/Notification.php @@ -115,45 +115,41 @@ class Notification extends Base */ public function getMailSubject($template, array $data) { - Translator::disableEscaping(); - switch ($template) { case 'notification_file_creation': - $subject = t('[%s][New attachment] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][New attachment] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_comment_creation': - $subject = t('[%s][New comment] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][New comment] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_comment_update': - $subject = t('[%s][Comment updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][Comment updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_subtask_creation': - $subject = t('[%s][New subtask] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][New subtask] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_subtask_update': - $subject = t('[%s][Subtask updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][Subtask updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_task_creation': - $subject = t('[%s][New task] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][New task] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_task_update': - $subject = t('[%s][Task updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][Task updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_task_close': - $subject = t('[%s][Task closed] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][Task closed] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_task_open': - $subject = t('[%s][Task opened] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); + $subject = e('[%s][Task opened] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']); break; case 'notification_task_due': - $subject = t('[%s][Due tasks]', $data['project']); + $subject = e('[%s][Due tasks]', $data['project']); break; default: - $subject = t('[Kanboard] Notification'); + $subject = e('[Kanboard] Notification'); } - Translator::enableEscaping(); - return $subject; } diff --git a/app/Model/Project.php b/app/Model/Project.php index 9ee54cbd..9aef3c7e 100644 --- a/app/Model/Project.php +++ b/app/Model/Project.php @@ -227,7 +227,7 @@ class Project extends Base */ public function getByToken($token) { - return $this->db->table(self::TABLE)->eq('token', $token)->findOne(); + return $this->db->table(self::TABLE)->eq('token', $token)->eq('is_public', 1)->findOne(); } /** @@ -245,46 +245,23 @@ class Project extends Base * Get all projects, optionaly fetch stats for each project and can check users permissions * * @access public - * @param bool $fetch_stats If true, return metrics about each projects - * @param bool $check_permissions If true, remove projects not allowed for the current user + * @param bool $filter_permissions If true, remove projects not allowed for the current user * @return array */ - public function getAll($fetch_stats = false, $check_permissions = false) + public function getAll($filter_permissions = false) { - if (! $fetch_stats) { - return $this->db->table(self::TABLE)->asc('name')->findAll(); - } - - $this->db->startTransaction(); - - $projects = $this->db - ->table(self::TABLE) - ->asc('name') - ->findAll(); + $projects = $this->db->table(self::TABLE)->asc('name')->findAll(); - foreach ($projects as $pkey => &$project) { + if ($filter_permissions) { - if ($check_permissions && ! $this->isUserAllowed($project['id'], $this->acl->getUserId())) { - unset($projects[$pkey]); - } - else { - - $columns = $this->board->getcolumns($project['id']); - $project['nb_active_tasks'] = 0; + foreach ($projects as $key => $project) { - foreach ($columns as &$column) { - $column['nb_active_tasks'] = $this->task->countByColumnId($project['id'], $column['id']); - $project['nb_active_tasks'] += $column['nb_active_tasks']; + if (! $this->isUserAllowed($project['id'], $this->acl->getUserId())) { + unset($projects[$key]); } - - $project['columns'] = $columns; - $project['nb_tasks'] = $this->task->countByProjectId($project['id']); - $project['nb_inactive_tasks'] = $project['nb_tasks'] - $project['nb_active_tasks']; } } - $this->db->closeTransaction(); - return $projects; } @@ -383,160 +360,77 @@ class Project extends Base } /** - * Create a project from another one. + * Gather some task metrics for a given project * - * @author Antonio Rabelo - * @param integer $project_id Project Id - * @return integer Cloned Project Id + * @access public + * @param integer $project_id Project id + * @return array */ - public function createProjectFromAnotherProject($project_id) + public function getStats($project_id) { - // Recover the template project data - $project = $this->getById($project_id); - - // Create a Clone project - $clone_project = array( - 'name' => $project['name'].' ('.t('Clone').')', - 'is_active' => true, - 'last_modified' => 0, - 'token' => Security::generateToken(), - ); + $stats = array(); + $columns = $this->board->getcolumns($project_id); + $stats['nb_active_tasks'] = 0; - // Register the cloned project - if (! $this->db->table(self::TABLE)->save($clone_project)) { - return false; + foreach ($columns as &$column) { + $column['nb_active_tasks'] = $this->task->countByColumnId($project_id, $column['id']); + $stats['nb_active_tasks'] += $column['nb_active_tasks']; } - // Get the cloned project Id - return $this->db->getConnection()->getLastId(); - } + $stats['columns'] = $columns; + $stats['nb_tasks'] = $this->task->countByProjectId($project_id); + $stats['nb_inactive_tasks'] = $stats['nb_tasks'] - $stats['nb_active_tasks']; - /** - * Copy Board Columns from a project to another one. - * - * @author Antonio Rabelo - * @param integer $project_from Project Template - * @return integer $project_to Project that receives the copy - * @return boolean - */ - public function copyBoardFromAnotherProject($project_from, $project_to) - { - $columns = $this->db->table(Board::TABLE)->eq('project_id', $project_from)->asc('position')->findAllByColumn('title'); - return $this->board->create($project_to, $columns); + return $stats; } /** - * Copy Categories from a project to another one. + * Create a project from another one. * * @author Antonio Rabelo - * @param integer $project_from Project Template - * @return integer $project_to Project that receives the copy - * @return boolean + * @param integer $project_id Project Id + * @return integer Cloned Project Id */ - public function copyCategoriesFromAnotherProject($project_from, $project_to) + public function createProjectFromAnotherProject($project_id) { - $categoriesTemplate = $this->category->getAll($project_from); - - foreach ($categoriesTemplate as $category) { - - unset($category['id']); - $category['project_id'] = $project_to; - - if (! $this->category->create($category)) { - return false; - } - } + $project_name = $this->db->table(self::TABLE)->eq('id', $project_id)->findOneColumn('name'); - return true; - } - - /** - * Copy User Access from a project to another one. - * - * @author Antonio Rabelo - * @param integer $project_from Project Template - * @return integer $project_to Project that receives the copy - * @return boolean - */ - public function copyUserAccessFromAnotherProject($project_from, $project_to) - { - $usersList = $this->getAllowedUsers($project_from); + $project = array( + 'name' => $project_name.' ('.t('Clone').')', + 'is_active' => true, + 'last_modified' => 0, + 'token' => '', + ); - foreach ($usersList as $id => $userName) { - if (! $this->allowUser($project_to, $id)) { - return false; - } + if (! $this->db->table(self::TABLE)->save($project)) { + return false; } - return true; + return $this->db->getConnection()->getLastId(); } /** - * Copy Actions and related Actions Parameters from a project to another one. + * Copy user access from a project to another one * * @author Antonio Rabelo * @param integer $project_from Project Template * @return integer $project_to Project that receives the copy * @return boolean */ - public function copyActionsFromAnotherProject($project_from, $project_to) + public function duplicateUsers($project_from, $project_to) { - $actionTemplate = $this->action->getAllByProject($project_from); + $users = $this->getAllowedUsers($project_from); - foreach ($actionTemplate as $action) { - - unset($action['id']); - $action['project_id'] = $project_to; - $actionParams = $action['params']; - unset($action['params']); - - if (! $this->db->table(Action::TABLE)->save($action)) { + foreach ($users as $user_id => $name) { + if (! $this->allowUser($project_to, $user_id)) { return false; } - - $action_clone_id = $this->db->getConnection()->getLastId(); - - foreach ($actionParams as $param) { - unset($param['id']); - $param['value'] = $this->resolveValueParamToClonedAction($param, $project_to); - $param['action_id'] = $action_clone_id; - - if (! $this->db->table(Action::TABLE_PARAMS)->save($param)) { - return false; - } - } } return true; } /** - * Resolve type of action value from a project to the respective value in another project. - * - * @author Antonio Rabelo - * @param integer $param A action parameter - * @return integer $project_to Project to find the corresponding values - * @return mixed The corresponding values from $project_to - */ - private function resolveValueParamToClonedAction($param, $project_to) - { - switch($param['name']) { - case 'project_id': - return $project_to; - case 'category_id': - $categoryTemplate = $this->category->getById($param['value']); - $categoryFromNewProject = $this->db->table(Category::TABLE)->eq('project_id', $project_to)->eq('name', $categoryTemplate['name'])->findOne(); - return $categoryFromNewProject['id']; - case 'column_id': - $boardTemplate = $this->board->getColumn($param['value']); - $boardFromNewProject = $this->db->table(Board::TABLE)->eq('project_id', $project_to)->eq('title', $boardTemplate['title'])->findOne(); - return $boardFromNewProject['id']; - default: - return $param['value']; - } - } - - /** * Clone a project * * @author Antonio Rabelo @@ -555,25 +449,25 @@ class Project extends Base } // Clone Board - if (! $this->copyBoardFromAnotherProject($project_id, $clone_project_id)) { + if (! $this->board->duplicate($project_id, $clone_project_id)) { $this->db->cancelTransaction(); return false; } // Clone Categories - if (! $this->copyCategoriesFromAnotherProject($project_id, $clone_project_id)) { + if (! $this->category->duplicate($project_id, $clone_project_id)) { $this->db->cancelTransaction(); return false; } // Clone Allowed Users - if (! $this->copyUserAccessFromAnotherProject($project_id, $clone_project_id)) { + if (! $this->duplicateUsers($project_id, $clone_project_id)) { $this->db->cancelTransaction(); return false; } // Clone Actions - if (! $this->copyActionsFromAnotherProject($project_id, $clone_project_id)) { + if (! $this->action->duplicate($project_id, $clone_project_id)) { $this->db->cancelTransaction(); return false; } @@ -594,7 +488,7 @@ class Project extends Base { $this->db->startTransaction(); - $values['token'] = Security::generateToken(); + $values['token'] = ''; if (! $this->db->table(self::TABLE)->save($values)) { $this->db->cancelTransaction(); @@ -604,10 +498,10 @@ class Project extends Base $project_id = $this->db->getConnection()->getLastId(); $this->board->create($project_id, array( - t('Backlog'), - t('Ready'), - t('Work in progress'), - t('Done'), + array('title' => t('Backlog'), 'task_limit' => 0), + array('title' => t('Ready'), 'task_limit' => 0), + array('title' => t('Work in progress'), 'task_limit' => 0), + array('title' => t('Done'), 'task_limit' => 0), )); $this->db->closeTransaction(); @@ -700,6 +594,36 @@ class Project extends Base } /** + * Enable public access for a project + * + * @access public + * @param integer $project_id Project id + * @return bool + */ + public function enablePublicAccess($project_id) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $project_id) + ->save(array('is_public' => 1, 'token' => Security::generateToken())); + } + + /** + * Disable public access for a project + * + * @access public + * @param integer $project_id Project id + * @return bool + */ + public function disablePublicAccess($project_id) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $project_id) + ->save(array('is_public' => 0, 'token' => '')); + } + + /** * Validate project creation * * @access public @@ -786,4 +710,35 @@ class Project extends Base $this->event->attach($event_name, $listener); } } + + /** + * Get project activity + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getActivity($project_id) + { + $activity = array(); + $tasks = $this->taskHistory->getAllContentByProjectId($project_id, 25); + $comments = $this->commentHistory->getAllContentByProjectId($project_id, 25); + $subtasks = $this->subtaskHistory->getAllContentByProjectId($project_id, 25); + + foreach ($tasks as &$task) { + $activity[$task['date_creation'].'-'.$task['id']] = $task; + } + + foreach ($subtasks as &$subtask) { + $activity[$subtask['date_creation'].'-'.$subtask['id']] = $subtask; + } + + foreach ($comments as &$comment) { + $activity[$comment['date_creation'].'-'.$comment['id']] = $comment; + } + + krsort($activity); + + return $activity; + } } diff --git a/app/Model/SubTask.php b/app/Model/SubTask.php index 9f2941c5..011c58e7 100644 --- a/app/Model/SubTask.php +++ b/app/Model/SubTask.php @@ -121,13 +121,12 @@ class SubTask extends Base } /** - * Create + * Prepare data before insert/update * * @access public * @param array $values Form values - * @return bool */ - public function create(array $values) + public function prepare(array &$values) { if (isset($values['another_subtask'])) { unset($values['another_subtask']); @@ -140,7 +139,18 @@ class SubTask extends Base if (isset($values['time_spent']) && empty($values['time_spent'])) { $values['time_spent'] = 0; } + } + /** + * Create + * + * @access public + * @param array $values Form values + * @return bool + */ + public function create(array $values) + { + $this->prepare($values); $result = $this->db->table(self::TABLE)->save($values); if ($result) { @@ -160,14 +170,7 @@ class SubTask extends Base */ public function update(array $values) { - if (isset($values['time_estimated']) && empty($values['time_estimated'])) { - $values['time_estimated'] = 0; - } - - if (isset($values['time_spent']) && empty($values['time_spent'])) { - $values['time_spent'] = 0; - } - + $this->prepare($values); $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values); if ($result) { @@ -190,6 +193,34 @@ class SubTask extends Base } /** + * Duplicate all subtasks to another task + * + * @access public + * @param integer $src_task_id Source task id + * @param integer $dst_task_id Destination task id + * @return bool + */ + public function duplicate($src_task_id, $dst_task_id) + { + $subtasks = $this->db->table(self::TABLE) + ->columns('title', 'time_estimated') + ->eq('task_id', $src_task_id) + ->findAll(); + + foreach ($subtasks as &$subtask) { + + $subtask['task_id'] = $dst_task_id; + $subtask['time_spent'] = 0; + + if (! $this->db->table(self::TABLE)->save($subtask)) { + return false; + } + } + + return true; + } + + /** * Validate creation/modification * * @access public diff --git a/app/Model/SubtaskHistory.php b/app/Model/SubtaskHistory.php new file mode 100644 index 00000000..89076261 --- /dev/null +++ b/app/Model/SubtaskHistory.php @@ -0,0 +1,161 @@ +<?php + +namespace Model; + +use PDO; +use Core\Registry; +use Event\SubtaskHistoryListener; + +/** + * Comment history model + * + * @package model + * @author Frederic Guillot + */ +class SubtaskHistory extends BaseHistory +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'subtask_has_events'; + + /** + * Maximum number of events + * + * @var integer + */ + const MAX_EVENTS = 5000; + + /** + * Constructor + * + * @access public + * @param \Core\Registry $registry Registry instance + */ + public function __construct(Registry $registry) + { + parent::__construct($registry); + $this->table = self::TABLE; + } + + /** + * Create a new event + * + * @access public + * @param integer $project_id Project id + * @param integer $task_id Task id + * @param integer $subtask_id Subtask id + * @param integer $creator_id Author of the event (user id) + * @param string $event_name Task event name + * @param string $data Current comment + * @return boolean + */ + public function create($project_id, $task_id, $subtask_id, $creator_id, $event_name, $data) + { + $values = array( + 'project_id' => $project_id, + 'task_id' => $task_id, + 'subtask_id' => $subtask_id, + 'creator_id' => $creator_id, + 'event_name' => $event_name, + 'date_creation' => time(), + 'data' => $data, + ); + + $this->db->startTransaction(); + + $this->cleanup(self::MAX_EVENTS - 1); + $result = $this->db->table(self::TABLE)->insert($values); + + $this->db->closeTransaction(); + + return $result; + } + + /** + * Get all necessary content to display activity feed + * + * $author_name + * $author_username + * $task['id', 'title', 'position', 'column_name'] + */ + public function getAllContentByProjectId($project_id, $limit = 50) + { + $sql = ' + SELECT + subtask_has_events.id, + subtask_has_events.date_creation, + subtask_has_events.event_name, + subtask_has_events.task_id, + tasks.title as task_title, + users.username as author_username, + users.name as author_name, + assignees.name as subtask_assignee_name, + assignees.username as subtask_assignee_username, + task_has_subtasks.title as subtask_title, + task_has_subtasks.status as subtask_status, + task_has_subtasks.time_spent as subtask_time_spent, + task_has_subtasks.time_estimated as subtask_time_estimated + FROM subtask_has_events + LEFT JOIN users ON users.id=subtask_has_events.creator_id + LEFT JOIN tasks ON tasks.id=subtask_has_events.task_id + LEFT JOIN task_has_subtasks ON task_has_subtasks.id=subtask_has_events.subtask_id + LEFT JOIN users AS assignees ON assignees.id=task_has_subtasks.user_id + WHERE subtask_has_events.project_id = ? + ORDER BY subtask_has_events.id DESC + LIMIT '.$limit.' OFFSET 0 + '; + + $rq = $this->db->execute($sql, array($project_id)); + $events = $rq->fetchAll(PDO::FETCH_ASSOC); + + foreach ($events as &$event) { + $event['author'] = $event['author_name'] ?: $event['author_username']; + $event['subtask_assignee'] = $event['subtask_assignee_name'] ?: $event['subtask_assignee_username']; + $event['subtask_status_list'] = $this->subTask->getStatusList(); + $event['event_title'] = $this->getTitle($event); + $event['event_content'] = $this->getContent($event); + $event['event_type'] = 'subtask'; + } + + return $events; + } + + /** + * Get the event title (translated) + * + * @access public + * @param array $event Event properties + * @return string + */ + public function getTitle(array $event) + { + $titles = array( + SubTask::EVENT_UPDATE => t('%s updated a subtask for the task #%d', $event['author'], $event['task_id']), + SubTask::EVENT_CREATE => t('%s created a subtask for the task #%d', $event['author'], $event['task_id']), + ); + + return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : ''; + } + + /** + * Attach events to be able to record the history + * + * @access public + */ + public function attachEvents() + { + $events = array( + SubTask::EVENT_UPDATE, + SubTask::EVENT_CREATE, + ); + + $listener = new SubtaskHistoryListener($this); + + foreach ($events as $event_name) { + $this->event->attach($event_name, $listener); + } + } +} diff --git a/app/Model/Task.php b/app/Model/Task.php index 09c77573..10d125d4 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -263,88 +263,97 @@ class Task extends Base } /** - * Duplicate a task + * Generic method to duplicate a task * * @access public - * @param integer $task_id Task id - * @return boolean + * @param array $task Task data + * @param array $override Task properties to override + * @return integer|boolean */ - public function duplicate($task_id) + public function copy(array $task, array $override = array()) { + // Values to override + if (! empty($override)) { + $task = $override + $task; + } + $this->db->startTransaction(); - // Get the original task - $task = $this->getById($task_id); + // Assign new values + $values = array(); + $values['title'] = $task['title']; + $values['description'] = $task['description']; + $values['date_creation'] = time(); + $values['date_modification'] = $values['date_creation']; + $values['date_due'] = $task['date_due']; + $values['color_id'] = $task['color_id']; + $values['project_id'] = $task['project_id']; + $values['column_id'] = $task['column_id']; + $values['owner_id'] = 0; + $values['creator_id'] = $task['creator_id']; + $values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']) + 1; + $values['score'] = $task['score']; + $values['category_id'] = 0; - // Cleanup data - unset($task['id']); - unset($task['date_completed']); + // Check if the assigned user is allowed for the new project + if ($task['owner_id'] && $this->project->isUserAllowed($values['project_id'], $task['owner_id'])) { + $values['owner_id'] = $task['owner_id']; + } - // Assign new values - $task['date_creation'] = time(); - $task['is_active'] = 1; - $task['position'] = $this->countByColumnId($task['project_id'], $task['column_id']); + // Check if the category exists + if ($task['category_id'] && $this->category->exists($task['category_id'], $task['project_id'])) { + $values['category_id'] = $task['category_id']; + } // Save task - if (! $this->db->table(self::TABLE)->save($task)) { + if (! $this->db->table(self::TABLE)->save($values)) { $this->db->cancelTransaction(); return false; } $task_id = $this->db->getConnection()->getLastId(); + // Duplicate subtasks + if (! $this->subTask->duplicate($task['id'], $task_id)) { + $this->db->cancelTransaction(); + return false; + } + $this->db->closeTransaction(); // Trigger events - $this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $task); - $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task); + $this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $values); + $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $values); return $task_id; } /** - * Duplicate a task to another project (always copy to the first column) + * Duplicate a task to the same project * * @access public - * @param integer $task_id Task id - * @param integer $project_id Destination project id - * @return boolean + * @param array $task Task data + * @return integer|boolean */ - public function duplicateToAnotherProject($task_id, $project_id) + public function duplicateSameProject($task) { - $this->db->startTransaction(); - - // Get the original task - $task = $this->getById($task_id); - - // Cleanup data - unset($task['id']); - unset($task['date_completed']); - - // Assign new values - $task['date_creation'] = time(); - $task['owner_id'] = 0; - $task['category_id'] = 0; - $task['is_active'] = 1; - $task['column_id'] = $this->board->getFirstColumn($project_id); - $task['project_id'] = $project_id; - $task['position'] = $this->countByColumnId($task['project_id'], $task['column_id']); - - // Save task - if (! $this->db->table(self::TABLE)->save($task)) { - $this->db->cancelTransaction(); - return false; - } - - $task_id = $this->db->getConnection()->getLastId(); - - $this->db->closeTransaction(); - - // Trigger events - $this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $task); - $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task); + return $this->copy($task); + } - return $task_id; + /** + * Duplicate a task to another project (always copy to the first column) + * + * @access public + * @param integer $project_id Destination project id + * @param array $task Task data + * @return integer|boolean + */ + public function duplicateToAnotherProject($project_id, array $task) + { + return $this->copy($task, array( + 'project_id' => $project_id, + 'column_id' => $this->board->getFirstColumn($project_id), + )); } /** @@ -386,9 +395,19 @@ class Task extends Base // Prepare data $this->prepare($values); + + if (empty($values['column_id'])) { + $values['column_id'] = $this->board->getFirstColumn($values['project_id']); + } + + if (empty($values['color_id'])) { + $colors = $this->getColors(); + $values['color_id'] = key($colors); + } + $values['date_creation'] = time(); $values['date_modification'] = $values['date_creation']; - $values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']); + $values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']) + 1; // Save task if (! $this->db->table(self::TABLE)->save($values)) { @@ -412,36 +431,28 @@ class Task extends Base * * @access public * @param array $values Form values - * @param boolean $trigger_events Flag to trigger events * @return boolean */ - public function update(array $values, $trigger_events = true) + public function update(array $values) { // Fetch original task $original_task = $this->getById($values['id']); - if ($original_task === false) { + if (! $original_task) { return false; } // Prepare data $this->prepare($values); $updated_task = $values; + $updated_task['date_modification'] = time(); unset($updated_task['id']); - // We update the modification date only for the selected task to highlight recent moves - if ($trigger_events) { - $updated_task['date_modification'] = time(); - } - - $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($updated_task); - - // Trigger events - if ($result && $trigger_events) { + if ($this->db->table(self::TABLE)->eq('id', $values['id'])->update($updated_task)) { $this->triggerUpdateEvents($original_task, $updated_task); } - return $result; + return true; } /** @@ -453,10 +464,7 @@ class Task extends Base */ public function triggerUpdateEvents(array $original_task, array $updated_task) { - $events = array( - self::EVENT_CREATE_UPDATE, - self::EVENT_UPDATE, - ); + $events = array(); if (isset($updated_task['column_id']) && $original_task['column_id'] != $updated_task['column_id']) { $events[] = self::EVENT_MOVE_COLUMN; @@ -464,6 +472,10 @@ class Task extends Base else if (isset($updated_task['position']) && $original_task['position'] != $updated_task['position']) { $events[] = self::EVENT_MOVE_POSITION; } + else { + $events[] = self::EVENT_CREATE_UPDATE; + $events[] = self::EVENT_UPDATE; + } $event_data = array_merge($original_task, $updated_task); $event_data['task_id'] = $original_task['id']; @@ -539,23 +551,125 @@ class Task extends Base * Move a task to another column or to another position * * @access public + * @param integer $project_id Project id * @param integer $task_id Task id * @param integer $column_id Column id - * @param integer $position Position (must be greater than 1) - * @param boolean $trigger_events Flag to trigger events + * @param integer $position Position (must be >= 1) * @return boolean */ - public function move($task_id, $column_id, $position, $trigger_events = true) + public function movePosition($project_id, $task_id, $column_id, $position) { - $this->event->clearTriggeredEvents(); + // The position can't be lower than 1 + if ($position < 1) { + return false; + } - $values = array( - 'id' => $task_id, - 'column_id' => $column_id, - 'position' => $position, - ); + $board = $this->db->table(Board::TABLE)->eq('project_id', $project_id)->asc('position')->findAllByColumn('id'); + $columns = array(); + + // Prepare the columns + foreach ($board as $board_column_id) { + + $columns[$board_column_id] = $this->db->table(self::TABLE) + ->eq('is_active', 1) + ->eq('project_id', $project_id) + ->eq('column_id', $board_column_id) + ->neq('id', $task_id) + ->asc('position') + ->findAllByColumn('id'); + } + + // The column must exists + if (! isset($columns[$column_id])) { + return false; + } + + // We put our task to the new position + array_splice($columns[$column_id], $position - 1, 0, $task_id); // print_r($columns); + + // We save the new positions for all tasks + return $this->savePositions($task_id, $columns); + } + + /** + * Save task positions + * + * @access private + * @param integer $moved_task_id Id of the moved task + * @param array $columns Sorted tasks + * @return boolean + */ + private function savePositions($moved_task_id, array $columns) + { + $this->db->startTransaction(); + + foreach ($columns as $column_id => $column) { + + $position = 1; + + foreach ($column as $task_id) { + + if ($task_id == $moved_task_id) { + + // Events will be triggered only for that task + $result = $this->update(array( + 'id' => $task_id, + 'position' => $position, + 'column_id' => $column_id + )); + } + else { + $result = $this->db->table(self::TABLE)->eq('id', $task_id)->update(array( + 'position' => $position, + 'column_id' => $column_id + )); + } + + $position++; - return $this->update($values, $trigger_events); + if (! $result) { + $this->db->cancelTransaction(); + return false; + } + } + } + + $this->db->closeTransaction(); + + return true; + } + + /** + * Move a task to another project + * + * @access public + * @param integer $project_id Project id + * @param array $task Task data + * @return boolean + */ + public function moveToAnotherProject($project_id, array $task) + { + $values = array(); + + // Clear values (categories are different for each project) + $values['category_id'] = 0; + $values['owner_id'] = 0; + + // Check if the assigned user is allowed for the new project + if ($task['owner_id'] && $this->project->isUserAllowed($project_id, $task['owner_id'])) { + $values['owner_id'] = $task['owner_id']; + } + + // We use the first column of the new project + $values['column_id'] = $this->board->getFirstColumn($project_id); + $values['position'] = $this->countByColumnId($project_id, $values['column_id']) + 1; + $values['project_id'] = $project_id; + + if ($this->db->table(self::TABLE)->eq('id', $task['id'])->update($values)) { + return $task['id']; + } + + return false; } /** @@ -568,10 +682,8 @@ class Task extends Base public function validateCreation(array $values) { $v = new Validator($values, array( - new Validators\Required('color_id', t('The color is required')), new Validators\Required('project_id', t('The project is required')), new Validators\Integer('project_id', t('This value must be an integer')), - new Validators\Required('column_id', t('The column is required')), new Validators\Integer('column_id', t('This value must be an integer')), new Validators\Integer('owner_id', t('This value must be an integer')), new Validators\Integer('creator_id', t('This value must be an integer')), @@ -620,14 +732,10 @@ class Task extends Base $v = new Validator($values, array( new Validators\Required('id', t('The id is required')), new Validators\Integer('id', t('This value must be an integer')), - new Validators\Required('color_id', t('The color is required')), - new Validators\Required('project_id', t('The project is required')), new Validators\Integer('project_id', t('This value must be an integer')), - new Validators\Required('column_id', t('The column is required')), new Validators\Integer('column_id', t('This value must be an integer')), new Validators\Integer('owner_id', t('This value must be an integer')), new Validators\Integer('score', t('This value must be an integer')), - new Validators\Required('title', t('The title is required')), new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200), new Validators\Date('date_due', t('Invalid date'), $this->getDateFormats()), )); @@ -663,6 +771,52 @@ class Task extends Base } /** + * Validate category change + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCategoryModification(array $values) + { + $v = new Validator($values, array( + new Validators\Required('id', t('The id is required')), + new Validators\Integer('id', t('This value must be an integer')), + new Validators\Required('project_id', t('The project is required')), + new Validators\Integer('project_id', t('This value must be an integer')), + new Validators\Required('category_id', t('This value is required')), + new Validators\Integer('category_id', t('This value must be an integer')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate project modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateProjectModification(array $values) + { + $v = new Validator($values, array( + new Validators\Required('id', t('The id is required')), + new Validators\Integer('id', t('This value must be an integer')), + new Validators\Required('project_id', t('The project is required')), + new Validators\Integer('project_id', t('This value must be an integer')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** * Return a timestamp if the given date format is correct otherwise return 0 * * @access public @@ -782,21 +936,21 @@ class Task extends Base $tasks = $rq->fetchAll(PDO::FETCH_ASSOC); $columns = array( - t('Task Id'), - t('Project'), - t('Status'), - t('Category'), - t('Column'), - t('Position'), - t('Color'), - t('Due date'), - t('Creator'), - t('Assignee'), - t('Complexity'), - t('Title'), - t('Creation date'), - t('Modification date'), - t('Completion date'), + e('Task Id'), + e('Project'), + e('Status'), + e('Category'), + e('Column'), + e('Position'), + e('Color'), + e('Due date'), + e('Creator'), + e('Assignee'), + e('Complexity'), + e('Title'), + e('Creation date'), + e('Modification date'), + e('Completion date'), ); $results = array($columns); @@ -819,7 +973,7 @@ class Task extends Base { $colors = $this->getColors(); $task['score'] = $task['score'] ?: ''; - $task['is_active'] = $task['is_active'] == self::STATUS_OPEN ? t('Open') : t('Closed'); + $task['is_active'] = $task['is_active'] == self::STATUS_OPEN ? e('Open') : e('Closed'); $task['color_id'] = $colors[$task['color_id']]; $task['date_creation'] = date('Y-m-d', $task['date_creation']); $task['date_due'] = $task['date_due'] ? date('Y-m-d', $task['date_due']) : ''; diff --git a/app/Model/TaskHistory.php b/app/Model/TaskHistory.php new file mode 100644 index 00000000..c81e3eb4 --- /dev/null +++ b/app/Model/TaskHistory.php @@ -0,0 +1,158 @@ +<?php + +namespace Model; + +use PDO; +use Core\Registry; +use Event\TaskHistoryListener; + +/** + * Task history model + * + * @package model + * @author Frederic Guillot + */ +class TaskHistory extends BaseHistory +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_events'; + + /** + * Maximum number of events + * + * @var integer + */ + const MAX_EVENTS = 5000; + + /** + * Constructor + * + * @access public + * @param \Core\Registry $registry Registry instance + */ + public function __construct(Registry $registry) + { + parent::__construct($registry); + $this->table = self::TABLE; + } + + /** + * Create a new event + * + * @access public + * @param integer $project_id Project id + * @param integer $task_id Task id + * @param integer $creator_id Author of the event (user id) + * @param string $event_name Task event name + * @return boolean + */ + public function create($project_id, $task_id, $creator_id, $event_name) + { + $values = array( + 'project_id' => $project_id, + 'task_id' => $task_id, + 'creator_id' => $creator_id, + 'event_name' => $event_name, + 'date_creation' => time(), + ); + + $this->db->startTransaction(); + + $this->cleanup(self::MAX_EVENTS - 1); + $result = $this->db->table(self::TABLE)->insert($values); + + $this->db->closeTransaction(); + + return $result; + } + + /** + * Get all necessary content to display activity feed + * + * $author_name + * $author_username + * $task['id', 'title', 'position', 'column_name'] + */ + public function getAllContentByProjectId($project_id, $limit = 50) + { + $sql = ' + SELECT + task_has_events.id, + task_has_events.date_creation, + task_has_events.event_name, + task_has_events.task_id, + tasks.title as task_title, + tasks.position as task_position, + columns.title as task_column_name, + users.username as author_username, + users.name as author_name + FROM task_has_events + LEFT JOIN users ON users.id=task_has_events.creator_id + LEFT JOIN tasks ON tasks.id=task_has_events.task_id + LEFT JOIN columns ON columns.id=tasks.column_id + WHERE task_has_events.project_id = ? + ORDER BY task_has_events.id DESC + LIMIT '.$limit.' OFFSET 0 + '; + + $rq = $this->db->execute($sql, array($project_id)); + $events = $rq->fetchAll(PDO::FETCH_ASSOC); + + foreach ($events as &$event) { + $event['author'] = $event['author_name'] ?: $event['author_username']; + $event['event_title'] = $this->getTitle($event); + $event['event_content'] = $this->getContent($event); + $event['event_type'] = 'task'; + } + + return $events; + } + + /** + * Get the event title (translated) + * + * @access public + * @param array $event Event properties + * @return string + */ + public function getTitle(array $event) + { + $titles = array( + Task::EVENT_UPDATE => t('%s updated the task #%d', $event['author'], $event['task_id']), + Task::EVENT_CREATE => t('%s created the task #%d', $event['author'], $event['task_id']), + Task::EVENT_CLOSE => t('%s closed the task #%d', $event['author'], $event['task_id']), + Task::EVENT_OPEN => t('%s open the task #%d', $event['author'], $event['task_id']), + Task::EVENT_MOVE_COLUMN => t('%s moved the task #%d to the column "%s"', $event['author'], $event['task_id'], $event['task_column_name']), + Task::EVENT_MOVE_POSITION => t('%s moved the task #%d to the position %d in the column "%s"', $event['author'], $event['task_id'], $event['task_position'], $event['task_column_name']), + ); + + return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : ''; + } + + /** + * Attach events to be able to record the history + * + * @access public + */ + public function attachEvents() + { + $events = array( + Task::EVENT_UPDATE, + Task::EVENT_CREATE, + Task::EVENT_CLOSE, + Task::EVENT_OPEN, + Task::EVENT_MOVE_COLUMN, + Task::EVENT_MOVE_POSITION, + ); + + $listener = new TaskHistoryListener($this); + + foreach ($events as $event_name) { + $this->event->attach($event_name, $listener); + } + } +} diff --git a/app/Model/User.php b/app/Model/User.php index cfabd342..918ecf2a 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -86,7 +86,7 @@ class User extends Base return $this->db ->table(self::TABLE) ->asc('username') - ->columns('id', 'username', 'name', 'email', 'is_admin', 'default_project_id', 'is_ldap_user') + ->columns('id', 'username', 'name', 'email', 'is_admin', 'default_project_id', 'is_ldap_user', 'notifications_enabled', 'google_id', 'github_id') ->findAll(); } @@ -291,25 +291,17 @@ class User extends Base { $v = new Validator($values, array( new Validators\Required('id', t('The user id is required')), - new Validators\Required('username', t('The username is required')), - new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), - new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), self::TABLE, 'id'), new Validators\Required('current_password', t('The current password is required')), new Validators\Required('password', t('The password is required')), new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6), new Validators\Required('confirmation', t('The confirmation is required')), new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')), - new Validators\Integer('default_project_id', t('This value must be an integer')), - new Validators\Integer('is_admin', t('This value must be an integer')), - new Validators\Email('email', t('Email address invalid')), )); if ($v->execute()) { // Check password - list($authenticated,) = $this->authenticate($_SESSION['user']['username'], $values['current_password']); - - if ($authenticated) { + if ($this->authentication->authenticate($_SESSION['user']['username'], $values['current_password'])) { return array(true, array()); } else { diff --git a/app/Model/Webhook.php b/app/Model/Webhook.php index 872031cc..e03bdcb4 100644 --- a/app/Model/Webhook.php +++ b/app/Model/Webhook.php @@ -104,15 +104,7 @@ class Webhook extends Base */ public function attachCreateEvents() { - $events = array( - Task::EVENT_CREATE, - ); - - $listener = new WebhookListener($this->url_task_creation, $this); - - foreach ($events as $event_name) { - $this->event->attach($event_name, $listener); - } + $this->event->attach(Task::EVENT_CREATE, new WebhookListener($this->url_task_creation, $this)); } /** diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 8f3ae5a1..2d3f993e 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -4,7 +4,67 @@ namespace Schema; use Core\Security; -const VERSION = 23; +const VERSION = 25; + +function version_25($pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_events ( + id INT NOT NULL AUTO_INCREMENT, + date_creation INT NOT NULL, + event_name TEXT NOT NULL, + creator_id INT, + project_id INT, + task_id INT, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE subtask_has_events ( + id INT NOT NULL AUTO_INCREMENT, + date_creation INT NOT NULL, + event_name TEXT NOT NULL, + creator_id INT, + project_id INT, + subtask_id INT, + task_id INT, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE comment_has_events ( + id INT NOT NULL AUTO_INCREMENT, + date_creation INT NOT NULL, + event_name TEXT NOT NULL, + creator_id INT, + project_id INT, + comment_id INT, + task_id INT, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(comment_id) REFERENCES comments(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_24($pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN is_public TINYINT(1) DEFAULT '0'"); +} function version_23($pdo) { @@ -52,7 +112,7 @@ function version_18($pdo) status INT DEFAULT 0, time_estimated INT DEFAULT 0, time_spent INT DEFAULT 0, - task_id INT, + task_id INT NOT NULL, user_id INT, PRIMARY KEY (id), FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE @@ -140,46 +200,6 @@ function version_12($pdo) ); } -function version_11($pdo) -{ -} - -function version_10($pdo) -{ -} - -function version_9($pdo) -{ -} - -function version_8($pdo) -{ -} - -function version_7($pdo) -{ -} - -function version_6($pdo) -{ -} - -function version_5($pdo) -{ -} - -function version_4($pdo) -{ -} - -function version_3($pdo) -{ -} - -function version_2($pdo) -{ -} - function version_1($pdo) { $pdo->exec(" diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index ce77a4ed..b58b9bb3 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -4,7 +4,64 @@ namespace Schema; use Core\Security; -const VERSION = 4; +const VERSION = 6; + +function version_6($pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_events ( + id SERIAL PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); + + $pdo->exec(" + CREATE TABLE subtask_has_events ( + id SERIAL PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + subtask_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); + + $pdo->exec(" + CREATE TABLE comment_has_events ( + id SERIAL PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + comment_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(comment_id) REFERENCES comments(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); +} + +function version_5($pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN is_public BOOLEAN DEFAULT '0'"); +} function version_4($pdo) { @@ -138,7 +195,7 @@ function version_1($pdo) status SMALLINT DEFAULT 0, time_estimated INTEGER DEFAULT 0, time_spent INTEGER DEFAULT 0, - task_id INTEGER, + task_id INTEGER NOT NULL, user_id INTEGER, FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE ); diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index c3a3f10e..ecd62c97 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -4,7 +4,64 @@ namespace Schema; use Core\Security; -const VERSION = 23; +const VERSION = 25; + +function version_25($pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_events ( + id INTEGER PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); + + $pdo->exec(" + CREATE TABLE subtask_has_events ( + id INTEGER PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + subtask_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); + + $pdo->exec(" + CREATE TABLE comment_has_events ( + id INTEGER PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + comment_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(comment_id) REFERENCES comments(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); +} + +function version_24($pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN is_public INTEGER DEFAULT "0"'); +} function version_23($pdo) { @@ -52,7 +109,7 @@ function version_18($pdo) status INTEGER DEFAULT 0, time_estimated INTEGER DEFAULT 0, time_spent INTEGER DEFAULT 0, - task_id INTEGER, + task_id INTEGER NOT NULL, user_id INTEGER, FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE )" @@ -238,19 +295,6 @@ function version_4($pdo) function version_3($pdo) { $pdo->exec('ALTER TABLE projects ADD COLUMN token TEXT'); - - // For each existing project, assign a different token - $rq = $pdo->prepare("SELECT id FROM projects WHERE token IS NULL"); - $rq->execute(); - $results = $rq->fetchAll(\PDO::FETCH_ASSOC); - - if ($results !== false) { - - foreach ($results as &$result) { - $rq = $pdo->prepare('UPDATE projects SET token=? WHERE id=?'); - $rq->execute(array(Security::generateToken(), $result['id'])); - } - } } function version_2($pdo) diff --git a/app/Templates/action_index.php b/app/Templates/action_index.php index 36c333a9..c21395fd 100644 --- a/app/Templates/action_index.php +++ b/app/Templates/action_index.php @@ -1,77 +1,70 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2> - <ul> - <li><a href="?controller=project"><?= t('All projects') ?></a></li> - </ul> - </div> - <section> +<div class="page-header"> + <h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2> +</div> - <?php if (! empty($actions)): ?> +<?php if (! empty($actions)): ?> - <h3><?= t('Defined actions') ?></h3> - <table> - <tr> - <th><?= t('Event name') ?></th> - <th><?= t('Action name') ?></th> - <th><?= t('Action parameters') ?></th> - <th><?= t('Action') ?></th> - </tr> +<h3><?= t('Defined actions') ?></h3> +<table> + <tr> + <th><?= t('Event name') ?></th> + <th><?= t('Action name') ?></th> + <th><?= t('Action parameters') ?></th> + <th><?= t('Action') ?></th> + </tr> - <?php foreach ($actions as $action): ?> - <tr> - <td><?= Helper\in_list($action['event_name'], $available_events) ?></td> - <td><?= Helper\in_list($action['action_name'], $available_actions) ?></td> - <td> - <ul> - <?php foreach ($action['params'] as $param): ?> - <li> - <?= Helper\in_list($param['name'], $available_params) ?> = - <strong> - <?php if (Helper\contains($param['name'], 'column_id')): ?> - <?= Helper\in_list($param['value'], $columns_list) ?> - <?php elseif (Helper\contains($param['name'], 'user_id')): ?> - <?= Helper\in_list($param['value'], $users_list) ?> - <?php elseif (Helper\contains($param['name'], 'project_id')): ?> - <?= Helper\in_list($param['value'], $projects_list) ?> - <?php elseif (Helper\contains($param['name'], 'color_id')): ?> - <?= Helper\in_list($param['value'], $colors_list) ?> - <?php elseif (Helper\contains($param['name'], 'category_id')): ?> - <?= Helper\in_list($param['value'], $categories_list) ?> - <?php endif ?> - </strong> - </li> - <?php endforeach ?> - </ul> - </td> - <td> - <a href="?controller=action&action=confirm&action_id=<?= $action['id'] ?>"><?= t('Remove') ?></a> - </td> - </tr> - <?php endforeach ?> + <?php foreach ($actions as $action): ?> + <tr> + <td><?= Helper\in_list($action['event_name'], $available_events) ?></td> + <td><?= Helper\in_list($action['action_name'], $available_actions) ?></td> + <td> + <ul> + <?php foreach ($action['params'] as $param): ?> + <li> + <?= Helper\in_list($param['name'], $available_params) ?> = + <strong> + <?php if (Helper\contains($param['name'], 'column_id')): ?> + <?= Helper\in_list($param['value'], $columns_list) ?> + <?php elseif (Helper\contains($param['name'], 'user_id')): ?> + <?= Helper\in_list($param['value'], $users_list) ?> + <?php elseif (Helper\contains($param['name'], 'project_id')): ?> + <?= Helper\in_list($param['value'], $projects_list) ?> + <?php elseif (Helper\contains($param['name'], 'color_id')): ?> + <?= Helper\in_list($param['value'], $colors_list) ?> + <?php elseif (Helper\contains($param['name'], 'category_id')): ?> + <?= Helper\in_list($param['value'], $categories_list) ?> + <?php endif ?> + </strong> + </li> + <?php endforeach ?> + </ul> + </td> + <td> + <a href="?controller=action&action=confirm&project_id=<?= $project['id'] ?>&action_id=<?= $action['id'] ?>"><?= t('Remove') ?></a> + </td> + </tr> + <?php endforeach ?> - </table> +</table> - <?php endif ?> +<?php endif ?> - <h3><?= t('Add an action') ?></h3> - <form method="post" action="?controller=action&action=params&project_id=<?= $project['id'] ?>" autocomplete="off"> - <?= Helper\form_csrf() ?> - <?= Helper\form_hidden('project_id', $values) ?> +<h3><?= t('Add an action') ?></h3> +<form method="post" action="?controller=action&action=params&project_id=<?= $project['id'] ?>" autocomplete="off"> + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('project_id', $values) ?> - <?= Helper\form_label(t('Event'), 'event_name') ?> - <?= Helper\form_select('event_name', $available_events, $values) ?><br/> + <?= Helper\form_label(t('Event'), 'event_name') ?> + <?= Helper\form_select('event_name', $available_events, $values) ?><br/> - <?= Helper\form_label(t('Action'), 'action_name') ?> - <?= Helper\form_select('action_name', $available_actions, $values) ?><br/> + <?= Helper\form_label(t('Action'), 'action_name') ?> + <?= Helper\form_select('action_name', $available_actions, $values) ?><br/> - <div class="form-help"> - <?= t('When the selected event occurs execute the corresponding action.') ?> - </div> + <div class="form-help"> + <?= t('When the selected event occurs execute the corresponding action.') ?> + </div> - <div class="form-actions"> - <input type="submit" value="<?= t('Next step') ?>" class="btn btn-blue"/> - </div> - </form> - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Next step') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/action_params.php b/app/Templates/action_params.php index da685860..92d16288 100644 --- a/app/Templates/action_params.php +++ b/app/Templates/action_params.php @@ -1,43 +1,37 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2> - <ul> - <li><a href="?controller=project"><?= t('All projects') ?></a></li> - </ul> - </div> - <section> +<div class="page-header"> + <h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2> +</div> +<section> - <h3><?= t('Define action parameters') ?></h3> - <form method="post" action="?controller=action&action=create&project_id=<?= $project['id'] ?>" autocomplete="off"> - <?= Helper\form_csrf() ?> - <?= Helper\form_hidden('project_id', $values) ?> - <?= Helper\form_hidden('event_name', $values) ?> - <?= Helper\form_hidden('action_name', $values) ?> +<h3><?= t('Define action parameters') ?></h3> +<form method="post" action="?controller=action&action=create&project_id=<?= $project['id'] ?>" autocomplete="off"> + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('project_id', $values) ?> + <?= Helper\form_hidden('event_name', $values) ?> + <?= Helper\form_hidden('action_name', $values) ?> - <?php foreach ($action_params as $param_name => $param_desc): ?> + <?php foreach ($action_params as $param_name => $param_desc): ?> - <?php if (Helper\contains($param_name, 'column_id')): ?> - <?= Helper\form_label($param_desc, $param_name) ?> - <?= Helper\form_select('params['.$param_name.']', $columns_list, $values) ?><br/> - <?php elseif (Helper\contains($param_name, 'user_id')): ?> - <?= Helper\form_label($param_desc, $param_name) ?> - <?= Helper\form_select('params['.$param_name.']', $users_list, $values) ?><br/> - <?php elseif (Helper\contains($param_name, 'project_id')): ?> - <?= Helper\form_label($param_desc, $param_name) ?> - <?= Helper\form_select('params['.$param_name.']', $projects_list, $values) ?><br/> - <?php elseif (Helper\contains($param_name, 'color_id')): ?> - <?= Helper\form_label($param_desc, $param_name) ?> - <?= Helper\form_select('params['.$param_name.']', $colors_list, $values) ?><br/> - <?php elseif (Helper\contains($param_name, 'category_id')): ?> - <?= Helper\form_label($param_desc, $param_name) ?> - <?= Helper\form_select('params['.$param_name.']', $categories_list, $values) ?><br/> - <?php endif ?> - <?php endforeach ?> + <?php if (Helper\contains($param_name, 'column_id')): ?> + <?= Helper\form_label($param_desc, $param_name) ?> + <?= Helper\form_select('params['.$param_name.']', $columns_list, $values) ?><br/> + <?php elseif (Helper\contains($param_name, 'user_id')): ?> + <?= Helper\form_label($param_desc, $param_name) ?> + <?= Helper\form_select('params['.$param_name.']', $users_list, $values) ?><br/> + <?php elseif (Helper\contains($param_name, 'project_id')): ?> + <?= Helper\form_label($param_desc, $param_name) ?> + <?= Helper\form_select('params['.$param_name.']', $projects_list, $values) ?><br/> + <?php elseif (Helper\contains($param_name, 'color_id')): ?> + <?= Helper\form_label($param_desc, $param_name) ?> + <?= Helper\form_select('params['.$param_name.']', $colors_list, $values) ?><br/> + <?php elseif (Helper\contains($param_name, 'category_id')): ?> + <?= Helper\form_label($param_desc, $param_name) ?> + <?= Helper\form_select('params['.$param_name.']', $categories_list, $values) ?><br/> + <?php endif ?> + <?php endforeach ?> - <div class="form-actions"> - <input type="submit" value="<?= t('Save this action') ?>" class="btn btn-blue"/> - <?= t('or') ?> <a href="?controller=action&action=index&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a> - </div> - </form> - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Save this action') ?>" class="btn btn-blue"/> + <?= t('or') ?> <a href="?controller=action&action=index&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/action_remove.php b/app/Templates/action_remove.php index 13679eab..4b574f11 100644 --- a/app/Templates/action_remove.php +++ b/app/Templates/action_remove.php @@ -1,16 +1,14 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Remove an automatic action') ?></h2> - </div> +<div class="page-header"> + <h2><?= t('Remove an automatic action') ?></h2> +</div> - <div class="confirm"> - <p class="alert alert-info"> - <?= t('Do you really want to remove this action: "%s"?', Helper\in_list($action['event_name'], $available_events).'/'.Helper\in_list($action['action_name'], $available_actions)) ?> - </p> +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this action: "%s"?', Helper\in_list($action['event_name'], $available_events).'/'.Helper\in_list($action['action_name'], $available_actions)) ?> + </p> - <div class="form-actions"> - <a href="?controller=action&action=remove&action_id=<?= $action['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> - <?= t('or') ?> <a href="?controller=action&action=index&project_id=<?= $action['project_id'] ?>"><?= t('cancel') ?></a> - </div> + <div class="form-actions"> + <a href="?controller=action&action=remove&action_id=<?= $action['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <?= t('or') ?> <a href="?controller=action&action=index&project_id=<?= $action['project_id'] ?>"><?= t('cancel') ?></a> </div> -</section>
\ No newline at end of file +</div>
\ No newline at end of file diff --git a/app/Templates/board_assign.php b/app/Templates/board_assignee.php index 45cb4b4f..41ede32b 100644 --- a/app/Templates/board_assign.php +++ b/app/Templates/board_assignee.php @@ -1,14 +1,12 @@ <section id="main"> <div class="page-header board"> - <h2> - <?= t('Project "%s"', $current_project_name) ?> - </h2> + <h2><?= t('Project "%s"', $current_project_name) ?></h2> </div> <section> <h3><?= t('Change assignee for the task "%s"', $values['title']) ?></h3> - <form method="post" action="?controller=board&action=assignTask" autocomplete="off"> + <form method="post" action="?controller=board&action=updateAssignee" autocomplete="off"> <?= Helper\form_csrf() ?> <?= Helper\form_hidden('id', $values) ?> <?= Helper\form_hidden('project_id', $values) ?> diff --git a/app/Templates/board_category.php b/app/Templates/board_category.php new file mode 100644 index 00000000..36126a1d --- /dev/null +++ b/app/Templates/board_category.php @@ -0,0 +1,24 @@ +<section id="main"> + + <div class="page-header board"> + <h2><?= t('Project "%s"', $current_project_name) ?></h2> + </div> + + <section> + <h3><?= t('Change category for the task "%s"', $values['title']) ?></h3> + <form method="post" action="?controller=board&action=updateCategory" autocomplete="off"> + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('id', $values) ?> + <?= Helper\form_hidden('project_id', $values) ?> + + <?= Helper\form_label(t('Category'), 'category_id') ?> + <?= Helper\form_select('category_id', $categories_list, $values, $errors) ?><br/> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + <?= t('or') ?> <a href="?controller=board&action=show&project_id=<?= $values['project_id'] ?>"><?= t('cancel') ?></a> + </div> + </form> + </section> + +</section>
\ No newline at end of file diff --git a/app/Templates/board_edit.php b/app/Templates/board_edit.php index 05d9a6f6..8832e71d 100644 --- a/app/Templates/board_edit.php +++ b/app/Templates/board_edit.php @@ -1,66 +1,58 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Edit the board for "%s"', $project['name']) ?></h2> - <ul> - <li><a href="?controller=project"><?= t('All projects') ?></a></li> - </ul> - </div> - <section> +<div class="page-header"> + <h2><?= t('Edit the board for "%s"', $project['name']) ?></h2> +</div> +<section> - <h3><?= t('Change columns') ?></h3> - <form method="post" action="?controller=board&action=update&project_id=<?= $project['id'] ?>" autocomplete="off"> - <?= Helper\form_csrf() ?> - <?php $i = 0; ?> - <table> - <tr> - <th><?= t('Position') ?></th> - <th><?= t('Column title') ?></th> - <th><?= t('Task limit') ?></th> - <th><?= t('Actions') ?></th> - </tr> - <?php foreach ($columns as $column): ?> - <tr> - <td><?= Helper\form_label(t('Column %d', ++$i), 'title['.$column['id'].']', array('title="column_id='.$column['id'].'"')) ?></td> - <td><?= Helper\form_text('title['.$column['id'].']', $values, $errors, array('required')) ?></td> - <td><?= Helper\form_number('task_limit['.$column['id'].']', $values, $errors, array('placeholder="'.t('limit').'"')) ?></td> - <td> - <ul> - <?php if ($column['position'] != 1): ?> - <li> - <a href="?controller=board&action=moveUp&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'].Helper\param_csrf() ?>"><?= t('Move Up') ?></a> - </li> - <?php endif ?> - <?php if ($column['position'] != count($columns)): ?> - <li> - <a href="?controller=board&action=moveDown&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'].Helper\param_csrf() ?>"><?= t('Move Down') ?></a> - </li> - <?php endif ?> - <li> - <a href="?controller=board&action=confirm&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'] ?>"><?= t('Remove') ?></a> - </li> - </ul> - </td> - </tr> - <?php endforeach ?> - </table> +<h3><?= t('Change columns') ?></h3> +<form method="post" action="?controller=board&action=update&project_id=<?= $project['id'] ?>" autocomplete="off"> + <?= Helper\form_csrf() ?> + <?php $i = 0; ?> + <table> + <tr> + <th><?= t('Position') ?></th> + <th><?= t('Column title') ?></th> + <th><?= t('Task limit') ?></th> + <th><?= t('Actions') ?></th> + </tr> + <?php foreach ($columns as $column): ?> + <tr> + <td><?= Helper\form_label(t('Column %d', ++$i), 'title['.$column['id'].']', array('title="column_id='.$column['id'].'"')) ?></td> + <td><?= Helper\form_text('title['.$column['id'].']', $values, $errors, array('required')) ?></td> + <td><?= Helper\form_number('task_limit['.$column['id'].']', $values, $errors, array('placeholder="'.t('limit').'"')) ?></td> + <td> + <ul> + <?php if ($column['position'] != 1): ?> + <li> + <a href="?controller=board&action=moveUp&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'].Helper\param_csrf() ?>"><?= t('Move Up') ?></a> + </li> + <?php endif ?> + <?php if ($column['position'] != count($columns)): ?> + <li> + <a href="?controller=board&action=moveDown&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'].Helper\param_csrf() ?>"><?= t('Move Down') ?></a> + </li> + <?php endif ?> + <li> + <a href="?controller=board&action=confirm&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'] ?>"><?= t('Remove') ?></a> + </li> + </ul> + </td> + </tr> + <?php endforeach ?> + </table> - <div class="form-actions"> - <input type="submit" value="<?= t('Update') ?>" class="btn btn-blue"/> - <?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a> - </div> - </form> + <div class="form-actions"> + <input type="submit" value="<?= t('Update') ?>" class="btn btn-blue"/> + </div> +</form> - <h3><?= t('Add a new column') ?></h3> - <form method="post" action="?controller=board&action=add&project_id=<?= $project['id'] ?>" autocomplete="off"> - <?= Helper\form_csrf() ?> - <?= Helper\form_hidden('project_id', $values) ?> - <?= Helper\form_label(t('Title'), 'title') ?> - <?= Helper\form_text('title', $values, $errors, array('required')) ?> +<h3><?= t('Add a new column') ?></h3> +<form method="post" action="?controller=board&action=add&project_id=<?= $project['id'] ?>" autocomplete="off"> + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('project_id', $values) ?> + <?= Helper\form_label(t('Title'), 'title') ?> + <?= Helper\form_text('title', $values, $errors, array('required')) ?> - <div class="form-actions"> - <input type="submit" value="<?= t('Add this column') ?>" class="btn btn-blue"/> - <?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a> - </div> - </form> - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Add this column') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/board_index.php b/app/Templates/board_index.php index c168d11a..da40468d 100644 --- a/app/Templates/board_index.php +++ b/app/Templates/board_index.php @@ -19,6 +19,7 @@ <li><a href="#" id="filter-due-date"><?= t('Filter by due date') ?></a></li> <li><a href="?controller=project&action=search&project_id=<?= $current_project_id ?>"><?= t('Search') ?></a></li> <li><a href="?controller=project&action=tasks&project_id=<?= $current_project_id ?>"><?= t('Completed tasks') ?></a></li> + <li><a href="?controller=project&action=activity&project_id=<?= $current_project_id ?>"><?= t('Activity') ?></a></li> </ul> </div> diff --git a/app/Templates/board_public.php b/app/Templates/board_public.php index f90dc01b..85c90cfa 100644 --- a/app/Templates/board_public.php +++ b/app/Templates/board_public.php @@ -21,7 +21,7 @@ <?php foreach ($column['tasks'] as $task): ?> <div class="task-board task-<?= $task['color_id'] ?>"> - <?= Helper\template('board_task', array('task' => $task, 'categories' => $categories, 'not_editable' => true)) ?> + <?= Helper\template('board_task', array('task' => $task, 'categories' => $categories, 'not_editable' => true, 'project' => $project)) ?> </div> <?php endforeach ?> diff --git a/app/Templates/board_remove.php b/app/Templates/board_remove.php index 76c217b3..d6fa9a88 100644 --- a/app/Templates/board_remove.php +++ b/app/Templates/board_remove.php @@ -1,17 +1,15 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Remove a column') ?></h2> - </div> +<div class="page-header"> + <h2><?= t('Remove a column') ?></h2> +</div> - <div class="confirm"> - <p class="alert alert-info"> - <?= t('Do you really want to remove this column: "%s"?', $column['title']) ?> - <?= t('This action will REMOVE ALL TASKS associated to this column!') ?> - </p> +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this column: "%s"?', $column['title']) ?> + <?= t('This action will REMOVE ALL TASKS associated to this column!') ?> + </p> - <div class="form-actions"> - <a href="?controller=board&action=remove&column_id=<?= $column['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> - <?= t('or') ?> <a href="?controller=board&action=edit&project_id=<?= $column['project_id'] ?>"><?= t('cancel') ?></a> - </div> + <div class="form-actions"> + <a href="?controller=board&action=remove&column_id=<?= $column['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <?= t('or') ?> <a href="?controller=board&action=edit&project_id=<?= $column['project_id'] ?>"><?= t('cancel') ?></a> </div> -</section>
\ No newline at end of file +</div>
\ No newline at end of file diff --git a/app/Templates/board_task.php b/app/Templates/board_task.php index 0bc96579..20947a02 100644 --- a/app/Templates/board_task.php +++ b/app/Templates/board_task.php @@ -1,6 +1,6 @@ <?php if (isset($not_editable)): ?> - #<?= $task['id'] ?> - + <a href="?controller=task&action=readonly&task_id=<?= $task['id'] ?>&token=<?= $project['token'] ?>">#<?= $task['id'] ?></a> - <span class="task-board-user"> <?php if (! empty($task['owner_id'])): ?> @@ -15,7 +15,9 @@ <?php endif ?> <div class="task-board-title"> - <?= Helper\escape($task['title']) ?> + <a href="?controller=task&action=readonly&task_id=<?= $task['id'] ?>&token=<?= $project['token'] ?>"> + <?= Helper\escape($task['title']) ?> + </a> </div> <?php else: ?> @@ -23,11 +25,13 @@ <a class="task-edit-popover" href="?controller=task&action=edit&task_id=<?= $task['id'] ?>" title="<?= t('Edit this task') ?>">#<?= $task['id'] ?></a> - <span class="task-board-user"> - <?php if (! empty($task['owner_id'])): ?> - <a class="assignee-popover" href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['assignee_name'] ?: $task['assignee_username']) ?></a> - <?php else: ?> - <a class="assignee-popover" href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>" class="task-board-nobody"><?= t('Nobody assigned') ?></a> - <?php endif ?> + <a class="assignee-popover" href="?controller=board&action=changeAssignee&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"> + <?php if (! empty($task['owner_id'])): ?> + <?= t('Assigned to %s', $task['assignee_name'] ?: $task['assignee_username']) ?></a> + <?php else: ?> + <?= t('Nobody assigned') ?> + <?php endif ?> + </a> </span> <?php if ($task['score']): ?> @@ -44,7 +48,9 @@ <?php if ($task['category_id']): ?> <div class="task-board-category-container"> <span class="task-board-category"> - <?= Helper\in_list($task['category_id'], $categories) ?> + <a class="category-popover" href="?controller=board&action=changeCategory&task_id=<?= $task['id'] ?>" title="<?= t('Change category') ?>"> + <?= Helper\in_list($task['category_id'], $categories) ?> + </a> </span> </div> <?php endif ?> @@ -69,7 +75,11 @@ <?php endif ?> <?php if (! empty($task['description'])): ?> - <a class="task-board-popover" href='?controller=task&action=editDescription&task_id=<?= $task['id'] ?>'><i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i></a> + <?php if (! isset($not_editable)): ?> + <a class="task-description-popover" href="?controller=task&action=description&task_id=<?= $task['id'] ?>"><i class="fa fa-file-text-o" title="<?= t('Description') ?>" data-href="?controller=task&action=description&task_id=<?= $task['id'] ?>"></i></a> + <?php else: ?> + <i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i> + <?php endif ?> <?php endif ?> </div> </div> diff --git a/app/Templates/category_edit.php b/app/Templates/category_edit.php index 1339f6da..278d7e12 100644 --- a/app/Templates/category_edit.php +++ b/app/Templates/category_edit.php @@ -1,24 +1,16 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Category modification for the project "%s"', $project['name']) ?></h2> - <ul> - <li><a href="?controller=project"><?= t('All projects') ?></a></li> - </ul> - </div> - <section> - - <form method="post" action="?controller=category&action=update&project_id=<?= $project['id'] ?>" autocomplete="off"> - <?= Helper\form_csrf() ?> - <?= Helper\form_hidden('id', $values) ?> - <?= Helper\form_hidden('project_id', $values) ?> +<div class="page-header"> + <h2><?= t('Category modification for the project "%s"', $project['name']) ?></h2> +</div> - <?= Helper\form_label(t('Category Name'), 'name') ?> - <?= Helper\form_text('name', $values, $errors, array('required')) ?> +<form method="post" action="?controller=category&action=update&project_id=<?= $project['id'] ?>" autocomplete="off"> + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('id', $values) ?> + <?= Helper\form_hidden('project_id', $values) ?> - <div class="form-actions"> - <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> - </div> - </form> + <?= Helper\form_label(t('Category Name'), 'name') ?> + <?= Helper\form_text('name', $values, $errors, array('autofocus required')) ?> - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/category_index.php b/app/Templates/category_index.php index 7fb923ba..4635406e 100644 --- a/app/Templates/category_index.php +++ b/app/Templates/category_index.php @@ -1,49 +1,41 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Categories for the project "%s"', $project['name']) ?></h2> - <ul> - <li><a href="?controller=project"><?= t('All projects') ?></a></li> - </ul> - </div> - <section> - - <?php if (! empty($categories)): ?> - <table> - <tr> - <th><?= t('Category Name') ?></th> - <th><?= t('Actions') ?></th> - </tr> - <?php foreach ($categories as $category_id => $category_name): ?> - <tr> - <td><?= Helper\escape($category_name) ?></td> - <td> - <ul> - <li> - <a href="?controller=category&action=edit&project_id=<?= $project['id'] ?>&category_id=<?= $category_id ?>"><?= t('Edit') ?></a> - </li> - <li> - <a href="?controller=category&action=confirm&project_id=<?= $project['id'] ?>&category_id=<?= $category_id ?>"><?= t('Remove') ?></a> - </li> - </ul> - </td> - </tr> - <?php endforeach ?> - </table> - <?php endif ?> +<div class="page-header"> + <h2><?= t('Categories') ?></h2> +</div> - <h3><?= t('Add a new category') ?></h3> - <form method="post" action="?controller=category&action=save&project_id=<?= $project['id'] ?>" autocomplete="off"> +<?php if (! empty($categories)): ?> +<table> + <tr> + <th><?= t('Category Name') ?></th> + <th><?= t('Actions') ?></th> + </tr> + <?php foreach ($categories as $category_id => $category_name): ?> + <tr> + <td><?= Helper\escape($category_name) ?></td> + <td> + <ul> + <li> + <a href="?controller=category&action=edit&project_id=<?= $project['id'] ?>&category_id=<?= $category_id ?>"><?= t('Edit') ?></a> + </li> + <li> + <a href="?controller=category&action=confirm&project_id=<?= $project['id'] ?>&category_id=<?= $category_id ?>"><?= t('Remove') ?></a> + </li> + </ul> + </td> + </tr> + <?php endforeach ?> +</table> +<?php endif ?> - <?= Helper\form_csrf() ?> - <?= Helper\form_hidden('project_id', $values) ?> +<h3><?= t('Add a new category') ?></h3> +<form method="post" action="?controller=category&action=save&project_id=<?= $project['id'] ?>" autocomplete="off"> - <?= Helper\form_label(t('Category Name'), 'name') ?> - <?= Helper\form_text('name', $values, $errors, array('required')) ?> + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('project_id', $values) ?> - <div class="form-actions"> - <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> - </div> - </form> + <?= Helper\form_label(t('Category Name'), 'name') ?> + <?= Helper\form_text('name', $values, $errors, array('autofocus required')) ?> - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/comment_show.php b/app/Templates/comment_show.php index 08d77b29..181a69fa 100644 --- a/app/Templates/comment_show.php +++ b/app/Templates/comment_show.php @@ -9,7 +9,7 @@ <?php if (! isset($preview)): ?> <ul class="comment-actions"> <li><a href="#comment-<?= $comment['id'] ?>"><?= t('link') ?></a></li> - <?php if (Helper\is_admin() || Helper\is_current_user($comment['user_id'])): ?> + <?php if ((! isset($not_editable) || ! $not_editable) && (Helper\is_admin() || Helper\is_current_user($comment['user_id']))): ?> <li> <a href="?controller=comment&action=confirm&task_id=<?= $task['id'] ?>&comment_id=<?= $comment['id'] ?>"><?= t('remove') ?></a> </li> diff --git a/app/Templates/config_index.php b/app/Templates/config_index.php index 11662c87..98b8b28d 100644 --- a/app/Templates/config_index.php +++ b/app/Templates/config_index.php @@ -1,143 +1,67 @@ <section id="main"> - <?php if ($user['is_admin']): ?> - <div class="page-header"> - <h2><?= t('Application settings') ?></h2> - </div> - <section> - <form method="post" action="?controller=config&action=save" autocomplete="off"> - - <?= Helper\form_csrf() ?> - - <?= Helper\form_label(t('Language'), 'language') ?> - <?= Helper\form_select('language', $languages, $values, $errors) ?><br/> - - <?= Helper\form_label(t('Timezone'), 'timezone') ?> - <?= Helper\form_select('timezone', $timezones, $values, $errors) ?><br/> - - <?= Helper\form_label(t('Webhook URL for task creation'), 'webhooks_url_task_creation') ?> - <?= Helper\form_text('webhooks_url_task_creation', $values, $errors) ?><br/> - - <?= Helper\form_label(t('Webhook URL for task modification'), 'webhooks_url_task_modification') ?> - <?= Helper\form_text('webhooks_url_task_modification', $values, $errors) ?><br/> - - <div class="form-actions"> - <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> - </div> - </form> - </section> - <?php endif ?> - <div class="page-header"> - <h2><?= t('User settings') ?></h2> + <h2><?= t('Application settings') ?></h2> </div> <section> - <h3 id="notifications"><?= t('Email notifications') ?></h3> - <form method="post" action="?controller=config&action=notifications" autocomplete="off"> + <form method="post" action="?controller=config&action=save" autocomplete="off"> - <?= Helper\form_csrf() ?> + <?= Helper\form_csrf() ?> - <?= Helper\form_checkbox('notifications_enabled', t('Enable email notifications'), '1', $notifications['notifications_enabled'] == 1) ?><br/> + <?= Helper\form_label(t('Language'), 'language') ?> + <?= Helper\form_select('language', $languages, $values, $errors) ?><br/> - <p><?= t('I want to receive notifications only for those projects:') ?><br/><br/></p> + <?= Helper\form_label(t('Timezone'), 'timezone') ?> + <?= Helper\form_select('timezone', $timezones, $values, $errors) ?><br/> - <div class="form-checkbox-group"> - <?php foreach ($user_projects as $project_id => $project_name): ?> - <?= Helper\form_checkbox('projects['.$project_id.']', $project_name, '1', isset($notifications['project_'.$project_id])) ?> - <?php endforeach ?> - </div> - <div class="form-actions"> - <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> - </div> - </form> - </section> + <?= Helper\form_label(t('Webhook URL for task creation'), 'webhooks_url_task_creation') ?> + <?= Helper\form_text('webhooks_url_task_creation', $values, $errors) ?><br/> - <?php if ($user['is_admin']): ?> - <div class="page-header"> - <h2><?= t('More information') ?></h2> + <?= Helper\form_label(t('Webhook URL for task modification'), 'webhooks_url_task_modification') ?> + <?= Helper\form_text('webhooks_url_task_modification', $values, $errors) ?><br/> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> </div> - <section class="settings"> - <ul> - <li><a href="?controller=config&action=tokens<?= Helper\param_csrf() ?>"><?= t('Reset all tokens') ?></a></li> - <li> - <?= t('Webhooks token:') ?> - <strong><?= Helper\escape($values['webhooks_token']) ?></strong> - </li> + </form> + </section> + + <div class="page-header"> + <h2><?= t('More information') ?></h2> + </div> + <section class="settings"> + <ul> + <li><a href="?controller=config&action=tokens<?= Helper\param_csrf() ?>"><?= t('Reset all tokens') ?></a></li> + <li> + <?= t('Webhooks token:') ?> + <strong><?= Helper\escape($values['webhooks_token']) ?></strong> + </li> + <li> + <?= t('API token:') ?> + <strong><?= Helper\escape($values['api_token']) ?></strong> + </li> + <?php if (DB_DRIVER === 'sqlite'): ?> <li> - <?= t('API token:') ?> - <strong><?= Helper\escape($values['api_token']) ?></strong> + <?= t('Database size:') ?> + <strong><?= Helper\format_bytes($db_size) ?></strong> </li> - <?php if (DB_DRIVER === 'sqlite'): ?> - <li> - <?= t('Database size:') ?> - <strong><?= Helper\format_bytes($db_size) ?></strong> - </li> - <li> - <a href="?controller=config&action=downloadDb<?= Helper\param_csrf() ?>"><?= t('Download the database') ?></a> - <?= t('(Gzip compressed Sqlite file)') ?> - </li> - <li> - <a href="?controller=config&action=optimizeDb <?= Helper\param_csrf() ?>"><?= t('Optimize the database') ?></a> - <?= t('(VACUUM command)') ?> - </li> - <?php endif ?> <li> - <?= t('Official website:') ?> - <a href="http://kanboard.net/" target="_blank" rel="noreferer">http://kanboard.net/</a> + <a href="?controller=config&action=downloadDb<?= Helper\param_csrf() ?>"><?= t('Download the database') ?></a> + <?= t('(Gzip compressed Sqlite file)') ?> </li> <li> - <?= t('Application version:') ?> - <?= APP_VERSION ?> + <a href="?controller=config&action=optimizeDb <?= Helper\param_csrf() ?>"><?= t('Optimize the database') ?></a> + <?= t('(VACUUM command)') ?> </li> - </ul> - </section> - <?php endif ?> - - <div class="page-header" id="last-logins"> - <h2><?= t('Last logins') ?></h2> - </div> - <?php if (! empty($last_logins)): ?> - <table class="table-small table-hover"> - <tr> - <th><?= t('Login date') ?></th> - <th><?= t('Authentication method') ?></th> - <th><?= t('IP address') ?></th> - <th><?= t('User agent') ?></th> - </tr> - <?php foreach($last_logins as $login): ?> - <tr> - <td><?= dt('%B %e, %G at %k:%M %p', $login['date_creation']) ?></td> - <td><?= Helper\escape($login['auth_type']) ?></td> - <td><?= Helper\escape($login['ip']) ?></td> - <td><?= Helper\escape($login['user_agent']) ?></td> - </tr> - <?php endforeach ?> - </table> - <?php endif ?> - - <div class="page-header" id="remember-me"> - <h2><?= t('Persistent connections') ?></h2> - </div> - <?php if (empty($remember_me_sessions)): ?> - <p class="alert alert-info"><?= t('No session') ?></p> - <?php else: ?> - <table class="table-small table-hover"> - <tr> - <th><?= t('Creation date') ?></th> - <th><?= t('Expiration date') ?></th> - <th><?= t('IP address') ?></th> - <th><?= t('User agent') ?></th> - <th><?= t('Action') ?></th> - </tr> - <?php foreach($remember_me_sessions as $session): ?> - <tr> - <td><?= dt('%B %e, %G at %k:%M %p', $session['date_creation']) ?></td> - <td><?= dt('%B %e, %G at %k:%M %p', $session['expiration']) ?></td> - <td><?= Helper\escape($session['ip']) ?></td> - <td><?= Helper\escape($session['user_agent']) ?></td> - <td><a href="?controller=config&action=removeRememberMeToken&id=<?= $session['id'].Helper\param_csrf() ?>"><?= t('Remove') ?></a></td> - </tr> - <?php endforeach ?> - </table> - <?php endif ?> + <?php endif ?> + <li> + <?= t('Official website:') ?> + <a href="http://kanboard.net/" target="_blank" rel="noreferer">http://kanboard.net/</a> + </li> + <li> + <?= t('Application version:') ?> + <?= APP_VERSION ?> + </li> + </ul> + </section> </section> diff --git a/app/Templates/event_comment_create.php b/app/Templates/event_comment_create.php new file mode 100644 index 00000000..09d45ed0 --- /dev/null +++ b/app/Templates/event_comment_create.php @@ -0,0 +1,7 @@ +<p class="activity-title"> + <?= e('%s commented the task <a href="?controller=task&action=show&task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em><br/> + <div class="markdown"><?= Helper\parse($comment) ?></div> +</p>
\ No newline at end of file diff --git a/app/Templates/event_comment_update.php b/app/Templates/event_comment_update.php new file mode 100644 index 00000000..a42e5dab --- /dev/null +++ b/app/Templates/event_comment_update.php @@ -0,0 +1,7 @@ +<p class="activity-title"> + <?= e('%s updated a comment on the task <a href="?controller=task&action=show&task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em><br/> + <div class="markdown"><?= Helper\parse($comment) ?></div> +</p>
\ No newline at end of file diff --git a/app/Templates/event_subtask_create.php b/app/Templates/event_subtask_create.php new file mode 100644 index 00000000..9772d807 --- /dev/null +++ b/app/Templates/event_subtask_create.php @@ -0,0 +1,12 @@ +<p class="activity-title"> + <?= e('%s created a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em><br/> + <p><?= Helper\escape($subtask_title) ?> <strong>(<?= Helper\in_list($subtask_status, $subtask_status_list) ?>)</strong></p> + <?php if ($subtask_assignee): ?> + <p><?= t('Assigned to %s with an estimate of %s/%sh', $subtask_assignee, $subtask_time_spent, $subtask_time_estimated) ?></p> + <?php else: ?> + <p><?= t('Not assigned, estimate of %sh', $subtask_time_estimated) ?></p> + <?php endif ?> +</p>
\ No newline at end of file diff --git a/app/Templates/event_subtask_update.php b/app/Templates/event_subtask_update.php new file mode 100644 index 00000000..70f9d76d --- /dev/null +++ b/app/Templates/event_subtask_update.php @@ -0,0 +1,12 @@ +<p class="activity-title"> + <?= e('%s updated a subtask for the task <a href="?controller=task&action=show&task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em><br/> + <p><?= Helper\escape($subtask_title) ?> <strong>(<?= Helper\in_list($subtask_status, $subtask_status_list) ?>)</strong></p> + <?php if ($subtask_assignee): ?> + <p><?= t('Assigned to %s with an estimate of %s/%sh', $subtask_assignee, $subtask_time_spent, $subtask_time_estimated) ?></p> + <?php else: ?> + <p><?= t('Not assigned, estimate of %sh', $subtask_time_estimated) ?></p> + <?php endif ?> +</p>
\ No newline at end of file diff --git a/app/Templates/event_task_close.php b/app/Templates/event_task_close.php new file mode 100644 index 00000000..9d92ea55 --- /dev/null +++ b/app/Templates/event_task_close.php @@ -0,0 +1,6 @@ +<p class="activity-title"> + <?= e('%s closed the task <a href="?controller=task&action=show&task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em> +</p>
\ No newline at end of file diff --git a/app/Templates/event_task_create.php b/app/Templates/event_task_create.php new file mode 100644 index 00000000..7ceab4bb --- /dev/null +++ b/app/Templates/event_task_create.php @@ -0,0 +1,6 @@ +<p class="activity-title"> + <?= e('%s created the task <a href="?controller=task&action=show&task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em> +</p>
\ No newline at end of file diff --git a/app/Templates/event_task_move_column.php b/app/Templates/event_task_move_column.php new file mode 100644 index 00000000..a674f80a --- /dev/null +++ b/app/Templates/event_task_move_column.php @@ -0,0 +1,6 @@ +<p class="activity-title"> + <?= e('%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the column "%s"', Helper\escape($author), $task_id, $task_id, Helper\escape($task_column_name)) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em> +</p>
\ No newline at end of file diff --git a/app/Templates/event_task_move_position.php b/app/Templates/event_task_move_position.php new file mode 100644 index 00000000..0b7e9615 --- /dev/null +++ b/app/Templates/event_task_move_position.php @@ -0,0 +1,6 @@ +<p class="activity-title"> + <?= e('%s moved the task <a href="?controller=task&action=show&task_id=%d">#%d</a> to the position #%d in the column "%s"', Helper\escape($author), $task_id, $task_id, $task_position, Helper\escape($task_column_name)) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em> +</p>
\ No newline at end of file diff --git a/app/Templates/event_task_open.php b/app/Templates/event_task_open.php new file mode 100644 index 00000000..763b0dfc --- /dev/null +++ b/app/Templates/event_task_open.php @@ -0,0 +1,6 @@ +<p class="activity-title"> + <?= e('%s open the task <a href="?controller=task&action=show&task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em> +</p>
\ No newline at end of file diff --git a/app/Templates/event_task_update.php b/app/Templates/event_task_update.php new file mode 100644 index 00000000..313e79cb --- /dev/null +++ b/app/Templates/event_task_update.php @@ -0,0 +1,6 @@ +<p class="activity-title"> + <?= e('%s updated the task <a href="?controller=task&action=show&task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?> +</p> +<p class="activity-description"> + <em><?= Helper\escape($task_title) ?></em> +</p>
\ No newline at end of file diff --git a/app/Templates/layout.php b/app/Templates/layout.php index 2b66c685..f2082db7 100644 --- a/app/Templates/layout.php +++ b/app/Templates/layout.php @@ -5,6 +5,7 @@ <meta name="viewport" content="width=device-width"> <meta name="mobile-web-app-capable" content="yes"> + <meta name="robots" content="noindex,nofollow"> <?= Helper\js('assets/js/jquery-1.11.1.min.js') ?> <?= Helper\js('assets/js/jquery-ui-1.10.4.custom.min.js') ?> @@ -29,7 +30,7 @@ <?php endif ?> </head> <body> - <?php if (isset($no_layout)): ?> + <?php if (isset($no_layout) && $no_layout): ?> <?= $content_for_layout ?> <?php else: ?> <header> @@ -37,7 +38,7 @@ <a class="logo" href="?">kanboard</a> <ul> - <?php if (isset($board_selector)): ?> + <?php if (isset($board_selector) && ! empty($board_selector)): ?> <li> <select id="board-selector" data-placeholder="<?= t('Display another project') ?>"> <option value=""></option> @@ -56,12 +57,14 @@ <li <?= isset($menu) && $menu === 'users' ? 'class="active"' : '' ?>> <a href="?controller=user"><?= t('Users') ?></a> </li> - <li <?= isset($menu) && $menu === 'config' ? 'class="active"' : '' ?>> - <a href="?controller=config"><?= t('Settings') ?></a> - </li> + <?php if (Helper\is_admin()): ?> + <li class="hide-tablet <?= isset($menu) && $menu === 'config' ? 'active' : '' ?>"> + <a href="?controller=config"><?= t('Settings') ?></a> + </li> + <?php endif ?> <li> <a href="?controller=user&action=logout<?= Helper\param_csrf() ?>"><?= t('Logout') ?></a> - (<?= Helper\escape(Helper\get_username()) ?>) + <span class="username">(<a href="?controller=user&action=show&user_id=<?= Helper\get_user_id() ?>"><?= Helper\escape(Helper\get_username()) ?></a>)</span> </li> </ul> </nav> diff --git a/app/Templates/project_activity.php b/app/Templates/project_activity.php new file mode 100644 index 00000000..ad91f8b8 --- /dev/null +++ b/app/Templates/project_activity.php @@ -0,0 +1,37 @@ +<section id="main"> + <div class="page-header"> + <h2><?= t('%s\'s activity', $project['name']) ?></h2> + <ul> + <li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li> + <li><a href="?controller=project&action=search&project_id=<?= $project['id'] ?>"><?= t('Search') ?></a></li> + <li><a href="?controller=project&action=tasks&project_id=<?= $project['id'] ?>"><?= t('Completed tasks') ?></a></li> + <li><a href="?controller=project&action=index"><?= t('List of projects') ?></a></li> + </ul> + </div> + <section> + <?php if (empty($events)): ?> + <p class="alert"><?= t('No activity.') ?></p> + <?php else: ?> + + <?php if ($project['is_public']): ?> + <p class="pull-right"><i class="fa fa-rss-square"></i> <a href="?controller=project&action=feed&token=<?= $project['token'] ?>" target="_blank"><?= t('RSS feed') ?></a></p> + <?php endif ?> + + <?php foreach ($events as $event): ?> + <div class="activity-event"> + <p class="activity-datetime"> + <?php if ($event['event_type'] === 'task'): ?> + <i class="fa fa-newspaper-o"></i> + <?php elseif ($event['event_type'] === 'subtask'): ?> + <i class="fa fa-tasks"></i> + <?php elseif ($event['event_type'] === 'comment'): ?> + <i class="fa fa-comments-o"></i> + <?php endif ?> + <?= dt('%B %e, %Y at %k:%M %p', $event['date_creation']) ?> + </p> + <div class="activity-content"><?= $event['event_content'] ?></div> + </div> + <?php endforeach ?> + <?php endif ?> + </section> +</section>
\ No newline at end of file diff --git a/app/Templates/project_disable.php b/app/Templates/project_disable.php new file mode 100644 index 00000000..39f55570 --- /dev/null +++ b/app/Templates/project_disable.php @@ -0,0 +1,14 @@ +<div class="page-header"> + <h2><?= t('Project activation') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to disable this project: "%s"?', $project['name']) ?> + </p> + + <div class="form-actions"> + <a href="?controller=project&action=disable&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <?= t('or') ?> <a href="?controller=project&action=show&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a> + </div> +</div>
\ No newline at end of file diff --git a/app/Templates/project_duplicate.php b/app/Templates/project_duplicate.php new file mode 100644 index 00000000..32cbd5d8 --- /dev/null +++ b/app/Templates/project_duplicate.php @@ -0,0 +1,14 @@ +<div class="page-header"> + <h2><?= t('Clone this project') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to duplicate this project: "%s"?', $project['name']) ?> + </p> + + <div class="form-actions"> + <a href="?controller=project&action=duplicate&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <?= t('or') ?> <a href="?controller=project&action=show&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a> + </div> +</div>
\ No newline at end of file diff --git a/app/Templates/project_edit.php b/app/Templates/project_edit.php index a882fbc6..4c9420f0 100644 --- a/app/Templates/project_edit.php +++ b/app/Templates/project_edit.php @@ -1,25 +1,17 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Edit project') ?></h2> - <ul> - <li><a href="?controller=project"><?= t('All projects') ?></a></li> - </ul> - </div> - <section> - <form method="post" action="?controller=project&action=update&project_id=<?= $values['id'] ?>" autocomplete="off"> +<div class="page-header"> + <h2><?= t('Edit project') ?></h2> +</div> +<form method="post" action="?controller=project&action=update&project_id=<?= $values['id'] ?>" autocomplete="off"> - <?= Helper\form_csrf() ?> - <?= Helper\form_hidden('id', $values) ?> + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('id', $values) ?> - <?= Helper\form_label(t('Name'), 'name') ?> - <?= Helper\form_text('name', $values, $errors, array('required')) ?> + <?= Helper\form_label(t('Name'), 'name') ?> + <?= Helper\form_text('name', $values, $errors, array('required')) ?> - <?= Helper\form_checkbox('is_active', t('Activated'), 1, isset($values['is_active']) && $values['is_active'] == 1 ? true : false) ?><br/> + <?= Helper\form_checkbox('is_active', t('Activated'), 1, isset($values['is_active']) && $values['is_active'] == 1) ?><br/> - <div class="form-actions"> - <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> - <?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a> - </div> - </form> - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/project_enable.php b/app/Templates/project_enable.php new file mode 100644 index 00000000..d2fce9f3 --- /dev/null +++ b/app/Templates/project_enable.php @@ -0,0 +1,14 @@ +<div class="page-header"> + <h2><?= t('Project activation') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to enable this project: "%s"?', $project['name']) ?> + </p> + + <div class="form-actions"> + <a href="?controller=project&action=enable&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <?= t('or') ?> <a href="?controller=project&action=show&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a> + </div> +</div>
\ No newline at end of file diff --git a/app/Templates/project_export.php b/app/Templates/project_export.php index 946a68a8..46b4f369 100644 --- a/app/Templates/project_export.php +++ b/app/Templates/project_export.php @@ -1,33 +1,24 @@ -<section id="main"> - <div class="page-header"> - <h2> - <?= t('Tasks exportation for "%s"', $project['name']) ?> - </h2> - <ul> - <li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li> - <li><a href="?controller=project&action=index"><?= t('List of projects') ?></a></li> - </ul> - </div> - <section id="project-section"> - - <form method="get" action="?" autocomplete="off"> +<div class="page-header"> + <h2> + <?= t('Tasks exportation for "%s"', $project['name']) ?> + </h2> +</div> - <?= Helper\form_hidden('controller', $values) ?> - <?= Helper\form_hidden('action', $values) ?> - <?= Helper\form_hidden('project_id', $values) ?> +<form method="get" action="?" autocomplete="off"> - <?= Helper\form_label(t('Start Date'), 'from') ?> - <?= Helper\form_text('from', $values, $errors, array('required', 'placeholder="'.t('month/day/year').'"'), 'form-date') ?><br/> + <?= Helper\form_hidden('controller', $values) ?> + <?= Helper\form_hidden('action', $values) ?> + <?= Helper\form_hidden('project_id', $values) ?> - <?= Helper\form_label(t('End Date'), 'to') ?> - <?= Helper\form_text('to', $values, $errors, array('required', 'placeholder="'.t('month/day/year').'"'), 'form-date') ?> + <?= Helper\form_label(t('Start Date'), 'from') ?> + <?= Helper\form_text('from', $values, $errors, array('required', 'placeholder="'.t('month/day/year').'"'), 'form-date') ?><br/> - <div class="form-help"><?= t('Others formats accepted: %s and %s', date('Y-m-d'), date('Y_m_d')) ?></div> + <?= Helper\form_label(t('End Date'), 'to') ?> + <?= Helper\form_text('to', $values, $errors, array('required', 'placeholder="'.t('month/day/year').'"'), 'form-date') ?> - <div class="form-actions"> - <input type="submit" value="<?= t('Execute') ?>" class="btn btn-blue"/> - </div> - </form> + <div class="form-help"><?= t('Others formats accepted: %s and %s', date('Y-m-d'), date('Y_m_d')) ?></div> - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Execute') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/project_feed.php b/app/Templates/project_feed.php new file mode 100644 index 00000000..b47c87ad --- /dev/null +++ b/app/Templates/project_feed.php @@ -0,0 +1,27 @@ +<?= '<?xml version="1.0" encoding="utf-8"?>' ?> +<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> + <title><?= t('%s\'s activity', $project['name']) ?></title> + <link rel="alternate" type="text/html" href="<?= Helper\get_current_base_url() ?>"/> + <link rel="self" type="application/atom+xml" href="<?= Helper\get_current_base_url() ?>?controller=project&action=feed&token=<?= $project['token'] ?>"/> + <updated><?= date(DATE_ATOM) ?></updated> + <id><?= Helper\get_current_base_url() ?></id> + <icon><?= Helper\get_current_base_url() ?>assets/img/favicon.png</icon> + + <?php foreach ($events as $e): ?> + <entry> + <title type="text"><?= $e['event_title'] ?></title> + <link rel="alternate" href="<?= Helper\get_current_base_url().'?controller=task&action=show&task_id='.$e['task_id'] ?>"/> + <id><?= $e['id'].'-'.$e['event_name'].'-'.$e['task_id'].'-'.$e['date_creation'] ?></id> + <published><?= date(DATE_ATOM, $e['date_creation']) ?></published> + <updated><?= date(DATE_ATOM, $e['date_creation']) ?></updated> + <author> + <name><?= Helper\escape($e['author']) ?></name> + </author> + <content type="html"> + <![CDATA[ + <?= $e['event_content'] ?> + ]]> + </content> + </entry> + <?php endforeach ?> +</feed>
\ No newline at end of file diff --git a/app/Templates/project_index.php b/app/Templates/project_index.php index dc71033f..8b103c52 100644 --- a/app/Templates/project_index.php +++ b/app/Templates/project_index.php @@ -8,99 +8,32 @@ <?php endif ?> </div> <section> - <?php if (empty($projects)): ?> + <?php if (empty($active_projects) && empty($inactive_projects)): ?> <p class="alert"><?= t('No project') ?></p> <?php else: ?> - <table> - <tr> - <th><?= t('Project') ?></th> - <th><?= t('Status') ?></th> - <th><?= t('Tasks') ?></th> - <th><?= t('Board') ?></th> - <?php if (Helper\is_admin()): ?> - <th><?= t('Actions') ?></th> - <?php endif ?> - </tr> - <?php foreach ($projects as $project): ?> - <tr> - <td> - <a href="?controller=board&action=show&project_id=<?= $project['id'] ?>" title="project_id=<?= $project['id'] ?>"><?= Helper\escape($project['name']) ?></a> - </td> - <td> - <?= $project['is_active'] ? t('Active') : t('Inactive') ?> - </td> - <td> - <ul> - <?php if ($project['nb_tasks'] > 0): ?> - - <?php if ($project['nb_active_tasks'] > 0): ?> - <li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('%d tasks on the board', $project['nb_active_tasks']) ?></a></li> - <?php endif ?> - - <?php if ($project['nb_inactive_tasks'] > 0): ?> - <li><a href="?controller=project&action=tasks&project_id=<?= $project['id'] ?>"><?= t('%d closed tasks', $project['nb_inactive_tasks']) ?></a></li> - <?php endif ?> + <?php if (! empty($active_projects)): ?> + <h3><?= t('Active projects') ?></h3> + <ul class="project-listing"> + <?php foreach ($active_projects as $project): ?> + <li> + <a href="?controller=project&action=show&project_id=<?= $project['id'] ?>"><?= Helper\escape($project['name']) ?></a> + </li> + <?php endforeach ?> + </ul> + <?php endif ?> - <li><?= t('%d tasks in total', $project['nb_tasks']) ?></li> + <?php if (! empty($inactive_projects)): ?> + <h3><?= t('Inactive projects') ?></h3> + <ul class="project-listing"> + <?php foreach ($inactive_projects as $project): ?> + <li> + <a href="?controller=project&action=show&project_id=<?= $project['id'] ?>"><?= Helper\escape($project['name']) ?></a> + </li> + <?php endforeach ?> + </ul> + <?php endif ?> - <?php else: ?> - <li><?= t('no task for this project') ?></li> - <?php endif ?> - </ul> - </td> - <td> - <ul> - <?php foreach ($project['columns'] as $column): ?> - <li> - <span title="column_id=<?= $column['id'] ?>"><?= Helper\escape($column['title']) ?></span> (<?= $column['nb_active_tasks'] ?>) - </li> - <?php endforeach ?> - </ul> - </td> - <?php if (Helper\is_admin()): ?> - <td> - <ul> - <li> - <a href="?controller=category&action=index&project_id=<?= $project['id'] ?>"><?= t('Categories') ?></a> - </li> - <li> - <a href="?controller=project&action=edit&project_id=<?= $project['id'] ?>"><?= t('Edit project') ?></a> - </li> - <li> - <a href="?controller=project&action=users&project_id=<?= $project['id'] ?>"><?= t('Edit users access') ?></a> - </li> - <li> - <a href="?controller=board&action=edit&project_id=<?= $project['id'] ?>"><?= t('Edit board') ?></a> - </li> - <li> - <a href="?controller=action&action=index&project_id=<?= $project['id'] ?>"><?= t('Automatic actions') ?></a> - </li> - <li> - <?php if ($project['is_active']): ?> - <a href="?controller=project&action=disable&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Disable') ?></a> - <?php else: ?> - <a href="?controller=project&action=enable&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Enable') ?></a> - <?php endif ?> - </li> - <li> - <a href="?controller=project&action=confirm&project_id=<?= $project['id'] ?>"><?= t('Remove') ?></a> - </li> - <li> - <a href="?controller=board&action=readonly&token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a> - </li> - <li> - <a href="?controller=project&action=export&project_id=<?= $project['id'] ?>"><?= t('Tasks Export') ?></a> - </li> - <li> - <a href="?controller=project&action=duplicate&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Clone Project') ?></a> - </li> - </ul> - </td> - <?php endif ?> - </tr> - <?php endforeach ?> - </table> <?php endif ?> </section> </section>
\ No newline at end of file diff --git a/app/Templates/project_layout.php b/app/Templates/project_layout.php new file mode 100644 index 00000000..c8cc9236 --- /dev/null +++ b/app/Templates/project_layout.php @@ -0,0 +1,17 @@ +<section id="main"> + <div class="page-header"> + <h2><?= t('Project "%s"', $project['name']) ?></h2> + <ul> + <li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li> + <li><a href="?controller=project"><?= t('All projects') ?></a></li> + </ul> + </div> + <section class="project-show" id="project-section"> + + <?= Helper\template('project_sidebar', array('project' => $project)) ?> + + <div class="project-show-main"> + <?= $project_content_for_layout ?> + </div> + </section> +</section>
\ No newline at end of file diff --git a/app/Templates/project_remove.php b/app/Templates/project_remove.php index e25efa2f..00771b5f 100644 --- a/app/Templates/project_remove.php +++ b/app/Templates/project_remove.php @@ -1,16 +1,14 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Remove project') ?></h2> - </div> +<div class="page-header"> + <h2><?= t('Remove project') ?></h2> +</div> - <div class="confirm"> - <p class="alert alert-info"> - <?= t('Do you really want to remove this project: "%s"?', $project['name']) ?> - </p> +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this project: "%s"?', $project['name']) ?> + </p> - <div class="form-actions"> - <a href="?controller=project&action=remove&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> - <?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a> - </div> + <div class="form-actions"> + <a href="?controller=project&action=remove&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <?= t('or') ?> <a href="?controller=project&action=show&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a> </div> -</section>
\ No newline at end of file +</div>
\ No newline at end of file diff --git a/app/Templates/project_search.php b/app/Templates/project_search.php index 7826ba63..a810afce 100644 --- a/app/Templates/project_search.php +++ b/app/Templates/project_search.php @@ -9,6 +9,7 @@ <ul> <li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li> <li><a href="?controller=project&action=tasks&project_id=<?= $project['id'] ?>"><?= t('Completed tasks') ?></a></li> + <li><a href="?controller=project&action=activity&project_id=<?= $project['id'] ?>"><?= t('Activity') ?></a></li> <li><a href="?controller=project&action=index"><?= t('List of projects') ?></a></li> </ul> </div> diff --git a/app/Templates/project_share.php b/app/Templates/project_share.php new file mode 100644 index 00000000..6cfd85f6 --- /dev/null +++ b/app/Templates/project_share.php @@ -0,0 +1,21 @@ +<div class="page-header"> + <h2><?= t('Public access') ?></h2> +</div> + +<?php if ($project['is_public']): ?> + + <div class="settings"> + <ul class="no-bullet"> + <li><strong><i class="fa fa-share-alt"></i> <a href="?controller=board&action=readonly&token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a></strong></li> + <li><strong><i class="fa fa-rss-square"></i> <a href="?controller=project&action=feed&token=<?= $project['token'] ?>" target="_blank"><?= t('RSS feed') ?></a></strong></li> + </ul> + <input type="text" readonly="readonly" value="<?= Helper\get_current_base_url() ?>?controller=board&action=readonly&token=<?= $project['token'] ?>"/> + </div> + + <a href="?controller=project&action=disablePublic&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Disable public access') ?></a> + +<?php else: ?> + + <a href="?controller=project&action=enablePublic&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-blue"><?= t('Enable public access') ?></a> + +<?php endif ?> diff --git a/app/Templates/project_show.php b/app/Templates/project_show.php new file mode 100644 index 00000000..98ffb581 --- /dev/null +++ b/app/Templates/project_show.php @@ -0,0 +1,51 @@ +<div class="page-header"> + <h2><?= t('Summary') ?></h2> +</div> +<ul class="settings"> + <li><strong><?= $project['is_active'] ? t('Active') : t('Inactive') ?></strong></li> + + <?php if ($project['is_public']): ?> + <li><i class="fa fa-share-alt"></i> <a href="?controller=board&action=readonly&token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a></li> + <li><i class="fa fa-rss-square"></i> <a href="?controller=project&action=feed&token=<?= $project['token'] ?>" target="_blank"><?= t('RSS feed') ?></a></li> + <?php else: ?> + <li><?= t('Public access disabled') ?></li> + <?php endif ?> + + <?php if ($project['last_modified']): ?> + <li><?= dt('Last modified on %B %e, %Y at %k:%M %p', $project['last_modified']) ?></li> + <?php endif ?> + + <?php if ($stats['nb_tasks'] > 0): ?> + + <?php if ($stats['nb_active_tasks'] > 0): ?> + <li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('%d tasks on the board', $stats['nb_active_tasks']) ?></a></li> + <?php endif ?> + + <?php if ($stats['nb_inactive_tasks'] > 0): ?> + <li><a href="?controller=project&action=tasks&project_id=<?= $project['id'] ?>"><?= t('%d closed tasks', $stats['nb_inactive_tasks']) ?></a></li> + <?php endif ?> + + <li><?= t('%d tasks in total', $stats['nb_tasks']) ?></li> + + <?php else: ?> + <li><?= t('No task for this project') ?></li> + <?php endif ?> +</ul> + +<div class="page-header"> + <h2><?= t('Board') ?></h2> +</div> +<table class="table-stripped"> + <tr> + <th width="50%"><?= t('Column') ?></th> + <th><?= t('Task limit') ?></th> + <th><?= t('Active tasks') ?></th> + </tr> + <?php foreach ($stats['columns'] as $column): ?> + <tr> + <td><?= Helper\escape($column['title']) ?></td> + <td><?= $column['task_limit'] ?: '∞' ?></td> + <td><?= $column['nb_active_tasks'] ?></td> + </tr> + <?php endforeach ?> +</table> diff --git a/app/Templates/project_sidebar.php b/app/Templates/project_sidebar.php new file mode 100644 index 00000000..d711e347 --- /dev/null +++ b/app/Templates/project_sidebar.php @@ -0,0 +1,47 @@ +<div class="project-show-sidebar"> + <h2><?= t('Actions') ?></h2> + <div class="project-show-actions"> + <ul> + <li> + <a href="?controller=project&action=show&project_id=<?= $project['id'] ?>"><?= t('Summary') ?></a> + </li> + <li> + <a href="?controller=project&action=export&project_id=<?= $project['id'] ?>"><?= t('Tasks Export') ?></a> + </li> + + <?php if (Helper\is_admin()): ?> + <li> + <a href="?controller=project&action=share&project_id=<?= $project['id'] ?>"><?= t('Public access') ?></a> + </li> + <li> + <a href="?controller=project&action=edit&project_id=<?= $project['id'] ?>"><?= t('Edit project') ?></a> + </li> + <li> + <a href="?controller=board&action=edit&project_id=<?= $project['id'] ?>"><?= t('Edit board') ?></a> + </li> + <li> + <a href="?controller=category&action=index&project_id=<?= $project['id'] ?>"><?= t('Categories management') ?></a> + </li> + <li> + <a href="?controller=project&action=users&project_id=<?= $project['id'] ?>"><?= t('Users management') ?></a> + </li> + <li> + <a href="?controller=action&action=index&project_id=<?= $project['id'] ?>"><?= t('Automatic actions') ?></a> + </li> + <li> + <a href="?controller=project&action=confirmDuplicate&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Duplicate') ?></a> + </li> + <li> + <?php if ($project['is_active']): ?> + <a href="?controller=project&action=confirmDisable&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Disable') ?></a> + <?php else: ?> + <a href="?controller=project&action=confirmEnable&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Enable') ?></a> + <?php endif ?> + </li> + <li> + <a href="?controller=project&action=confirmRemove&project_id=<?= $project['id'] ?>"><?= t('Remove') ?></a> + </li> + <?php endif ?> + </ul> + </div> +</div>
\ No newline at end of file diff --git a/app/Templates/project_tasks.php b/app/Templates/project_tasks.php index a820be13..bc27befa 100644 --- a/app/Templates/project_tasks.php +++ b/app/Templates/project_tasks.php @@ -4,6 +4,7 @@ <ul> <li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li> <li><a href="?controller=project&action=search&project_id=<?= $project['id'] ?>"><?= t('Search') ?></a></li> + <li><a href="?controller=project&action=activity&project_id=<?= $project['id'] ?>"><?= t('Activity') ?></a></li> <li><a href="?controller=project&action=index"><?= t('List of projects') ?></a></li> </ul> </div> diff --git a/app/Templates/project_users.php b/app/Templates/project_users.php index 8afac709..dca3524f 100644 --- a/app/Templates/project_users.php +++ b/app/Templates/project_users.php @@ -1,46 +1,36 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Project access list for "%s"', $project['name']) ?></h2> - <ul> - <li><a href="?controller=project"><?= t('All projects') ?></a></li> - </ul> - </div> - <section> +<div class="page-header"> + <h2><?= t('List of authorized users') ?></h2> +</div> - <?php if (! empty($users['not_allowed'])): ?> - <form method="post" action="?controller=project&action=allow&project_id=<?= $project['id'] ?>" autocomplete="off"> +<?php if (empty($users['allowed'])): ?> + <div class="alert alert-info"><?= t('Everybody have access to this project.') ?></div> +<?php else: ?> +<div class="listing"> + <p><?= t('Only those users have access to this project:') ?></p> + <ul> + <?php foreach ($users['allowed'] as $user_id => $username): ?> + <li> + <strong><?= Helper\escape($username) ?></strong> + (<a href="?controller=project&action=revoke&project_id=<?= $project['id'] ?>&user_id=<?= $user_id.Helper\param_csrf() ?>"><?= t('revoke') ?></a>) + </li> + <?php endforeach ?> + </ul> + <p><?= t('Don\'t forget that administrators have access to everything.') ?></p> +</div> +<?php endif ?> - <?= Helper\form_csrf() ?> +<?php if (! empty($users['not_allowed'])): ?> + <form method="post" action="?controller=project&action=allow&project_id=<?= $project['id'] ?>" autocomplete="off"> - <?= Helper\form_hidden('project_id', array('project_id' => $project['id'])) ?> + <?= Helper\form_csrf() ?> - <?= Helper\form_label(t('User'), 'user_id') ?> - <?= Helper\form_select('user_id', $users['not_allowed']) ?><br/> + <?= Helper\form_hidden('project_id', array('project_id' => $project['id'])) ?> - <div class="form-actions"> - <input type="submit" value="<?= t('Allow this user') ?>" class="btn btn-blue"/> - <?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a> - </div> - </form> - <?php endif ?> + <?= Helper\form_label(t('User'), 'user_id') ?> + <?= Helper\form_select('user_id', $users['not_allowed']) ?><br/> - <h3><?= t('List of authorized users') ?></h3> - <?php if (empty($users['allowed'])): ?> - <div class="alert alert-info"><?= t('Everybody have access to this project.') ?></div> - <?php else: ?> - <div class="listing"> - <p><?= t('Only those users have access to this project:') ?></p> - <ul> - <?php foreach ($users['allowed'] as $user_id => $username): ?> - <li> - <strong><?= Helper\escape($username) ?></strong> - (<a href="?controller=project&action=revoke&project_id=<?= $project['id'] ?>&user_id=<?= $user_id.Helper\param_csrf() ?>"><?= t('revoke') ?></a>) - </li> - <?php endforeach ?> - </ul> - <p><?= t('Don\'t forget that administrators have access to everything.') ?></p> - </div> - <?php endif ?> - - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Allow this user') ?>" class="btn btn-blue"/> + </div> + </form> +<?php endif ?>
\ No newline at end of file diff --git a/app/Templates/subtask_show.php b/app/Templates/subtask_show.php index 968473af..ffabbff4 100644 --- a/app/Templates/subtask_show.php +++ b/app/Templates/subtask_show.php @@ -1,60 +1,70 @@ -<div class="page-header"> - <h2><?= t('Sub-Tasks') ?></h2> -</div> +<?php if (! empty($subtasks)): ?> +<div id="subtasks" class="task-show-section"> -<?php + <div class="page-header"> + <h2><?= t('Sub-Tasks') ?></h2> + </div> -$total_spent = 0; -$total_estimated = 0; -$total_remaining = 0; + <?php -?> + $total_spent = 0; + $total_estimated = 0; + $total_remaining = 0; -<table class="subtasks-table"> - <tr> - <th width="40%"><?= t('Title') ?></th> - <th><?= t('Status') ?></th> - <th><?= t('Assignee') ?></th> - <th><?= t('Time tracking') ?></th> - <th><?= t('Actions') ?></th> - </tr> - <?php foreach ($subtasks as $subtask): ?> - <tr> - <td><?= Helper\escape($subtask['title']) ?></td> - <td><?= Helper\escape($subtask['status_name']) ?></td> - <td> - <?php if (! empty($subtask['username'])): ?> - <?= Helper\escape($subtask['name'] ?: $subtask['username']) ?> - <?php endif ?> - </td> - <td> - <?php if (! empty($subtask['time_spent'])): ?> - <strong><?= Helper\escape($subtask['time_spent']).'h' ?></strong> <?= t('spent') ?> + ?> + + <table class="subtasks-table"> + <tr> + <th width="40%"><?= t('Title') ?></th> + <th><?= t('Status') ?></th> + <th><?= t('Assignee') ?></th> + <th><?= t('Time tracking') ?></th> + <?php if (! isset($not_editable)): ?> + <th><?= t('Actions') ?></th> <?php endif ?> + </tr> + <?php foreach ($subtasks as $subtask): ?> + <tr> + <td><?= Helper\escape($subtask['title']) ?></td> + <td><?= Helper\escape($subtask['status_name']) ?></td> + <td> + <?php if (! empty($subtask['username'])): ?> + <?= Helper\escape($subtask['name'] ?: $subtask['username']) ?> + <?php endif ?> + </td> + <td> + <?php if (! empty($subtask['time_spent'])): ?> + <strong><?= Helper\escape($subtask['time_spent']).'h' ?></strong> <?= t('spent') ?> + <?php endif ?> - <?php if (! empty($subtask['time_estimated'])): ?> - <strong><?= Helper\escape($subtask['time_estimated']).'h' ?></strong> <?= t('estimated') ?> + <?php if (! empty($subtask['time_estimated'])): ?> + <strong><?= Helper\escape($subtask['time_estimated']).'h' ?></strong> <?= t('estimated') ?> + <?php endif ?> + </td> + <?php if (! isset($not_editable)): ?> + <td> + <a href="?controller=subtask&action=edit&task_id=<?= $task['id'] ?>&subtask_id=<?= $subtask['id'] ?>"><?= t('Edit') ?></a> + <?= t('or') ?> + <a href="?controller=subtask&action=confirm&task_id=<?= $task['id'] ?>&subtask_id=<?= $subtask['id'] ?>"><?= t('Remove') ?></a> + </td> <?php endif ?> - </td> - <td> - <a href="?controller=subtask&action=edit&task_id=<?= $task['id'] ?>&subtask_id=<?= $subtask['id'] ?>"><?= t('Edit') ?></a> - <?= t('or') ?> - <a href="?controller=subtask&action=confirm&task_id=<?= $task['id'] ?>&subtask_id=<?= $subtask['id'] ?>"><?= t('Remove') ?></a> - </td> - </tr> - <?php - $total_estimated += $subtask['time_estimated']; - $total_spent += $subtask['time_spent']; - $total_remaining = $total_estimated - $total_spent; - ?> - <?php endforeach ?> -</table> + </tr> + <?php + $total_estimated += $subtask['time_estimated']; + $total_spent += $subtask['time_spent']; + $total_remaining = $total_estimated - $total_spent; + ?> + <?php endforeach ?> + </table> -<div class="subtasks-time-tracking"> - <h4><?= t('Time tracking') ?></h4> - <ul> - <li><?= t('Estimate:') ?> <strong><?= Helper\escape($total_estimated) ?></strong> <?= t('hours') ?></li> - <li><?= t('Spent:') ?> <strong><?= Helper\escape($total_spent) ?></strong> <?= t('hours') ?></li> - <li><?= t('Remaining:') ?> <strong><?= Helper\escape($total_remaining > 0 ? $total_remaining : 0) ?></strong> <?= t('hours') ?></li> - </ul> -</div>
\ No newline at end of file + <div class="subtasks-time-tracking"> + <h4><?= t('Time tracking') ?></h4> + <ul> + <li><?= t('Estimate:') ?> <strong><?= Helper\escape($total_estimated) ?></strong> <?= t('hours') ?></li> + <li><?= t('Spent:') ?> <strong><?= Helper\escape($total_spent) ?></strong> <?= t('hours') ?></li> + <li><?= t('Remaining:') ?> <strong><?= Helper\escape($total_remaining > 0 ? $total_remaining : 0) ?></strong> <?= t('hours') ?></li> + </ul> + </div> + +</div> +<?php endif ?>
\ No newline at end of file diff --git a/app/Templates/task_close.php b/app/Templates/task_close.php index 5c75b72b..2abfd032 100644 --- a/app/Templates/task_close.php +++ b/app/Templates/task_close.php @@ -8,7 +8,7 @@ </p> <div class="form-actions"> - <a href="?controller=task&action=close&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <a href="?controller=task&action=close&confirmation=yes&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> <?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> </div> </div>
\ No newline at end of file diff --git a/app/Templates/task_comments.php b/app/Templates/task_comments.php new file mode 100644 index 00000000..acd84952 --- /dev/null +++ b/app/Templates/task_comments.php @@ -0,0 +1,15 @@ +<?php if (! empty($comments)): ?> +<div id="comments" class="task-show-section"> + <div class="page-header"> + <h2><?= t('Comments') ?></h2> + </div> + + <?php foreach ($comments as $comment): ?> + <?= Helper\template('comment_show', array( + 'comment' => $comment, + 'task' => $task, + 'not_editable' => isset($not_editable) && $not_editable, + )) ?> + <?php endforeach ?> +</div> +<?php endif ?>
\ No newline at end of file diff --git a/app/Templates/task_details.php b/app/Templates/task_details.php new file mode 100644 index 00000000..018b88f3 --- /dev/null +++ b/app/Templates/task_details.php @@ -0,0 +1,63 @@ +<div class="task-<?= $task['color_id'] ?> task-show-details"> + <h2><?= Helper\escape($task['title']) ?></h2> + <?php if ($task['score']): ?> + <span class="task-score"><?= Helper\escape($task['score']) ?></span> + <?php endif ?> + <ul> + <li> + <?= dt('Created on %B %e, %Y at %k:%M %p', $task['date_creation']) ?> + </li> + <?php if ($task['date_modification']): ?> + <li> + <?= dt('Last modified on %B %e, %Y at %k:%M %p', $task['date_modification']) ?> + </li> + <?php endif ?> + <?php if ($task['date_completed']): ?> + <li> + <?= dt('Completed on %B %e, %Y at %k:%M %p', $task['date_completed']) ?> + </li> + <?php endif ?> + <?php if ($task['date_due']): ?> + <li> + <strong><?= dt('Must be done before %B %e, %Y', $task['date_due']) ?></strong> + </li> + <?php endif ?> + <?php if ($task['creator_username']): ?> + <li> + <?= t('Created by %s', $task['creator_name'] ?: $task['creator_username']) ?> + </li> + <?php endif ?> + <li> + <strong> + <?php if ($task['assignee_username']): ?> + <?= t('Assigned to %s', $task['assignee_name'] ?: $task['assignee_username']) ?> + <?php else: ?> + <?= t('There is nobody assigned') ?> + <?php endif ?> + </strong> + </li> + <li> + <?= t('Column on the board:') ?> + <strong><?= Helper\escape($task['column_title']) ?></strong> + (<?= Helper\escape($task['project_name']) ?>) + </li> + <li><?= t('Task position:').' '.Helper\escape($task['position']) ?></li> + <?php if ($task['category_name']): ?> + <li> + <?= t('Category:') ?> <strong><?= Helper\escape($task['category_name']) ?></strong> + </li> + <?php endif ?> + <li> + <?php if ($task['is_active'] == 1): ?> + <?= t('Status is open') ?> + <?php else: ?> + <?= t('Status is closed') ?> + <?php endif ?> + </li> + <?php if ($project['is_public']): ?> + <li> + <a href="?controller=task&action=readonly&task_id=<?= $task['id'] ?>&token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a> + </li> + <?php endif ?> + </ul> +</div> diff --git a/app/Templates/task_duplicate.php b/app/Templates/task_duplicate.php new file mode 100644 index 00000000..ef903f1d --- /dev/null +++ b/app/Templates/task_duplicate.php @@ -0,0 +1,14 @@ +<div class="page-header"> + <h2><?= t('Duplicate a task') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to duplicate this task?') ?> + </p> + + <div class="form-actions"> + <a href="?controller=task&action=duplicate&confirmation=yes&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> + </div> +</div>
\ No newline at end of file diff --git a/app/Templates/task_duplicate_project.php b/app/Templates/task_duplicate_project.php new file mode 100644 index 00000000..86d2114a --- /dev/null +++ b/app/Templates/task_duplicate_project.php @@ -0,0 +1,24 @@ +<div class="page-header"> + <h2><?= t('Duplicate the task to another project') ?></h2> +</div> + +<?php if (empty($projects_list)): ?> + <p class="alert"><?= t('No project') ?></p> +<?php else: ?> + + <form method="post" action="?controller=task&action=copy&task_id=<?= $task['id'] ?>&project_id=<?= $task['project_id'] ?>" autocomplete="off"> + + <?= Helper\form_csrf() ?> + + <?= Helper\form_hidden('id', $values) ?> + <?= Helper\form_label(t('Project'), 'project_id') ?> + <?= Helper\form_select('project_id', $projects_list, $values, $errors) ?><br/> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + <?= t('or') ?> + <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> + </div> + </form> + +<?php endif ?>
\ No newline at end of file diff --git a/app/Templates/task_edit.php b/app/Templates/task_edit.php index a3a0eddd..83a4ca17 100644 --- a/app/Templates/task_edit.php +++ b/app/Templates/task_edit.php @@ -33,9 +33,6 @@ <?= Helper\form_label(t('Category'), 'category_id') ?> <?= Helper\form_select('category_id', $categories_list, $values, $errors) ?><br/> - <?= Helper\form_label(t('Column'), 'column_id') ?> - <?= Helper\form_select('column_id', $columns_list, $values, $errors) ?><br/> - <?= Helper\form_label(t('Color'), 'color_id') ?> <?= Helper\form_select('color_id', $colors_list, $values, $errors) ?><br/> diff --git a/app/Templates/task_edit_description.php b/app/Templates/task_edit_description.php index e3a3e8b9..2d2a4d0b 100644 --- a/app/Templates/task_edit_description.php +++ b/app/Templates/task_edit_description.php @@ -2,7 +2,7 @@ <h2><?= t('Edit the description') ?></h2> </div> -<form method="post" action="?controller=task&action=saveDescription&task_id=<?= $task['id'] ?>&ajax=<?= $ajax ?>" autocomplete="off"> +<form method="post" action="?controller=task&action=description&task_id=<?= $task['id'] ?>&ajax=<?= $ajax ?>" autocomplete="off"> <?= Helper\form_csrf() ?> @@ -13,11 +13,10 @@ <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?= t('or') ?> -<?php if ($ajax): ?> - <a href="?controller=board&action=show&project_id=<?= $task['project_id'] ?>"><?= t('cancel') ?></a> -<?php else: ?> - <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> -<?php endif ?> + <?php if ($ajax): ?> + <a href="?controller=board&action=show&project_id=<?= $task['project_id'] ?>"><?= t('cancel') ?></a> + <?php else: ?> + <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> + <?php endif ?> </div> </form> - diff --git a/app/Templates/task_move_project.php b/app/Templates/task_move_project.php new file mode 100644 index 00000000..3bc3bcb8 --- /dev/null +++ b/app/Templates/task_move_project.php @@ -0,0 +1,24 @@ +<div class="page-header"> + <h2><?= t('Move the task to another project') ?></h2> +</div> + +<?php if (empty($projects_list)): ?> + <p class="alert"><?= t('No project') ?></p> +<?php else: ?> + + <form method="post" action="?controller=task&action=move&task_id=<?= $task['id'] ?>&project_id=<?= $task['project_id'] ?>" autocomplete="off"> + + <?= Helper\form_csrf() ?> + + <?= Helper\form_hidden('id', $values) ?> + <?= Helper\form_label(t('Project'), 'project_id') ?> + <?= Helper\form_select('project_id', $projects_list, $values, $errors) ?><br/> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + <?= t('or') ?> + <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> + </div> + </form> + +<?php endif ?>
\ No newline at end of file diff --git a/app/Templates/task_open.php b/app/Templates/task_open.php index 3526ec81..d28970e3 100644 --- a/app/Templates/task_open.php +++ b/app/Templates/task_open.php @@ -8,7 +8,7 @@ </p> <div class="form-actions"> - <a href="?controller=task&action=open&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <a href="?controller=task&action=open&confirmation=yes&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> <?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> </div> </div>
\ No newline at end of file diff --git a/app/Templates/task_public.php b/app/Templates/task_public.php new file mode 100644 index 00000000..4578b720 --- /dev/null +++ b/app/Templates/task_public.php @@ -0,0 +1,11 @@ +<section id="main" class="public-task"> + + <?= Helper\template('task_details', array('task' => $task, 'project' => $project)) ?> + + <?= Helper\template('task_show_description', array('task' => $task)) ?> + + <?= Helper\template('subtask_show', array('task' => $task, 'subtasks' => $subtasks, 'not_editable' => true)) ?> + + <?= Helper\template('task_comments', array('task' => $task, 'comments' => $comments, 'not_editable' => true)) ?> + +</section>
\ No newline at end of file diff --git a/app/Templates/task_remove.php b/app/Templates/task_remove.php index dd4841db..496ac2d8 100644 --- a/app/Templates/task_remove.php +++ b/app/Templates/task_remove.php @@ -8,7 +8,7 @@ </p> <div class="form-actions"> - <a href="?controller=task&action=remove&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <a href="?controller=task&action=remove&confirmation=yes&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> <?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a> </div> </div>
\ No newline at end of file diff --git a/app/Templates/task_show.php b/app/Templates/task_show.php index 329fde80..ece4c57c 100644 --- a/app/Templates/task_show.php +++ b/app/Templates/task_show.php @@ -1,74 +1,9 @@ -<div class="task-<?= $task['color_id'] ?> task-show-details"> - <h2><?= Helper\escape($task['title']) ?></h2> - <?php if ($task['score']): ?> - <span class="task-score"><?= Helper\escape($task['score']) ?></span> - <?php endif ?> - <ul> - <li> - <?= dt('Created on %B %e, %Y at %k:%M %p', $task['date_creation']) ?> - </li> - <?php if ($task['date_modification']): ?> - <li> - <?= dt('Last modified on %B %e, %Y at %k:%M %p', $task['date_modification']) ?> - </li> - <?php endif ?> - <?php if ($task['date_completed']): ?> - <li> - <?= dt('Completed on %B %e, %Y at %k:%M %p', $task['date_completed']) ?> - </li> - <?php endif ?> - <?php if ($task['date_due']): ?> - <li> - <strong><?= dt('Must be done before %B %e, %Y', $task['date_due']) ?></strong> - </li> - <?php endif ?> - <?php if ($task['creator_username']): ?> - <li> - <?= t('Created by %s', $task['creator_name'] ?: $task['creator_username']) ?> - </li> - <?php endif ?> - <li> - <strong> - <?php if ($task['assignee_username']): ?> - <?= t('Assigned to %s', $task['assignee_name'] ?: $task['assignee_username']) ?> - <?php else: ?> - <?= t('There is nobody assigned') ?> - <?php endif ?> - </strong> - </li> - <li> - <?= t('Column on the board:') ?> - <strong><?= Helper\escape($task['column_title']) ?></strong> - (<?= Helper\escape($task['project_name']) ?>) - </li> - <?php if ($task['category_name']): ?> - <li> - <?= t('Category:') ?> <strong><?= Helper\escape($task['category_name']) ?></strong> - </li> - <?php endif ?> - <li> - <?php if ($task['is_active'] == 1): ?> - <?= t('Status is open') ?> - <?php else: ?> - <?= t('Status is closed') ?> - <?php endif ?> - </li> - </ul> -</div> - -<?php if (! empty($task['description'])): ?> -<div id="description" class="task-show-section"> - <div class="page-header"> - <h2><?= t('Description') ?></h2> - </div> +<?= Helper\template('task_details', array('task' => $task, 'project' => $project)) ?> - <article class="markdown task-show-description"> - <?= Helper\parse($task['description']) ?: t('There is no description.') ?> - </article> -</div> -<?php endif ?> +<?= Helper\template('task_show_description', array('task' => $task)) ?> +<?= Helper\template('subtask_show', array('task' => $task, 'subtasks' => $subtasks)) ?> <?php if (! empty($files)): ?> <div id="attachments" class="task-show-section"> @@ -76,25 +11,4 @@ </div> <?php endif ?> - -<?php if (! empty($subtasks)): ?> -<div id="subtasks" class="task-show-section"> - <?= Helper\template('subtask_show', array('task' => $task, 'subtasks' => $subtasks)) ?> -</div> -<?php endif ?> - - -<?php if (! empty($comments)): ?> -<div id="comments" class="task-show-section"> - <div class="page-header"> - <h2><?= t('Comments') ?></h2> - </div> - - <?php foreach ($comments as $comment): ?> - <?= Helper\template('comment_show', array( - 'comment' => $comment, - 'task' => $task, - )) ?> - <?php endforeach ?> -</div> -<?php endif ?> +<?= Helper\template('task_comments', array('task' => $task, 'comments' => $comments)) ?> diff --git a/app/Templates/task_show_description.php b/app/Templates/task_show_description.php new file mode 100644 index 00000000..2d90137f --- /dev/null +++ b/app/Templates/task_show_description.php @@ -0,0 +1,11 @@ +<?php if (! empty($task['description'])): ?> + <div id="description" class="task-show-section"> + <div class="page-header"> + <h2><?= t('Description') ?></h2> + </div> + + <article class="markdown task-show-description"> + <?= Helper\parse($task['description']) ?: t('There is no description.') ?> + </article> + </div> +<?php endif ?>
\ No newline at end of file diff --git a/app/Templates/task_sidebar.php b/app/Templates/task_sidebar.php index d97c44e2..4d363fec 100644 --- a/app/Templates/task_sidebar.php +++ b/app/Templates/task_sidebar.php @@ -4,19 +4,21 @@ <ul> <li><a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('Summary') ?></a></li> <li><a href="?controller=task&action=edit&task_id=<?= $task['id'] ?>"><?= t('Edit the task') ?></a></li> - <li><a href="?controller=task&action=editDescription&task_id=<?= $task['id'] ?>"><?= t('Edit the description') ?></a></li> + <li><a href="?controller=task&action=description&task_id=<?= $task['id'] ?>"><?= t('Edit the description') ?></a></li> <li><a href="?controller=subtask&action=create&task_id=<?= $task['id'] ?>"><?= t('Add a sub-task') ?></a></li> <li><a href="?controller=comment&action=create&task_id=<?= $task['id'] ?>"><?= t('Add a comment') ?></a></li> <li><a href="?controller=file&action=create&task_id=<?= $task['id'] ?>"><?= t('Attach a document') ?></a></li> <li><a href="?controller=task&action=duplicate&project_id=<?= $task['project_id'] ?>&task_id=<?= $task['id'] ?>"><?= t('Duplicate') ?></a></li> + <li><a href="?controller=task&action=copy&project_id=<?= $task['project_id'] ?>&task_id=<?= $task['id'] ?>"><?= t('Duplicate to another project') ?></a></li> + <li><a href="?controller=task&action=move&project_id=<?= $task['project_id'] ?>&task_id=<?= $task['id'] ?>"><?= t('Move to another project') ?></a></li> <li> <?php if ($task['is_active'] == 1): ?> - <a href="?controller=task&action=confirmClose&task_id=<?= $task['id'] ?>"><?= t('Close this task') ?></a> + <a href="?controller=task&action=close&task_id=<?= $task['id'] ?>"><?= t('Close this task') ?></a> <?php else: ?> - <a href="?controller=task&action=confirmOpen&task_id=<?= $task['id'] ?>"><?= t('Open this task') ?></a> + <a href="?controller=task&action=open&task_id=<?= $task['id'] ?>"><?= t('Open this task') ?></a> <?php endif ?> </li> - <li><a href="?controller=task&action=confirmRemove&task_id=<?= $task['id'] ?>"><?= t('Remove') ?></a></li> + <li><a href="?controller=task&action=remove&task_id=<?= $task['id'] ?>"><?= t('Remove') ?></a></li> </ul> </div> </div>
\ No newline at end of file diff --git a/app/Templates/user_edit.php b/app/Templates/user_edit.php index 8fba922f..14063d49 100644 --- a/app/Templates/user_edit.php +++ b/app/Templates/user_edit.php @@ -1,79 +1,30 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Edit user') ?></h2> - <ul> - <li><a href="?controller=user"><?= t('All users') ?></a></li> - </ul> - </div> - <section> - <form method="post" action="?controller=user&action=update" autocomplete="off"> - - <?= Helper\form_csrf() ?> - - <div class="form-column"> - - <?= Helper\form_hidden('id', $values) ?> - <?= Helper\form_hidden('is_ldap_user', $values) ?> - - <?= Helper\form_label(t('Username'), 'username') ?> - <?= Helper\form_text('username', $values, $errors, array('required', $values['is_ldap_user'] == 1 ? 'readonly' : '')) ?><br/> - - <?= Helper\form_label(t('Name'), 'name') ?> - <?= Helper\form_text('name', $values, $errors) ?><br/> +<div class="page-header"> + <h2><?= t('Edit user') ?></h2> +</div> +<form method="post" action="?controller=user&action=edit&user_id=<?= $user['id'] ?>" autocomplete="off"> - <?= Helper\form_label(t('Email'), 'email') ?> - <?= Helper\form_email('email', $values, $errors) ?><br/> + <?= Helper\form_csrf() ?> - <?= Helper\form_label(t('Default Project'), 'default_project_id') ?> - <?= Helper\form_select('default_project_id', $projects, $values, $errors) ?><br/> + <?= Helper\form_hidden('id', $values) ?> + <?= Helper\form_hidden('is_ldap_user', $values) ?> - </div> + <?= Helper\form_label(t('Username'), 'username') ?> + <?= Helper\form_text('username', $values, $errors, array('required', $values['is_ldap_user'] == 1 ? 'readonly' : '')) ?><br/> - <div class="form-column"> + <?= Helper\form_label(t('Name'), 'name') ?> + <?= Helper\form_text('name', $values, $errors) ?><br/> - <?php if ($values['is_ldap_user'] == 0): ?> + <?= Helper\form_label(t('Email'), 'email') ?> + <?= Helper\form_email('email', $values, $errors) ?><br/> - <?= Helper\form_label(t('Current password for the user "%s"', Helper\get_username()), 'current_password') ?> - <?= Helper\form_password('current_password', $values, $errors) ?><br/> + <?= Helper\form_label(t('Default project'), 'default_project_id') ?> + <?= Helper\form_select('default_project_id', $projects, $values, $errors) ?><br/> - <?= Helper\form_label(t('Password'), 'password') ?> - <?= Helper\form_password('password', $values, $errors) ?><br/> + <?php if (Helper\is_admin()): ?> + <?= Helper\form_checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?><br/> + <?php endif ?> - <?= Helper\form_label(t('Confirmation'), 'confirmation') ?> - <?= Helper\form_password('confirmation', $values, $errors) ?><br/> - - <?php endif ?> - - <?php if (Helper\is_admin()): ?> - <?= Helper\form_checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?><br/> - <?php endif ?> - - <ul> - <?php if (GOOGLE_AUTH && Helper\is_current_user($values['id'])): ?> - <li> - <?php if (empty($values['google_id'])): ?> - <a href="?controller=user&action=google<?= Helper\param_csrf() ?>"><?= t('Link my Google Account') ?></a> - <?php else: ?> - <a href="?controller=user&action=unlinkGoogle<?= Helper\param_csrf() ?>"><?= t('Unlink my Google Account') ?></a> - <?php endif ?> - </li> - <?php endif ?> - - <?php if (GITHUB_AUTH && Helper\is_current_user($values['id'])): ?> - <li> - <?php if (empty($values['github_id'])): ?> - <a href="?controller=user&action=gitHub<?= Helper\param_csrf() ?>"><?= t('Link my GitHub Account') ?></a> - <?php else: ?> - <a href="?controller=user&action=unlinkGitHub<?= Helper\param_csrf() ?>"><?= t('Unlink my GitHub Account') ?></a> - <?php endif ?> - </li> - <?php endif ?> - </ul> - </div> - - <div class="form-actions"> - <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?= t('or') ?> <a href="?controller=user"><?= t('cancel') ?></a> - </div> - </form> - </section> -</section>
\ No newline at end of file + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?= t('or') ?> <a href="?controller=user&action=show&user_id=<?= $user['id'] ?>"><?= t('cancel') ?></a> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/user_external.php b/app/Templates/user_external.php new file mode 100644 index 00000000..a67d886e --- /dev/null +++ b/app/Templates/user_external.php @@ -0,0 +1,39 @@ +<div class="page-header"> + <h2><?= t('External authentications') ?></h2> +</div> + +<?php if (GOOGLE_AUTH): ?> + <h3><i class="fa fa-google"></i> <?= t('Google Account') ?></h3> + + <p class="settings"> + <?php if (Helper\is_current_user($user['id'])): ?> + <?php if (empty($user['google_id'])): ?> + <a href="?controller=user&action=google<?= Helper\param_csrf() ?>"><?= t('Link my Google Account') ?></a> + <?php else: ?> + <a href="?controller=user&action=unlinkGoogle<?= Helper\param_csrf() ?>"><?= t('Unlink my Google Account') ?></a> + <?php endif ?> + <?php else: ?> + <?= empty($user['google_id']) ? t('No account linked.') : t('Account linked.') ?> + <?php endif ?> + </p> +<?php endif ?> + +<?php if (GITHUB_AUTH): ?> + <h3><i class="fa fa-github"></i> <?= t('Github Account') ?></h3> + + <p class="settings"> + <?php if (Helper\is_current_user($user['id'])): ?> + <?php if (empty($user['github_id'])): ?> + <a href="?controller=user&action=gitHub<?= Helper\param_csrf() ?>"><?= t('Link my GitHub Account') ?></a> + <?php else: ?> + <a href="?controller=user&action=unlinkGitHub<?= Helper\param_csrf() ?>"><?= t('Unlink my GitHub Account') ?></a> + <?php endif ?> + <?php else: ?> + <?= empty($user['github_id']) ? t('No account linked.') : t('Account linked.') ?> + <?php endif ?> + </p> +<?php endif ?> + +<?php if (! GOOGLE_AUTH && ! GITHUB_AUTH): ?> + <p class="alert"><?= t('No external authentication enabled.') ?></p> +<?php endif ?> diff --git a/app/Templates/user_index.php b/app/Templates/user_index.php index f6302a6b..d4e1bbf9 100644 --- a/app/Templates/user_index.php +++ b/app/Templates/user_index.php @@ -13,17 +13,23 @@ <?php else: ?> <table> <tr> + <th><?= t('Id') ?></th> <th><?= t('Username') ?></th> <th><?= t('Name') ?></th> <th><?= t('Email') ?></th> <th><?= t('Administrator') ?></th> - <th><?= t('Default Project') ?></th> - <th><?= t('Actions') ?></th> + <th><?= t('Default project') ?></th> + <th><?= t('Notifications') ?></th> + <th><?= t('External accounts') ?></th> + <th><?= t('Account type') ?></th> </tr> <?php foreach ($users as $user): ?> <tr> <td> - <span title="user_id=<?= $user['id'] ?>"><?= Helper\escape($user['username']) ?></span> + <a href="?controller=user&action=show&user_id=<?= $user['id'] ?>">#<?= $user['id'] ?></a> + </td> + <td> + <a href="?controller=user&action=show&user_id=<?= $user['id'] ?>"><?= Helper\escape($user['username']) ?></a> </td> <td> <?= Helper\escape($user['name']) ?> @@ -38,15 +44,24 @@ <?= (isset($user['default_project_id']) && isset($projects[$user['default_project_id']])) ? Helper\escape($projects[$user['default_project_id']]) : t('None'); ?> </td> <td> - <?php if (Helper\is_admin() || Helper\is_current_user($user['id'])): ?> - <a href="?controller=user&action=edit&user_id=<?= $user['id'] ?>"><?= t('edit') ?></a> + <?php if ($user['notifications_enabled'] == 1): ?> + <?= t('Enabled') ?> + <?php else: ?> + <?= t('Disabled') ?> <?php endif ?> - <?php if (Helper\is_admin()): ?> - <?php if (count($users) > 1): ?> - <?= t('or') ?> - <a href="?controller=user&action=confirm&user_id=<?= $user['id'] ?>"><?= t('remove') ?></a> - <?php endif ?> + </td> + <td> + <ul class="no-bullet"> + <?php if ($user['google_id']): ?> + <li><i class="fa fa-google"></i> <?= t('Google account linked') ?></li> <?php endif ?> + <?php if ($user['github_id']): ?> + <li><i class="fa fa-github"></i> <?= t('Github account linked') ?></li> + <?php endif ?> + </ul> + </td> + <td> + <?= $user['is_ldap_user'] ? t('Remote') : t('Local') ?> </td> </tr> <?php endforeach ?> diff --git a/app/Templates/user_last.php b/app/Templates/user_last.php new file mode 100644 index 00000000..0b55b0d5 --- /dev/null +++ b/app/Templates/user_last.php @@ -0,0 +1,24 @@ +<div class="page-header"> + <h2><?= t('Last logins') ?></h2> +</div> + +<?php if (empty($last_logins)): ?> + <p class="alert"><?= t('Never connected.') ?></p> +<?php else: ?> + <table class="table-small"> + <tr> + <th><?= t('Login date') ?></th> + <th><?= t('Authentication method') ?></th> + <th><?= t('IP address') ?></th> + <th><?= t('User agent') ?></th> + </tr> + <?php foreach($last_logins as $login): ?> + <tr> + <td><?= dt('%B %e, %Y at %k:%M %p', $login['date_creation']) ?></td> + <td><?= Helper\escape($login['auth_type']) ?></td> + <td><?= Helper\escape($login['ip']) ?></td> + <td><?= Helper\escape(Helper\summary($login['user_agent'])) ?></td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?>
\ No newline at end of file diff --git a/app/Templates/user_layout.php b/app/Templates/user_layout.php new file mode 100644 index 00000000..890b0c0a --- /dev/null +++ b/app/Templates/user_layout.php @@ -0,0 +1,19 @@ +<section id="main"> + <div class="page-header"> + <h2><?= Helper\escape($user['name'] ?: $user['username']).' (#'.$user['id'].')' ?></h2> + <ul> + <li><a href="?controller=user&action=index"><?= t('All users') ?></a></li> + <?php if (Helper\is_admin()): ?> + <li><a href="?controller=user&action=create"><?= t('New user') ?></a></li> + <?php endif ?> + </ul> + </div> + <section class="user-show" id="user-section"> + + <?= Helper\template('user_sidebar', array('user' => $user)) ?> + + <div class="user-show-main"> + <?= $user_content_for_layout ?> + </div> + </section> +</section>
\ No newline at end of file diff --git a/app/Templates/user_new.php b/app/Templates/user_new.php index 3e22b7ee..158813cb 100644 --- a/app/Templates/user_new.php +++ b/app/Templates/user_new.php @@ -10,33 +10,25 @@ <?= Helper\form_csrf() ?> - <div class="form-column"> + <?= Helper\form_label(t('Username'), 'username') ?> + <?= Helper\form_text('username', $values, $errors, array('autofocus', 'required')) ?><br/> - <?= Helper\form_label(t('Username'), 'username') ?> - <?= Helper\form_text('username', $values, $errors, array('autofocus', 'required')) ?><br/> + <?= Helper\form_label(t('Name'), 'name') ?> + <?= Helper\form_text('name', $values, $errors) ?><br/> - <?= Helper\form_label(t('Name'), 'name') ?> - <?= Helper\form_text('name', $values, $errors) ?><br/> + <?= Helper\form_label(t('Email'), 'email') ?> + <?= Helper\form_email('email', $values, $errors) ?><br/> - <?= Helper\form_label(t('Email'), 'email') ?> - <?= Helper\form_email('email', $values, $errors) ?><br/> + <?= Helper\form_label(t('Password'), 'password') ?> + <?= Helper\form_password('password', $values, $errors, array('required')) ?><br/> - <?= Helper\form_label(t('Default Project'), 'default_project_id') ?> - <?= Helper\form_select('default_project_id', $projects, $values, $errors) ?><br/> + <?= Helper\form_label(t('Confirmation'), 'confirmation') ?> + <?= Helper\form_password('confirmation', $values, $errors, array('required')) ?><br/> - </div> - - <div class="form-column"> - - <?= Helper\form_label(t('Password'), 'password') ?> - <?= Helper\form_password('password', $values, $errors, array('required')) ?><br/> + <?= Helper\form_label(t('Default project'), 'default_project_id') ?> + <?= Helper\form_select('default_project_id', $projects, $values, $errors) ?><br/> - <?= Helper\form_label(t('Confirmation'), 'confirmation') ?> - <?= Helper\form_password('confirmation', $values, $errors, array('required')) ?><br/> - - <?= Helper\form_checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?> - - </div> + <?= Helper\form_checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?> <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> diff --git a/app/Templates/user_notifications.php b/app/Templates/user_notifications.php new file mode 100644 index 00000000..13dd9809 --- /dev/null +++ b/app/Templates/user_notifications.php @@ -0,0 +1,22 @@ +<div class="page-header"> + <h2><?= t('Email notifications') ?></h2> +</div> + +<form method="post" action="?controller=user&action=notifications&user_id=<?= $user['id'] ?>" autocomplete="off"> + + <?= Helper\form_csrf() ?> + + <?= Helper\form_checkbox('notifications_enabled', t('Enable email notifications'), '1', $notifications['notifications_enabled'] == 1) ?><br/> + + <p><?= t('I want to receive notifications only for those projects:') ?><br/><br/></p> + + <div class="form-checkbox-group"> + <?php foreach ($projects as $project_id => $project_name): ?> + <?= Helper\form_checkbox('projects['.$project_id.']', $project_name, '1', isset($notifications['project_'.$project_id])) ?> + <?php endforeach ?> + </div> + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + <?= t('or') ?> <a href="?controller=user&action=show&user_id=<?= $user['id'] ?>"><?= t('cancel') ?></a> + </div> +</form>
\ No newline at end of file diff --git a/app/Templates/user_password.php b/app/Templates/user_password.php new file mode 100644 index 00000000..a494e42d --- /dev/null +++ b/app/Templates/user_password.php @@ -0,0 +1,23 @@ +<div class="page-header"> + <h2><?= t('Password modification') ?></h2> +</div> + +<form method="post" action="?controller=user&action=password&user_id=<?= $user['id'] ?>" autocomplete="off"> + + <?= Helper\form_hidden('id', $values) ?> + <?= Helper\form_csrf() ?> + + <?= Helper\form_label(t('Current password for the user "%s"', Helper\get_username()), 'current_password') ?> + <?= Helper\form_password('current_password', $values, $errors) ?><br/> + + <?= Helper\form_label(t('Password'), 'password') ?> + <?= Helper\form_password('password', $values, $errors) ?><br/> + + <?= Helper\form_label(t('Confirmation'), 'confirmation') ?> + <?= Helper\form_password('confirmation', $values, $errors) ?><br/> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?= t('or') ?> <a href="?controller=user&action=show&user_id=<?= $user['id'] ?>"><?= t('cancel') ?></a> + </div> + +</form>
\ No newline at end of file diff --git a/app/Templates/user_remove.php b/app/Templates/user_remove.php index 45774d27..c20ccbba 100644 --- a/app/Templates/user_remove.php +++ b/app/Templates/user_remove.php @@ -1,14 +1,12 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Remove user') ?></h2> - </div> +<div class="page-header"> + <h2><?= t('Remove user') ?></h2> +</div> - <div class="confirm"> - <p class="alert alert-info"><?= t('Do you really want to remove this user: "%s"?', $user['name'] ?: $user['username']) ?></p> +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this user: "%s"?', $user['name'] ?: $user['username']) ?></p> - <div class="form-actions"> - <a href="?controller=user&action=remove&user_id=<?= $user['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> - <?= t('or') ?> <a href="?controller=user"><?= t('cancel') ?></a> - </div> + <div class="form-actions"> + <a href="?controller=user&action=remove&confirmation=yes&user_id=<?= $user['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a> + <?= t('or') ?> <a href="?controller=user&action=show&user_id=<?= $user['id'] ?>"><?= t('cancel') ?></a> </div> -</section>
\ No newline at end of file +</div>
\ No newline at end of file diff --git a/app/Templates/user_sessions.php b/app/Templates/user_sessions.php new file mode 100644 index 00000000..b647d726 --- /dev/null +++ b/app/Templates/user_sessions.php @@ -0,0 +1,26 @@ +<div class="page-header"> + <h2><?= t('Persistent connections') ?></h2> +</div> + +<?php if (empty($sessions)): ?> + <p class="alert"><?= t('No session.') ?></p> +<?php else: ?> + <table class="table-small"> + <tr> + <th><?= t('Creation date') ?></th> + <th><?= t('Expiration date') ?></th> + <th><?= t('IP address') ?></th> + <th><?= t('User agent') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach($sessions as $session): ?> + <tr> + <td><?= dt('%B %e, %Y at %k:%M %p', $session['date_creation']) ?></td> + <td><?= dt('%B %e, %Y at %k:%M %p', $session['expiration']) ?></td> + <td><?= Helper\escape($session['ip']) ?></td> + <td><?= Helper\escape(Helper\summary($session['user_agent'])) ?></td> + <td><a href="?controller=user&action=removeSession&user_id=<?= $user['id'] ?>&id=<?= $session['id'].Helper\param_csrf() ?>"><?= t('Remove') ?></a></td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Templates/user_show.php b/app/Templates/user_show.php new file mode 100644 index 00000000..5d42d3cf --- /dev/null +++ b/app/Templates/user_show.php @@ -0,0 +1,12 @@ +<div class="page-header"> + <h2><?= t('Summary') ?></h2> +</div> +<ul class="settings"> + <li><?= t('Username:') ?> <strong><?= Helper\escape($user['username']) ?></strong></li> + <li><?= t('Name:') ?> <strong><?= Helper\escape($user['name']) ?></strong></li> + <li><?= t('Email:') ?> <strong><?= Helper\escape($user['email']) ?></strong></li> + <li><?= t('Default project:') ?> <strong><?= (isset($user['default_project_id']) && isset($projects[$user['default_project_id']])) ? Helper\escape($projects[$user['default_project_id']]) : t('None'); ?></strong></li> + <li><?= t('Notifications:') ?> <strong><?= $user['notifications_enabled'] == 1 ? t('Enabled') : t('Disabled') ?></strong></li> + <li><?= t('Group:') ?> <strong><?= $user['is_admin'] ? t('Administrator') : t('Regular user') ?></strong></li> + <li><?= t('Account type:') ?> <strong><?= $user['is_ldap_user'] ? t('Remote') : t('Local') ?></strong></li> +</ul> diff --git a/app/Templates/user_sidebar.php b/app/Templates/user_sidebar.php new file mode 100644 index 00000000..9d8f8b46 --- /dev/null +++ b/app/Templates/user_sidebar.php @@ -0,0 +1,42 @@ +<div class="project-show-sidebar"> + <h2><?= t('Actions') ?></h2> + <div class="user-show-actions"> + <ul> + <li> + <a href="?controller=user&action=show&user_id=<?= $user['id'] ?>"><?= t('Summary') ?></a> + </li> + + <?php if (Helper\is_admin() || Helper\is_current_user($user['id'])): ?> + <li> + <a href="?controller=user&action=edit&user_id=<?= $user['id'] ?>"><?= t('Edit profile') ?></a> + </li> + + <?php if ($user['is_ldap_user'] == 0): ?> + <li> + <a href="?controller=user&action=password&user_id=<?= $user['id'] ?>"><?= t('Change password') ?></a> + </li> + <?php endif ?> + + <li> + <a href="?controller=user&action=notifications&user_id=<?= $user['id'] ?>"><?= t('Email notifications') ?></a> + </li> + <li> + <a href="?controller=user&action=external&user_id=<?= $user['id'] ?>"><?= t('External accounts') ?></a> + </li> + <li> + <a href="?controller=user&action=last&user_id=<?= $user['id'] ?>"><?= t('Last logins') ?></a> + </li> + <li> + <a href="?controller=user&action=sessions&user_id=<?= $user['id'] ?>"><?= t('Persistent connections') ?></a> + </li> + <?php endif ?> + + <?php if (Helper\is_admin()): ?> + <li> + <a href="?controller=user&action=remove&user_id=<?= $user['id'] ?>"><?= t('Remove') ?></a> + </li> + <?php endif ?> + + </ul> + </div> +</div>
\ No newline at end of file diff --git a/app/common.php b/app/common.php index f92e3ddb..f46e3c6b 100644 --- a/app/common.php +++ b/app/common.php @@ -62,6 +62,7 @@ defined('LDAP_AUTH') or define('LDAP_AUTH', false); defined('LDAP_SERVER') or define('LDAP_SERVER', ''); defined('LDAP_PORT') or define('LDAP_PORT', 389); defined('LDAP_SSL_VERIFY') or define('LDAP_SSL_VERIFY', true); +defined('LDAP_BIND_TYPE') or define('LDAP_BIND_TYPE', 'anonymous'); defined('LDAP_USERNAME') or define('LDAP_USERNAME', null); defined('LDAP_PASSWORD') or define('LDAP_PASSWORD', null); defined('LDAP_ACCOUNT_BASE') or define('LDAP_ACCOUNT_BASE', ''); @@ -83,6 +84,7 @@ defined('GITHUB_CLIENT_SECRET') or define('GITHUB_CLIENT_SECRET', ''); defined('REVERSE_PROXY_AUTH') or define('REVERSE_PROXY_AUTH', false); defined('REVERSE_PROXY_USER_HEADER') or define('REVERSE_PROXY_USER_HEADER', 'REMOTE_USER'); defined('REVERSE_PROXY_DEFAULT_ADMIN') or define('REVERSE_PROXY_DEFAULT_ADMIN', ''); +defined('REVERSE_PROXY_DEFAULT_DOMAIN') or define('REVERSE_PROXY_DEFAULT_DOMAIN', ''); // Mail configuration defined('MAIL_FROM') or define('MAIL_FROM', 'notifications@kanboard.net'); @@ -150,7 +152,8 @@ $registry->db = function() use ($registry) { return $db; } else { - die('Unable to migrate database schema!'); + $errors = $db->getLogMessages(); + die('Unable to migrate database schema: <br/><br/><strong>'.(isset($errors[0]) ? $errors[0] : 'Unknown error').'</strong>'); } }; diff --git a/app/helpers.php b/app/helpers.php index 9a2b4cbf..85a2507d 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -40,6 +40,11 @@ function get_username() return $_SESSION['user']['name'] ?: $_SESSION['user']['username']; } +function get_user_id() +{ + return $_SESSION['user']['id']; +} + function parse($text) { $text = markdown($text); @@ -60,7 +65,7 @@ function markdown($text) function get_current_base_url() { - $url = isset($_SERVER['HTTPS']) ? 'https://' : 'http://'; + $url = \Core\Tool::isHTTPS() ? 'https://' : 'http://'; $url .= $_SERVER['SERVER_NAME']; $url .= $_SERVER['SERVER_PORT'] == 80 || $_SERVER['SERVER_PORT'] == 443 ? '' : ':'.$_SERVER['SERVER_PORT']; $url .= dirname($_SERVER['PHP_SELF']) !== '/' ? dirname($_SERVER['PHP_SELF']).'/' : '/'; @@ -110,15 +115,12 @@ function get_host_from_url($url) return escape(parse_url($url, PHP_URL_HOST)) ?: $url; } -function summary($value, $min_length = 5, $max_length = 120, $end = '[...]') +function summary($value, $max_length = 85, $end = '[...]') { $length = strlen($value); if ($length > $max_length) { - return substr($value, 0, strpos($value, ' ', $max_length)).' '.$end; - } - else if ($length < $min_length) { - return ''; + return substr($value, 0, $max_length).' '.$end; } return $value; diff --git a/app/translator.php b/app/translator.php index 338821d3..ac4d72e2 100644 --- a/app/translator.php +++ b/app/translator.php @@ -9,6 +9,13 @@ function t() return call_user_func_array(array($t, 'translate'), func_get_args()); } +// translate with no html escaping +function e() +{ + $t = new Translator; + return call_user_func_array(array($t, 'translateNoEscaping'), func_get_args()); +} + // Get a locale currency function c($value) { diff --git a/assets/css/app.css b/assets/css/app.css index 2866151d..a612a8f4 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -60,6 +60,15 @@ h3 { font-size: 1.2em; } +ul.no-bullet li { + list-style-type: none; + margin-left: 0; +} + +.pull-right { + text-align: right; +} + /* tables */ table { width: 100%; @@ -103,7 +112,7 @@ td li { } .table-small { - font-size: 0.85em; + font-size: 0.8em; } .table-hover tr:hover td { @@ -136,6 +145,7 @@ input[type="text"] { color: #888; border: 1px solid #ccc; width: 400px; + max-width: 95%; font-size: 1.0em; height: 25px; padding-bottom: 0; @@ -166,11 +176,16 @@ input[type="number"] { textarea { border: 1px solid #ccc; width: 400px; + max-width: 95%; height: 200px; font-size: 1.0em; font-family: sans-serif; } +select { + max-width: 95%; +} + ::-webkit-input-placeholder { color: #bbb; padding-top: 2px; @@ -420,13 +435,16 @@ a.btn-red:hover, 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; @@ -458,6 +476,15 @@ nav .active a { font-weight: bold; } +.username a { + color: #000; +} + +.username a:hover { + color: red; + text-decoration: underline; +} + .logo { color: #DF5353; letter-spacing: 1px; @@ -535,6 +562,12 @@ a.filter-on { margin-top: 5px; } +.public-task { + max-width: 700px; + margin: 0 auto; + margin-top: 5px; +} + #board th a { text-decoration: none; font-size: 150%; @@ -585,7 +618,7 @@ a.filter-on { } .task-board-recent { - box-shadow: 0px 0px 10px rgba(82, 158, 236, 1); + box-shadow: 0px 0px 10px rgba(130, 130, 130, 1); } .task-table a, @@ -673,14 +706,20 @@ a.task-board-nobody { } /* task view */ +.user-show, +.project-show, .task-show { position: relative; } +.user-show-main, +.project-show-main, .task-show-main { margin-left: 330px; } +.user-show-sidebar, +.project-show-sidebar, .task-show-sidebar { position: absolute; left: 0px; @@ -693,6 +732,8 @@ a.task-board-nobody { border-radius: 5px; } +.user-show-sidebar li, +.project-show-sidebar li, .task-show-sidebar li { list-style-type: square; margin-left: 30px; @@ -968,6 +1009,63 @@ tr td.task-orange, margin-bottom: 15px; } +/* project view */ +.project-listing { + border-left: 3px solid #000; + margin-left: 35px; + padding-bottom: 10px; + max-width: 700px; +} + +.project-listing li { + font-size: 1.3em; + line-height: 1.7em; + list-style-type: none; + margin-left: 20px; + border-bottom: 1px dashed #ccc; +} + +.project-listing li:hover { + border-color: #333; +} + +.project-listing a { + text-decoration: none; +} + +.project-listing a:hover, +.project-listing a:focus { + color: #000; +} + +/* activity */ +.activity-event { + margin-bottom: 20px; +} + +.activity-datetime { + color: #999; + font-size: 0.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: 0.9em; + color: #aaa; + padding-top: 5px; +} + /* confirmation box */ .confirm { max-width: 700px; @@ -1000,46 +1098,70 @@ tr td.task-orange, } /* responsive design */ -@media only screen and (min-width : 600px) and (max-width : 1024px) { +@media only screen and (min-width : 768px) and (max-width : 1024px) { .hide-tablet { display: none; } - .form-column { - float: none; - margin: 0; - padding: 0; - } - - #board { - font-size: 0.85em; + body { + font-size: 0.9em; } .project-menu { - font-size: 0.7em; + font-size: 0.8em; } - table input[type="text"] { - width: 200px; + .task-board-title { + font-size: 1.5em; } } -@media only screen and (max-width : 600px) { +@media only screen and (max-width : 768px) { - header { - font-size: 0.8em; + .hide-tablet { + display: none; } + body { + font-size: 0.85em; + } + + .logo, .project-menu { display: none; } - #board { - margin-top: 10px; + nav li:first-child { + padding-left: 0; + } + + .username { + display: block; + text-align: right; } - .task-board .task-score { + .user-show-sidebar, + .project-show-sidebar, + .task-show-sidebar { + width: 200px; + } + + .user-show-main, + .project-show-main, + .task-show-main { + margin-left: 230px; + } + + table input[type="text"] { + width: 150px; + } + + .task-score { display: none; } + + .task-board-title { + font-size: 1.5em; + } } diff --git a/assets/css/font-awesome.min.css b/assets/css/font-awesome.min.css index 449d6ac5..ec53d4d6 100644 --- a/assets/css/font-awesome.min.css +++ b/assets/css/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.2.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.0.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333333333em;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.2857142857142858em;text-align:center}.fa-ul{padding-left:0;margin-left:2.142857142857143em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;top:.14285714285714285em;text-align:center}.fa-li.fa-lg{left:-1.8571428571428572em}.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:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0,mirror=1);-webkit-transform:scale(-1,1);-moz-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-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);-moz-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.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-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-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{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: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-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-asc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-desc: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-reply-all:before{content:"\f122"}.fa-mail-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{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"}
\ No newline at end of file + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.2.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.2.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}.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}@-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: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{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-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"}
\ No newline at end of file diff --git a/assets/fonts/FontAwesome.otf b/assets/fonts/FontAwesome.otf Binary files differindex 8b0f54e4..81c9ad94 100644 --- a/assets/fonts/FontAwesome.otf +++ b/assets/fonts/FontAwesome.otf diff --git a/assets/fonts/fontawesome-webfont.eot b/assets/fonts/fontawesome-webfont.eot Binary files differindex 7c79c6a6..84677bc0 100755 --- a/assets/fonts/fontawesome-webfont.eot +++ b/assets/fonts/fontawesome-webfont.eot diff --git a/assets/fonts/fontawesome-webfont.svg b/assets/fonts/fontawesome-webfont.svg index 45fdf338..d907b25a 100755 --- a/assets/fonts/fontawesome-webfont.svg +++ b/assets/fonts/fontawesome-webfont.svg @@ -14,10 +14,11 @@ <glyph unicode="®" horiz-adv-x="1792" /> <glyph unicode="´" horiz-adv-x="1792" /> <glyph unicode="Æ" horiz-adv-x="1792" /> +<glyph unicode="Ø" horiz-adv-x="1792" /> <glyph unicode=" " horiz-adv-x="768" /> -<glyph unicode=" " /> +<glyph unicode=" " horiz-adv-x="1537" /> <glyph unicode=" " horiz-adv-x="768" /> -<glyph unicode=" " /> +<glyph unicode=" " horiz-adv-x="1537" /> <glyph unicode=" " horiz-adv-x="512" /> <glyph unicode=" " horiz-adv-x="384" /> <glyph unicode=" " horiz-adv-x="256" /> @@ -30,7 +31,7 @@ <glyph unicode="™" horiz-adv-x="1792" /> <glyph unicode="∞" horiz-adv-x="1792" /> <glyph unicode="≠" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="500" d="M0 0z" /> +<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" /> <glyph unicode="" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" /> <glyph unicode="" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" /> <glyph unicode="" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" /> @@ -52,7 +53,7 @@ <glyph unicode="" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" /> <glyph unicode="" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" /> <glyph unicode="" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" /> -<glyph unicode="" horiz-adv-x="1280" d="M128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280zM768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z " /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z " /> <glyph unicode="" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" /> <glyph unicode="" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" /> @@ -77,11 +78,11 @@ <glyph unicode="" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" /> <glyph unicode="" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" /> <glyph unicode="" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M725 977l-170 -450q73 -1 153.5 -2t119 -1.5t52.5 -0.5l29 2q-32 95 -92 241q-53 132 -92 211zM21 -128h-21l2 79q22 7 80 18q89 16 110 31q20 16 48 68l237 616l280 724h75h53l11 -21l205 -480q103 -242 124 -297q39 -102 96 -235q26 -58 65 -164q24 -67 65 -149 q22 -49 35 -57q22 -19 69 -23q47 -6 103 -27q6 -39 6 -57q0 -14 -1 -26q-80 0 -192 8q-93 8 -189 8q-79 0 -135 -2l-200 -11l-58 -2q0 45 4 78l131 28q56 13 68 23q12 12 12 27t-6 32l-47 114l-92 228l-450 2q-29 -65 -104 -274q-23 -64 -23 -84q0 -31 17 -43 q26 -21 103 -32q3 0 13.5 -2t30 -5t40.5 -6q1 -28 1 -58q0 -17 -2 -27q-66 0 -349 20l-48 -8q-81 -14 -167 -14z" /> -<glyph unicode="" horiz-adv-x="1408" d="M555 15q76 -32 140 -32q131 0 216 41t122 113q38 70 38 181q0 114 -41 180q-58 94 -141 126q-80 32 -247 32q-74 0 -101 -10v-144l-1 -173l3 -270q0 -15 12 -44zM541 761q43 -7 109 -7q175 0 264 65t89 224q0 112 -85 187q-84 75 -255 75q-52 0 -130 -13q0 -44 2 -77 q7 -122 6 -279l-1 -98q0 -43 1 -77zM0 -128l2 94q45 9 68 12q77 12 123 31q17 27 21 51q9 66 9 194l-2 497q-5 256 -9 404q-1 87 -11 109q-1 4 -12 12q-18 12 -69 15q-30 2 -114 13l-4 83l260 6l380 13l45 1q5 0 14 0.5t14 0.5q1 0 21.5 -0.5t40.5 -0.5h74q88 0 191 -27 q43 -13 96 -39q57 -29 102 -76q44 -47 65 -104t21 -122q0 -70 -32 -128t-95 -105q-26 -20 -150 -77q177 -41 267 -146q92 -106 92 -236q0 -76 -29 -161q-21 -62 -71 -117q-66 -72 -140 -108q-73 -36 -203 -60q-82 -15 -198 -11l-197 4q-84 2 -298 -11q-33 -3 -272 -11z" /> -<glyph unicode="" horiz-adv-x="1024" d="M0 -126l17 85q4 1 77 20q76 19 116 39q29 37 41 101l27 139l56 268l12 64q8 44 17 84.5t16 67t12.5 46.5t9 30.5t3.5 11.5l29 157l16 63l22 135l8 50v38q-41 22 -144 28q-28 2 -38 4l19 103l317 -14q39 -2 73 -2q66 0 214 9q33 2 68 4.5t36 2.5q-2 -19 -6 -38 q-7 -29 -13 -51q-55 -19 -109 -31q-64 -16 -101 -31q-12 -31 -24 -88q-9 -44 -13 -82q-44 -199 -66 -306l-61 -311l-38 -158l-43 -235l-12 -45q-2 -7 1 -27q64 -15 119 -21q36 -5 66 -10q-1 -29 -7 -58q-7 -31 -9 -41q-18 0 -23 -1q-24 -2 -42 -2q-9 0 -28 3q-19 4 -145 17 l-198 2q-41 1 -174 -11q-74 -7 -98 -9z" /> -<glyph unicode="" horiz-adv-x="1792" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l215 -1h293l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -42.5 2t-103.5 -1t-111 -1 q-34 0 -67 -5q-10 -97 -8 -136l1 -152v-332l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-88 0 -233 -14q-48 -4 -70 -4q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q8 192 6 433l-5 428q-1 62 -0.5 118.5t0.5 102.5t-2 57t-6 15q-6 5 -14 6q-38 6 -148 6q-43 0 -100 -13.5t-73 -24.5q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1744 128q33 0 42 -18.5t-11 -44.5 l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80z" /> -<glyph unicode="" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l446 -1h318l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -58.5 2t-138.5 -1t-128 -1 q-94 0 -127 -5q-10 -97 -8 -136l1 -152v52l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-82 0 -233 -13q-45 -5 -70 -5q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q6 137 6 433l-5 44q0 265 -2 278q-2 11 -6 15q-6 5 -14 6q-38 6 -148 6q-50 0 -168.5 -14t-132.5 -24q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1505 113q26 -20 26 -49t-26 -49l-162 -126 q-26 -20 -44.5 -11t-18.5 42v80h-1024v-80q0 -33 -18.5 -42t-44.5 11l-162 126q-26 20 -26 49t26 49l162 126q26 20 44.5 11t18.5 -42v-80h1024v80q0 33 18.5 42t44.5 -11z" /> +<glyph unicode="" horiz-adv-x="1664" d="M725 977l-170 -450q33 0 136.5 -2t160.5 -2q19 0 57 2q-87 253 -184 452zM0 -128l2 79q23 7 56 12.5t57 10.5t49.5 14.5t44.5 29t31 50.5l237 616l280 724h75h53q8 -14 11 -21l205 -480q33 -78 106 -257.5t114 -274.5q15 -34 58 -144.5t72 -168.5q20 -45 35 -57 q19 -15 88 -29.5t84 -20.5q6 -38 6 -57q0 -4 -0.5 -13t-0.5 -13q-63 0 -190 8t-191 8q-76 0 -215 -7t-178 -8q0 43 4 78l131 28q1 0 12.5 2.5t15.5 3.5t14.5 4.5t15 6.5t11 8t9 11t2.5 14q0 16 -31 96.5t-72 177.5t-42 100l-450 2q-26 -58 -76.5 -195.5t-50.5 -162.5 q0 -22 14 -37.5t43.5 -24.5t48.5 -13.5t57 -8.5t41 -4q1 -19 1 -58q0 -9 -2 -27q-58 0 -174.5 10t-174.5 10q-8 0 -26.5 -4t-21.5 -4q-80 -14 -188 -14z" /> +<glyph unicode="" horiz-adv-x="1408" d="M555 15q74 -32 140 -32q376 0 376 335q0 114 -41 180q-27 44 -61.5 74t-67.5 46.5t-80.5 25t-84 10.5t-94.5 2q-73 0 -101 -10q0 -53 -0.5 -159t-0.5 -158q0 -8 -1 -67.5t-0.5 -96.5t4.5 -83.5t12 -66.5zM541 761q42 -7 109 -7q82 0 143 13t110 44.5t74.5 89.5t25.5 142 q0 70 -29 122.5t-79 82t-108 43.5t-124 14q-50 0 -130 -13q0 -50 4 -151t4 -152q0 -27 -0.5 -80t-0.5 -79q0 -46 1 -69zM0 -128l2 94q15 4 85 16t106 27q7 12 12.5 27t8.5 33.5t5.5 32.5t3 37.5t0.5 34v35.5v30q0 982 -22 1025q-4 8 -22 14.5t-44.5 11t-49.5 7t-48.5 4.5 t-30.5 3l-4 83q98 2 340 11.5t373 9.5q23 0 68.5 -0.5t67.5 -0.5q70 0 136.5 -13t128.5 -42t108 -71t74 -104.5t28 -137.5q0 -52 -16.5 -95.5t-39 -72t-64.5 -57.5t-73 -45t-84 -40q154 -35 256.5 -134t102.5 -248q0 -100 -35 -179.5t-93.5 -130.5t-138 -85.5t-163.5 -48.5 t-176 -14q-44 0 -132 3t-132 3q-106 0 -307 -11t-231 -12z" /> +<glyph unicode="" horiz-adv-x="1024" d="M0 -126l17 85q6 2 81.5 21.5t111.5 37.5q28 35 41 101q1 7 62 289t114 543.5t52 296.5v25q-24 13 -54.5 18.5t-69.5 8t-58 5.5l19 103q33 -2 120 -6.5t149.5 -7t120.5 -2.5q48 0 98.5 2.5t121 7t98.5 6.5q-5 -39 -19 -89q-30 -10 -101.5 -28.5t-108.5 -33.5 q-8 -19 -14 -42.5t-9 -40t-7.5 -45.5t-6.5 -42q-27 -148 -87.5 -419.5t-77.5 -355.5q-2 -9 -13 -58t-20 -90t-16 -83.5t-6 -57.5l1 -18q17 -4 185 -31q-3 -44 -16 -99q-11 0 -32.5 -1.5t-32.5 -1.5q-29 0 -87 10t-86 10q-138 2 -206 2q-51 0 -143 -9t-121 -11z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1744 128q33 0 42 -18.5t-11 -44.5l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80zM81 1407l54 -27q12 -5 211 -5q44 0 132 2 t132 2q36 0 107.5 -0.5t107.5 -0.5h293q6 0 21 -0.5t20.5 0t16 3t17.5 9t15 17.5l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 48t-14.5 73.5t-7.5 35.5q-6 8 -12 12.5t-15.5 6t-13 2.5t-18 0.5t-16.5 -0.5 q-17 0 -66.5 0.5t-74.5 0.5t-64 -2t-71 -6q-9 -81 -8 -136q0 -94 2 -388t2 -455q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q19 42 19 383q0 101 -3 303t-3 303v117q0 2 0.5 15.5t0.5 25t-1 25.5t-3 24t-5 14q-11 12 -162 12q-33 0 -93 -12t-80 -26q-19 -13 -34 -72.5t-31.5 -111t-42.5 -53.5q-42 26 -56 44v383z" /> +<glyph unicode="" d="M81 1407l54 -27q12 -5 211 -5q44 0 132 2t132 2q70 0 246.5 1t304.5 0.5t247 -4.5q33 -1 56 31l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 47.5t-15 73.5t-7 36q-10 13 -27 19q-5 2 -66 2q-30 0 -93 1t-103 1 t-94 -2t-96 -7q-9 -81 -8 -136l1 -152v52q0 -55 1 -154t1.5 -180t0.5 -153q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q7 16 11.5 74t6 145.5t1.5 155t-0.5 153.5t-0.5 89q0 7 -2.5 21.5t-2.5 22.5q0 7 0.5 44t1 73t0 76.5t-3 67.5t-6.5 32q-11 12 -162 12q-41 0 -163 -13.5t-138 -24.5q-19 -12 -34 -71.5t-31.5 -111.5t-42.5 -54q-42 26 -56 44v383zM1310 125q12 0 42 -19.5t57.5 -41.5 t59.5 -49t36 -30q26 -21 26 -49t-26 -49q-4 -3 -36 -30t-59.5 -49t-57.5 -41.5t-42 -19.5q-13 0 -20.5 10.5t-10 28.5t-2.5 33.5t1.5 33t1.5 19.5h-1024q0 -2 1.5 -19.5t1.5 -33t-2.5 -33.5t-10 -28.5t-20.5 -10.5q-12 0 -42 19.5t-57.5 41.5t-59.5 49t-36 30q-26 21 -26 49 t26 49q4 3 36 30t59.5 49t57.5 41.5t42 19.5q13 0 20.5 -10.5t10 -28.5t2.5 -33.5t-1.5 -33t-1.5 -19.5h1024q0 2 -1.5 19.5t-1.5 33t2.5 33.5t10 28.5t20.5 10.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> @@ -109,8 +110,8 @@ <glyph unicode="" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" /> <glyph unicode="" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" /> <glyph unicode="" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" /> -<glyph unicode="" horiz-adv-x="1152" d="M742 -37l-652 651q-37 37 -37 90.5t37 90.5l652 651q37 37 90.5 37t90.5 -37l75 -75q37 -37 37 -90.5t-37 -90.5l-486 -486l486 -485q37 -38 37 -91t-37 -90l-75 -75q-37 -37 -90.5 -37t-90.5 37z" /> -<glyph unicode="" horiz-adv-x="1152" d="M1099 704q0 -52 -37 -91l-652 -651q-37 -37 -90 -37t-90 37l-76 75q-37 39 -37 91q0 53 37 90l486 486l-486 485q-37 39 -37 91q0 53 37 90l76 75q36 38 90 38t90 -38l652 -651q37 -37 37 -90z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1171 1235l-531 -531l531 -531q19 -19 19 -45t-19 -45l-166 -166q-19 -19 -45 -19t-45 19l-742 742q-19 19 -19 45t19 45l742 742q19 19 45 19t45 -19l166 -166q19 -19 19 -45t-19 -45z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1107 659l-742 -742q-19 -19 -45 -19t-45 19l-166 166q-19 19 -19 45t19 45l531 531l-531 531q-19 19 -19 45t19 45l166 166q19 19 45 19t45 -19l742 -742q19 -19 19 -45t-19 -45z" /> <glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" /> <glyph unicode="" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> @@ -143,17 +144,17 @@ <glyph unicode="" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" /> <glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" /> <glyph unicode="" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1611 320q0 -53 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-486 485l-486 -485q-36 -38 -90 -38t-90 38l-75 75q-38 36 -38 90q0 53 38 91l651 651q37 37 90 37q52 0 91 -37l650 -651q38 -38 38 -91z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1611 832q0 -53 -37 -90l-651 -651q-38 -38 -91 -38q-54 0 -90 38l-651 651q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l486 -486l486 486q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1683 205l-166 -165q-19 -19 -45 -19t-45 19l-531 531l-531 -531q-19 -19 -45 -19t-45 19l-166 165q-19 19 -19 45.5t19 45.5l742 741q19 19 45 19t45 -19l742 -741q19 -19 19 -45.5t-19 -45.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1683 728l-742 -741q-19 -19 -45 -19t-45 19l-742 741q-19 19 -19 45.5t19 45.5l166 165q19 19 45 19t45 -19l531 -531l531 531q19 19 45 19t45 -19l166 -165q19 -19 19 -45.5t-19 -45.5z" /> <glyph unicode="" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " /> <glyph unicode="" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" /> <glyph unicode="" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" /> <glyph unicode="" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" /> <glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" /> -<glyph unicode="" horiz-adv-x="1920" d="M512 512v-384h-256v384h256zM896 1024v-896h-256v896h256zM1280 768v-640h-256v640h256zM1664 1152v-1024h-256v1024h256zM1792 32v1216q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5z M1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" /> +<glyph unicode="" horiz-adv-x="2048" d="M640 640v-512h-256v512h256zM1024 1152v-1024h-256v1024h256zM2048 0v-128h-2048v1536h128v-1408h1920zM1408 896v-768h-256v768h256zM1792 1280v-1152h-256v1152h256z" /> <glyph unicode="" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1307 618l23 219h-198v109q0 49 15.5 68.5t71.5 19.5h110v219h-175q-152 0 -218 -72t-66 -213v-131h-131v-219h131v-635h262v635h175zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1536 160q0 -119 -84.5 -203.5t-203.5 -84.5h-192v608h203l30 224h-233v143q0 54 28 83t96 29l132 1v207q-96 9 -180 9q-136 0 -218 -80.5t-82 -225.5v-166h-224v-224h224v-608h-544q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5v-960z" /> <glyph unicode="" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" /> <glyph unicode="" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" /> @@ -176,14 +177,14 @@ <glyph unicode="" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" /> <glyph unicode="" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" /> -<glyph unicode="" horiz-adv-x="768" d="M511 980h257l-30 -284h-227v-824h-341v824h-170v284h170v171q0 182 86 275.5t283 93.5h227v-284h-142q-39 0 -62.5 -6.5t-34 -23.5t-13.5 -34.5t-3 -49.5v-142z" /> +<glyph unicode="" horiz-adv-x="1024" d="M959 1524v-264h-157q-86 0 -116 -36t-30 -108v-189h293l-39 -296h-254v-759h-306v759h-255v296h255v218q0 186 104 288.5t277 102.5q147 0 228 -12z" /> <glyph unicode="" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" /> <glyph unicode="" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" /> <glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" /> <glyph unicode="" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" /> <glyph unicode="" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" /> -<glyph unicode="" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM183 128h1298q-164 181 -246.5 411.5t-82.5 484.5q0 256 -320 256t-320 -256q0 -254 -82.5 -484.5t-246.5 -411.5zM1664 128q0 -52 -38 -90t-90 -38 h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM246 128h1300q-266 300 -266 832q0 51 -24 105t-69 103t-121.5 80.5t-169.5 31.5t-169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -532 -266 -832z M1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5 t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" /> <glyph unicode="" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" /> <glyph unicode="" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" /> <glyph unicode="" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" /> @@ -218,8 +219,8 @@ <glyph unicode="" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" /> <glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" /> -<glyph unicode="" d="M678 -57q0 -38 -10 -71h-380q-95 0 -171.5 56.5t-103.5 147.5q24 45 69 77.5t100 49.5t107 24t107 7q32 0 49 -2q6 -4 30.5 -21t33 -23t31 -23t32 -25.5t27.5 -25.5t26.5 -29.5t21 -30.5t17.5 -34.5t9.5 -36t4.5 -40.5zM385 294q-234 -7 -385 -85v433q103 -118 273 -118 q32 0 70 5q-21 -61 -21 -86q0 -67 63 -149zM558 805q0 -100 -43.5 -160.5t-140.5 -60.5q-51 0 -97 26t-78 67.5t-56 93.5t-35.5 104t-11.5 99q0 96 51.5 165t144.5 69q66 0 119 -41t84 -104t47 -130t16 -128zM1536 896v-736q0 -119 -84.5 -203.5t-203.5 -84.5h-468 q39 73 39 157q0 66 -22 122.5t-55.5 93t-72 71t-72 59.5t-55.5 54.5t-22 59.5q0 36 23 68t56 61.5t65.5 64.5t55.5 93t23 131t-26.5 145.5t-75.5 118.5q-6 6 -14 11t-12.5 7.5t-10 9.5t-10.5 17h135l135 64h-437q-138 0 -244.5 -38.5t-182.5 -133.5q0 126 81 213t207 87h960 q119 0 203.5 -84.5t84.5 -203.5v-96h-256v256h-128v-256h-256v-128h256v-256h128v256h256z" /> -<glyph unicode="" horiz-adv-x="1664" d="M876 71q0 21 -4.5 40.5t-9.5 36t-17.5 34.5t-21 30.5t-26.5 29.5t-27.5 25.5t-32 25.5t-31 23t-33 23t-30.5 21q-17 2 -50 2q-54 0 -106 -7t-108 -25t-98 -46t-69 -75t-27 -107q0 -68 35.5 -121.5t93 -84t120.5 -45.5t127 -15q59 0 112.5 12.5t100.5 39t74.5 73.5 t27.5 110zM756 933q0 60 -16.5 127.5t-47 130.5t-84 104t-119.5 41q-93 0 -144 -69t-51 -165q0 -47 11.5 -99t35.5 -104t56 -93.5t78 -67.5t97 -26q97 0 140.5 60.5t43.5 160.5zM625 1408h437l-135 -79h-135q71 -45 110 -126t39 -169q0 -74 -23 -131.5t-56 -92.5t-66 -64.5 t-56 -61t-23 -67.5q0 -26 16.5 -51t43 -48t58.5 -48t64 -55.5t58.5 -66t43 -85t16.5 -106.5q0 -160 -140 -282q-152 -131 -420 -131q-59 0 -119.5 10t-122 33.5t-108.5 58t-77 89t-30 121.5q0 61 37 135q32 64 96 110.5t145 71t155 36t150 13.5q-64 83 -64 149q0 12 2 23.5 t5 19.5t8 21.5t7 21.5q-40 -5 -70 -5q-149 0 -255.5 98t-106.5 246q0 140 95 250.5t234 141.5q94 20 187 20zM1664 1152v-128h-256v-256h-128v256h-256v128h256v256h128v-256h256z" /> +<glyph unicode="" d="M829 318q0 -76 -58.5 -112.5t-139.5 -36.5q-41 0 -80.5 9.5t-75.5 28.5t-58 53t-22 78q0 46 25 80t65.5 51.5t82 25t84.5 7.5q20 0 31 -2q2 -1 23 -16.5t26 -19t23 -18t24.5 -22t19 -22.5t17 -26t9 -26.5t4.5 -31.5zM755 863q0 -60 -33 -99.5t-92 -39.5q-53 0 -93 42.5 t-57.5 96.5t-17.5 106q0 61 32 104t92 43q53 0 93.5 -45t58 -101t17.5 -107zM861 1120l88 64h-265q-85 0 -161 -32t-127.5 -98t-51.5 -153q0 -93 64.5 -154.5t158.5 -61.5q22 0 43 3q-13 -29 -13 -54q0 -44 40 -94q-175 -12 -257 -63q-47 -29 -75.5 -73t-28.5 -95 q0 -43 18.5 -77.5t48.5 -56.5t69 -37t77.5 -21t76.5 -6q60 0 120.5 15.5t113.5 46t86 82.5t33 117q0 49 -20 89.5t-49 66.5t-58 47.5t-49 44t-20 44.5t15.5 42.5t37.5 39.5t44 42t37.5 59.5t15.5 82.5q0 60 -22.5 99.5t-72.5 90.5h83zM1152 672h128v64h-128v128h-64v-128 h-128v-64h128v-160h64v160zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M735 740q0 -36 32 -70.5t77.5 -68t90.5 -73.5t77 -104t32 -142q0 -90 -48 -173q-72 -122 -211 -179.5t-298 -57.5q-132 0 -246.5 41.5t-171.5 137.5q-37 60 -37 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 42 -47.5 74t-15.5 73q0 36 21 85q-46 -4 -68 -4 q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q77 66 182.5 98t217.5 32h418l-138 -88h-131q74 -63 112 -133t38 -160q0 -72 -24.5 -129.5t-59 -93t-69.5 -65t-59.5 -61.5t-24.5 -66zM589 836q38 0 78 16.5t66 43.5q53 57 53 159q0 58 -17 125t-48.5 129.5 t-84.5 103.5t-117 41q-42 0 -82.5 -19.5t-65.5 -52.5q-47 -59 -47 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26zM591 -37q58 0 111.5 13t99 39t73 73t27.5 109q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -48 2 q-53 0 -105 -7t-107.5 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -70 35 -123.5t91.5 -83t119 -44t127.5 -14.5zM1401 839h213v-108h-213v-219h-105v219h-212v108h212v217h105v-217z" /> <glyph unicode="" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" /> @@ -247,10 +248,10 @@ <glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" /> <glyph unicode="" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" /> <glyph unicode="" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" /> -<glyph unicode="" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1664 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5 q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5 t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" /> <glyph unicode="" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" /> <glyph unicode="" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1024 352v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM1024 608v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280z M768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M384 736q0 14 9 23t23 9h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64zM1120 512q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704zM1120 256q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704 q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704z" /> <glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> @@ -345,8 +346,8 @@ <glyph unicode="" horiz-adv-x="1280" d="M1043 971q0 100 -65 162t-171 62h-320v-448h320q106 0 171 62t65 162zM1280 971q0 -193 -126.5 -315t-326.5 -122h-340v-118h505q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-505v-192q0 -14 -9.5 -23t-22.5 -9h-167q-14 0 -23 9t-9 23v192h-224q-14 0 -23 9t-9 23v128 q0 14 9 23t23 9h224v118h-224q-14 0 -23 9t-9 23v149q0 13 9 22.5t23 9.5h224v629q0 14 9 23t23 9h539q200 0 326.5 -122t126.5 -315z" /> <glyph unicode="" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" /> <glyph unicode="" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1024 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1024 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28 t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" /> +<glyph unicode="" d="M1024 1024v472q22 -14 36 -28l408 -408q14 -14 28 -36h-472zM896 992q0 -40 28 -68t68 -28h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544z" /> +<glyph unicode="" d="M1468 1060q14 -14 28 -36h-472v472q22 -14 36 -28zM992 896h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544q0 -40 28 -68t68 -28zM1152 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23z" /> <glyph unicode="" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" /> <glyph unicode="" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" /> <glyph unicode="" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" /> @@ -367,8 +368,8 @@ <glyph unicode="" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" /> <glyph unicode="" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1024" d="M390 1408h219v-388h364v-241h-364v-394q0 -136 14 -172q13 -37 52 -60q50 -31 117 -31q117 0 232 76v-242q-102 -48 -178 -65q-77 -19 -173 -19q-105 0 -186 27q-78 25 -138 75q-58 51 -79 105q-22 54 -22 161v539h-170v217q91 30 155 84q64 55 103 132q39 78 54 196z " /> -<glyph unicode="" d="M1123 127v181q-88 -56 -174 -56q-51 0 -88 23q-29 17 -39 45q-11 30 -11 129v295h274v181h-274v291h-164q-11 -90 -40 -147t-78 -99q-48 -40 -116 -63v-163h127v-404q0 -78 17 -121q17 -42 59 -78q43 -37 104 -57q62 -20 140 -20q67 0 129 14q57 13 134 49zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M944 207l80 -237q-23 -35 -111 -66t-177 -32q-104 -2 -190.5 26t-142.5 74t-95 106t-55.5 120t-16.5 118v544h-168v215q72 26 129 69.5t91 90t58 102t34 99t15 88.5q1 5 4.5 8.5t7.5 3.5h244v-424h333v-252h-334v-518q0 -30 6.5 -56t22.5 -52.5t49.5 -41.5t81.5 -14 q78 2 134 29z" /> +<glyph unicode="" d="M1136 75l-62 183q-44 -22 -103 -22q-36 -1 -62 10.5t-38.5 31.5t-17.5 40.5t-5 43.5v398h257v194h-256v326h-188q-8 0 -9 -10q-5 -44 -17.5 -87t-39 -95t-77 -95t-118.5 -68v-165h130v-418q0 -57 21.5 -115t65 -111t121 -85.5t176.5 -30.5q69 1 136.5 25t85.5 50z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" /> <glyph unicode="" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" /> <glyph unicode="" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" /> @@ -379,7 +380,7 @@ <glyph unicode="" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31 -29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 10.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" /> <glyph unicode="" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1483 512l-587 -587q-52 -53 -127.5 -53t-128.5 53l-587 587q-53 53 -53 128t53 128l587 587q53 53 128 53t128 -53l265 -265l-398 -399l-188 188q-42 42 -99 42q-59 0 -100 -41l-120 -121q-42 -40 -42 -99q0 -58 42 -100l406 -408q30 -28 67 -37l6 -4h28q60 0 99 41 l619 619l2 -3q53 -53 53 -128t-53 -128zM1406 1138l120 -120q14 -15 14 -36t-14 -36l-730 -730q-17 -15 -37 -15v0q-4 0 -6 1q-18 2 -30 14l-407 408q-14 15 -14 36t14 35l121 120q13 15 35 15t36 -15l252 -252l574 575q15 15 36 15t36 -15z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1000 1102l37 194q5 23 -9 40t-35 17h-712q-23 0 -38.5 -17t-15.5 -37v-1101q0 -7 6 -1l291 352q23 26 38 33.5t48 7.5h239q22 0 37 14.5t18 29.5q24 130 37 191q4 21 -11.5 40t-36.5 19h-294q-29 0 -48 19t-19 48v42q0 29 19 47.5t48 18.5h346q18 0 35 13.5t20 29.5z M1227 1324q-15 -73 -53.5 -266.5t-69.5 -350t-35 -173.5q-6 -22 -9 -32.5t-14 -32.5t-24.5 -33t-38.5 -21t-58 -10h-271q-13 0 -22 -10q-8 -9 -426 -494q-22 -25 -58.5 -28.5t-48.5 5.5q-55 22 -55 98v1410q0 55 38 102.5t120 47.5h888q95 0 127 -53t10 -159zM1227 1324 l-158 -790q4 17 35 173.5t69.5 350t53.5 266.5z" /> <glyph unicode="" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> <glyph unicode="" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> @@ -401,14 +402,119 @@ <glyph unicode="" d="M1254 899q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69 t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237 q104 139 172.5 292.5t82.5 226.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" /> <glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="2176" d="M620 416q-110 -64 -268 -64h-128v64h-64q-13 0 -22.5 23.5t-9.5 56.5q0 24 7 49q-58 2 -96.5 10.5t-38.5 20.5t38.5 20.5t96.5 10.5q-7 25 -7 49q0 33 9.5 56.5t22.5 23.5h64v64h128q158 0 268 -64h1113q42 -7 106.5 -18t80.5 -14q89 -15 150 -40.5t83.5 -47.5t22.5 -40 t-22.5 -40t-83.5 -47.5t-150 -40.5q-16 -3 -80.5 -14t-106.5 -18h-1113zM1739 668q53 -36 53 -92t-53 -92l81 -30q68 48 68 122t-68 122zM625 400h1015q-217 -38 -456 -80q-57 0 -113 -24t-83 -48l-28 -24l-288 -288q-26 -26 -70.5 -45t-89.5 -19h-96l-93 464h29 q157 0 273 64zM352 816h-29l93 464h96q46 0 90 -19t70 -45l288 -288q4 -4 11 -10.5t30.5 -23t48.5 -29t61.5 -23t72.5 -10.5l456 -80h-1015q-116 64 -273 64z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1519 760q62 0 103.5 -40.5t41.5 -101.5q0 -97 -93 -130l-172 -59l56 -167q7 -21 7 -47q0 -59 -42 -102t-101 -43q-47 0 -85.5 27t-53.5 72l-55 165l-310 -106l55 -164q8 -24 8 -47q0 -59 -42 -102t-102 -43q-47 0 -85 27t-53 72l-55 163l-153 -53q-29 -9 -50 -9 q-61 0 -101.5 40t-40.5 101q0 47 27.5 85t71.5 53l156 53l-105 313l-156 -54q-26 -8 -48 -8q-60 0 -101 40.5t-41 100.5q0 47 27.5 85t71.5 53l157 53l-53 159q-8 24 -8 47q0 60 42 102.5t102 42.5q47 0 85 -27t53 -72l54 -160l310 105l-54 160q-8 24 -8 47q0 59 42.5 102 t101.5 43q47 0 85.5 -27.5t53.5 -71.5l53 -161l162 55q21 6 43 6q60 0 102.5 -39.5t42.5 -98.5q0 -45 -30 -81.5t-74 -51.5l-157 -54l105 -316l164 56q24 8 46 8zM725 498l310 105l-105 315l-310 -107z" /> +<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM1280 352v436q-31 -35 -64 -55q-34 -22 -132.5 -85t-151.5 -99q-98 -69 -164 -69v0v0q-66 0 -164 69 q-46 32 -141.5 92.5t-142.5 92.5q-12 8 -33 27t-31 27v-436q0 -40 28 -68t68 -28h832q40 0 68 28t28 68zM1280 925q0 41 -27.5 70t-68.5 29h-832q-40 0 -68 -28t-28 -68q0 -37 30.5 -76.5t67.5 -64.5q47 -32 137.5 -89t129.5 -83q3 -2 17 -11.5t21 -14t21 -13t23.5 -13 t21.5 -9.5t22.5 -7.5t20.5 -2.5t20.5 2.5t22.5 7.5t21.5 9.5t23.5 13t21 13t21 14t17 11.5l267 174q35 23 66.5 62.5t31.5 73.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M127 640q0 163 67 313l367 -1005q-196 95 -315 281t-119 411zM1415 679q0 -19 -2.5 -38.5t-10 -49.5t-11.5 -44t-17.5 -59t-17.5 -58l-76 -256l-278 826q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-75 1 -202 10q-12 1 -20.5 -5t-11.5 -15t-1.5 -18.5t9 -16.5 t19.5 -8l80 -8l120 -328l-168 -504l-280 832q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-7 0 -23 0.5t-26 0.5q105 160 274.5 253.5t367.5 93.5q147 0 280.5 -53t238.5 -149h-10q-55 0 -92 -40.5t-37 -95.5q0 -12 2 -24t4 -21.5t8 -23t9 -21t12 -22.5t12.5 -21 t14.5 -24t14 -23q63 -107 63 -212zM909 573l237 -647q1 -6 5 -11q-126 -44 -255 -44q-112 0 -217 32zM1570 1009q95 -174 95 -369q0 -209 -104 -385.5t-279 -278.5l235 678q59 169 59 276q0 42 -6 79zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286 t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 -215q173 0 331.5 68t273 182.5t182.5 273t68 331.5t-68 331.5t-182.5 273t-273 182.5t-331.5 68t-331.5 -68t-273 -182.5t-182.5 -273t-68 -331.5t68 -331.5t182.5 -273 t273 -182.5t331.5 -68z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1086 1536v-1536l-272 -128q-228 20 -414 102t-293 208.5t-107 272.5q0 140 100.5 263.5t275 205.5t391.5 108v-172q-217 -38 -356.5 -150t-139.5 -255q0 -152 154.5 -267t388.5 -145v1360zM1755 954l37 -390l-525 114l147 83q-119 70 -280 99v172q277 -33 481 -157z" /> +<glyph unicode="" horiz-adv-x="2048" d="M960 1536l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128v128zM256 896h256v-768h128v768h256v-768h128v768h256v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664v64q0 26 20.5 45t48.5 19h59v768zM1851 -64 q28 0 48.5 -19t20.5 -45v-128h-1920v128q0 26 20.5 45t48.5 19h1782z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" /> +<glyph unicode="" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" /> +<glyph unicode="" horiz-adv-x="1280" d="M981 197q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13t99 39t73 73t27.5 109zM864 1055 q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5q53 56 53 159zM752 1536h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5 t-59.5 -93t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5 t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32z" /> +<glyph unicode="" horiz-adv-x="1984" d="M831 572q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96q0 57 41.5 98t97.5 41t96.5 -41t40.5 -98zM1292 711q56 0 96.5 -41t40.5 -98q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96q0 57 41.5 98t97.5 41zM1984 722q0 -62 -31 -114t-83 -82q5 -33 5 -61 q0 -121 -68.5 -230.5t-197.5 -193.5q-125 -82 -285.5 -125.5t-335.5 -43.5q-176 0 -336.5 43.5t-284.5 125.5q-129 84 -197.5 193t-68.5 231q0 29 5 66q-48 31 -77 81.5t-29 109.5q0 94 66 160t160 66q83 0 148 -55q248 158 592 164l134 423q4 14 17.5 21.5t28.5 4.5 l347 -82q22 50 68.5 81t102.5 31q77 0 131.5 -54.5t54.5 -131.5t-54.5 -132t-131.5 -55q-76 0 -130.5 54t-55.5 131l-315 74l-116 -366q327 -14 560 -166q64 58 151 58q94 0 160 -66t66 -160zM1664 1459q-45 0 -77 -32t-32 -77t32 -77t77 -32t77 32t32 77t-32 77t-77 32z M77 722q0 -67 51 -111q49 131 180 235q-36 25 -82 25q-62 0 -105.5 -43.5t-43.5 -105.5zM1567 105q112 73 171.5 166t59.5 194t-59.5 193.5t-171.5 165.5q-116 75 -265.5 115.5t-313.5 40.5t-313.5 -40.5t-265.5 -115.5q-112 -73 -171.5 -165.5t-59.5 -193.5t59.5 -194 t171.5 -166q116 -75 265.5 -115.5t313.5 -40.5t313.5 40.5t265.5 115.5zM1850 605q57 46 57 117q0 62 -43.5 105.5t-105.5 43.5q-49 0 -86 -28q131 -105 178 -238zM1258 237q11 11 27 11t27 -11t11 -27.5t-11 -27.5q-99 -99 -319 -99h-2q-220 0 -319 99q-11 11 -11 27.5 t11 27.5t27 11t27 -11q77 -77 265 -77h2q188 0 265 77z" /> +<glyph unicode="" d="M950 393q7 7 17.5 7t17.5 -7t7 -18t-7 -18q-65 -64 -208 -64h-1h-1q-143 0 -207 64q-8 7 -8 18t8 18q7 7 17.5 7t17.5 -7q49 -51 172 -51h1h1q122 0 173 51zM671 613q0 -37 -26 -64t-63 -27t-63 27t-26 64t26 63t63 26t63 -26t26 -63zM1214 1049q-29 0 -50 21t-21 50 q0 30 21 51t50 21q30 0 51 -21t21 -51q0 -29 -21 -50t-51 -21zM1216 1408q132 0 226 -94t94 -227v-894q0 -133 -94 -227t-226 -94h-896q-132 0 -226 94t-94 227v894q0 133 94 227t226 94h896zM1321 596q35 14 57 45.5t22 70.5q0 51 -36 87.5t-87 36.5q-60 0 -98 -48 q-151 107 -375 115l83 265l206 -49q1 -50 36.5 -85t84.5 -35q50 0 86 35.5t36 85.5t-36 86t-86 36q-36 0 -66 -20.5t-45 -53.5l-227 54q-9 2 -17.5 -2.5t-11.5 -14.5l-95 -302q-224 -4 -381 -113q-36 43 -93 43q-51 0 -87 -36.5t-36 -87.5q0 -37 19.5 -67.5t52.5 -45.5 q-7 -25 -7 -54q0 -98 74 -181.5t201.5 -132t278.5 -48.5q150 0 277.5 48.5t201.5 132t74 181.5q0 27 -6 54zM971 702q37 0 63 -26t26 -63t-26 -64t-63 -27t-63 27t-26 64t26 63t63 26z" /> +<glyph unicode="" d="M866 697l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14q19 0 32.5 -14t13.5 -33v-54zM1199 502v122h-150 v-126q0 -20 -13.5 -33.5t-33.5 -13.5q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123q0 -80 58 -137t139 -57t138.5 57t57.5 139zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103 t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1062 824v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58zM1592 602h328 v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275z" /> +<glyph unicode="" d="M1472 160v480h-704v704h-480q-93 0 -158.5 -65.5t-65.5 -158.5v-480h704v-704h480q93 0 158.5 65.5t65.5 158.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M328 1254h204v-983h-532v697h328v286zM328 435v369h-123v-369h123zM614 968v-697h205v697h-205zM614 1254v-204h205v204h-205zM901 968h533v-942h-533v163h328v82h-328v697zM1229 435v369h-123v-369h123zM1516 968h532v-942h-532v163h327v82h-327v697zM1843 435v369h-123 v-369h123z" /> +<glyph unicode="" d="M1046 516q0 -64 -38 -109t-91 -45q-43 0 -70 15v277q28 17 70 17q53 0 91 -45.5t38 -109.5zM703 944q0 -64 -38 -109.5t-91 -45.5q-43 0 -70 15v277q28 17 70 17q53 0 91 -45t38 -109zM1265 513q0 134 -88 229t-213 95q-20 0 -39 -3q-23 -78 -78 -136q-87 -95 -211 -101 v-636l211 41v206q51 -19 117 -19q125 0 213 95t88 229zM922 940q0 134 -88.5 229t-213.5 95q-74 0 -141 -36h-186v-840l211 41v206q55 -19 116 -19q125 0 213.5 95t88.5 229zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="2038" d="M1222 607q75 3 143.5 -20.5t118 -58.5t101 -94.5t84 -108t75.5 -120.5q33 -56 78.5 -109t75.5 -80.5t99 -88.5q-48 -30 -108.5 -57.5t-138.5 -59t-114 -47.5q-44 37 -74 115t-43.5 164.5t-33 180.5t-42.5 168.5t-72.5 123t-122.5 48.5l-10 -2l-6 -4q4 -5 13 -14 q6 -5 28 -23.5t25.5 -22t19 -18t18 -20.5t11.5 -21t10.5 -27.5t4.5 -31t4 -40.5l1 -33q1 -26 -2.5 -57.5t-7.5 -52t-12.5 -58.5t-11.5 -53q-35 1 -101 -9.5t-98 -10.5q-39 0 -72 10q-2 16 -2 47q0 74 3 96q2 13 31.5 41.5t57 59t26.5 51.5q-24 2 -43 -24 q-36 -53 -111.5 -99.5t-136.5 -46.5q-25 0 -75.5 63t-106.5 139.5t-84 96.5q-6 4 -27 30q-482 -112 -513 -112q-16 0 -28 11t-12 27q0 15 8.5 26.5t22.5 14.5l486 106q-8 14 -8 25t5.5 17.5t16 11.5t20 7t23 4.5t18.5 4.5q4 1 15.5 7.5t17.5 6.5q15 0 28 -16t20 -33 q163 37 172 37q17 0 29.5 -11t12.5 -28q0 -15 -8.5 -26t-23.5 -14l-182 -40l-1 -16q-1 -26 81.5 -117.5t104.5 -91.5q47 0 119 80t72 129q0 36 -23.5 53t-51 18.5t-51 11.5t-23.5 34q0 16 10 34l-68 19q43 44 43 117q0 26 -5 58q82 16 144 16q44 0 71.5 -1.5t48.5 -8.5 t31 -13.5t20.5 -24.5t15.5 -33.5t17 -47.5t24 -60l50 25q-3 -40 -23 -60t-42.5 -21t-40 -6.5t-16.5 -20.5zM1282 842q-5 5 -13.5 15.5t-12 14.5t-10.5 11.5t-10 10.5l-8 8t-8.5 7.5t-8 5t-8.5 4.5q-7 3 -14.5 5t-20.5 2.5t-22 0.5h-32.5h-37.5q-126 0 -217 -43 q16 30 36 46.5t54 29.5t65.5 36t46 36.5t50 55t43.5 50.5q12 -9 28 -31.5t32 -36.5t38 -13l12 1v-76l22 -1q247 95 371 190q28 21 50 39t42.5 37.5t33 31t29.5 34t24 31t24.5 37t23 38t27 47.5t29.5 53l7 9q-2 -53 -43 -139q-79 -165 -205 -264t-306 -142q-14 -3 -42 -7.5 t-50 -9.5t-39 -14q3 -19 24.5 -46t21.5 -34q0 -11 -26 -30zM1061 -79q39 26 131.5 47.5t146.5 21.5q9 0 22.5 -15.5t28 -42.5t26 -50t24 -51t14.5 -33q-121 -45 -244 -45q-61 0 -125 11zM822 568l48 12l109 -177l-73 -48zM1323 51q3 -15 3 -16q0 -7 -17.5 -14.5t-46 -13 t-54 -9.5t-53.5 -7.5t-32 -4.5l-7 43q21 2 60.5 8.5t72 10t60.5 3.5h14zM866 679l-96 -20l-6 17q10 1 32.5 7t34.5 6q19 0 35 -10zM1061 45h31l10 -83l-41 -12v95zM1950 1535v1v-1zM1950 1535l-1 -5l-2 -2l1 3zM1950 1535l1 1z" /> +<glyph unicode="" d="M1167 -50q-5 19 -24 5q-30 -22 -87 -39t-131 -17q-129 0 -193 49q-5 4 -13 4q-11 0 -26 -12q-7 -6 -7.5 -16t7.5 -20q34 -32 87.5 -46t102.5 -12.5t99 4.5q41 4 84.5 20.5t65 30t28.5 20.5q12 12 7 29zM1128 65q-19 47 -39 61q-23 15 -76 15q-47 0 -71 -10 q-29 -12 -78 -56q-26 -24 -12 -44q9 -8 17.5 -4.5t31.5 23.5q3 2 10.5 8.5t10.5 8.5t10 7t11.5 7t12.5 5t15 4.5t16.5 2.5t20.5 1q27 0 44.5 -7.5t23 -14.5t13.5 -22q10 -17 12.5 -20t12.5 1q23 12 14 34zM1483 346q0 22 -5 44.5t-16.5 45t-34 36.5t-52.5 14 q-33 0 -97 -41.5t-129 -83.5t-101 -42q-27 -1 -63.5 19t-76 49t-83.5 58t-100 49t-111 19q-115 -1 -197 -78.5t-84 -178.5q-2 -112 74 -164q29 -20 62.5 -28.5t103.5 -8.5q57 0 132 32.5t134 71t120 70.5t93 31q26 -1 65 -31.5t71.5 -67t68 -67.5t55.5 -32q35 -3 58.5 14 t55.5 63q28 41 42.5 101t14.5 106zM1536 506q0 -164 -62 -304.5t-166 -236t-242.5 -149.5t-290.5 -54t-293 57.5t-247.5 157t-170.5 241.5t-64 302q0 89 19.5 172.5t49 145.5t70.5 118.5t78.5 94t78.5 69.5t64.5 46.5t42.5 24.5q14 8 51 26.5t54.5 28.5t48 30t60.5 44 q36 28 58 72.5t30 125.5q129 -155 186 -193q44 -29 130 -68t129 -66q21 -13 39 -25t60.5 -46.5t76 -70.5t75 -95t69 -122t47 -148.5t19.5 -177.5z" /> +<glyph unicode="" d="M1070 463l-160 -160l-151 -152l-30 -30q-65 -64 -151.5 -87t-171.5 -2q-16 -70 -72 -115t-129 -45q-85 0 -145 60.5t-60 145.5q0 72 44.5 128t113.5 72q-22 86 1 173t88 152l12 12l151 -152l-11 -11q-37 -37 -37 -89t37 -90q37 -37 89 -37t89 37l30 30l151 152l161 160z M729 1145l12 -12l-152 -152l-12 12q-37 37 -89 37t-89 -37t-37 -89.5t37 -89.5l29 -29l152 -152l160 -160l-151 -152l-161 160l-151 152l-30 30q-68 67 -90 159.5t5 179.5q-70 15 -115 71t-45 129q0 85 60 145.5t145 60.5q76 0 133.5 -49t69.5 -123q84 20 169.5 -3.5 t149.5 -87.5zM1536 78q0 -85 -60 -145.5t-145 -60.5q-74 0 -131 47t-71 118q-86 -28 -179.5 -6t-161.5 90l-11 12l151 152l12 -12q37 -37 89 -37t89 37t37 89t-37 89l-30 30l-152 152l-160 160l152 152l160 -160l152 -152l29 -30q64 -64 87.5 -150.5t2.5 -171.5 q76 -11 126.5 -68.5t50.5 -134.5zM1534 1202q0 -77 -51 -135t-127 -69q26 -85 3 -176.5t-90 -158.5l-12 -12l-151 152l12 12q37 37 37 89t-37 89t-89 37t-89 -37l-30 -30l-152 -152l-160 -160l-152 152l161 160l152 152l29 30q67 67 159 89.5t178 -3.5q11 75 68.5 126 t135.5 51q85 0 145 -60.5t60 -145.5z" /> +<glyph unicode="" d="M654 458q-1 -3 -12.5 0.5t-31.5 11.5l-20 9q-44 20 -87 49q-7 5 -41 31.5t-38 28.5q-67 -103 -134 -181q-81 -95 -105 -110q-4 -2 -19.5 -4t-18.5 0q6 4 82 92q21 24 85.5 115t78.5 118q17 30 51 98.5t36 77.5q-8 1 -110 -33q-8 -2 -27.5 -7.5t-34.5 -9.5t-17 -5 q-2 -2 -2 -10.5t-1 -9.5q-5 -10 -31 -15q-23 -7 -47 0q-18 4 -28 21q-4 6 -5 23q6 2 24.5 5t29.5 6q58 16 105 32q100 35 102 35q10 2 43 19.5t44 21.5q9 3 21.5 8t14.5 5.5t6 -0.5q2 -12 -1 -33q0 -2 -12.5 -27t-26.5 -53.5t-17 -33.5q-25 -50 -77 -131l64 -28 q12 -6 74.5 -32t67.5 -28q4 -1 10.5 -25.5t4.5 -30.5zM449 944q3 -15 -4 -28q-12 -23 -50 -38q-30 -12 -60 -12q-26 3 -49 26q-14 15 -18 41l1 3q3 -3 19.5 -5t26.5 0t58 16q36 12 55 14q17 0 21 -17zM1147 815l63 -227l-139 42zM39 15l694 232v1032l-694 -233v-1031z M1280 332l102 -31l-181 657l-100 31l-216 -536l102 -31l45 110l211 -65zM777 1294l573 -184v380zM1088 -29l158 -13l-54 -160l-40 66q-130 -83 -276 -108q-58 -12 -91 -12h-84q-79 0 -199.5 39t-183.5 85q-8 7 -8 16q0 8 5 13.5t13 5.5q4 0 18 -7.5t30.5 -16.5t20.5 -11 q73 -37 159.5 -61.5t157.5 -24.5q95 0 167 14.5t157 50.5q15 7 30.5 15.5t34 19t28.5 16.5zM1536 1050v-1079l-774 246q-14 -6 -375 -127.5t-368 -121.5q-13 0 -18 13q0 1 -1 3v1078q3 9 4 10q5 6 20 11q106 35 149 50v384l558 -198q2 0 160.5 55t316 108.5t161.5 53.5 q20 0 20 -21v-418z" /> +<glyph unicode="" horiz-adv-x="1792" d="M288 1152q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-128q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h128zM1664 989q58 -34 93 -93t35 -128v-768q0 -106 -75 -181t-181 -75h-864q-66 0 -113 47t-47 113v1536q0 40 28 68t68 28h672q40 0 88 -20t76 -48 l152 -152q28 -28 48 -76t20 -88v-163zM928 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 512v128q0 14 -9 23 t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128 q14 0 23 9t9 23zM1184 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 256v128q0 14 -9 23t-23 9h-128 q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1536 896v256h-160q-40 0 -68 28t-28 68v160h-640v-512h896z" /> +<glyph unicode="" d="M1344 1536q26 0 45 -19t19 -45v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280zM512 1248v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 992v-64q0 -14 9 -23t23 -9h64q14 0 23 9 t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 736v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 480v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 160v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM384 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 -96v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9 t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM896 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 928v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 160v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9 t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1188 988l-292 -292v-824q0 -46 -33 -79t-79 -33t-79 33t-33 79v384h-64v-384q0 -46 -33 -79t-79 -33t-79 33t-33 79v824l-292 292q-28 28 -28 68t28 68t68 28t68 -28l228 -228h368l228 228q28 28 68 28t68 -28t28 -68t-28 -68zM864 1152q0 -93 -65.5 -158.5 t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M780 1064q0 -60 -19 -113.5t-63 -92.5t-105 -39q-76 0 -138 57.5t-92 135.5t-30 151q0 60 19 113.5t63 92.5t105 39q77 0 138.5 -57.5t91.5 -135t30 -151.5zM438 581q0 -80 -42 -139t-119 -59q-76 0 -141.5 55.5t-100.5 133.5t-35 152q0 80 42 139.5t119 59.5 q76 0 141.5 -55.5t100.5 -134t35 -152.5zM832 608q118 0 255 -97.5t229 -237t92 -254.5q0 -46 -17 -76.5t-48.5 -45t-64.5 -20t-76 -5.5q-68 0 -187.5 45t-182.5 45q-66 0 -192.5 -44.5t-200.5 -44.5q-183 0 -183 146q0 86 56 191.5t139.5 192.5t187.5 146t193 59zM1071 819 q-61 0 -105 39t-63 92.5t-19 113.5q0 74 30 151.5t91.5 135t138.5 57.5q61 0 105 -39t63 -92.5t19 -113.5q0 -73 -30 -151t-92 -135.5t-138 -57.5zM1503 923q77 0 119 -59.5t42 -139.5q0 -74 -35 -152t-100.5 -133.5t-141.5 -55.5q-77 0 -119 59t-42 139q0 74 35 152.5 t100.5 134t141.5 55.5z" /> +<glyph unicode="" horiz-adv-x="768" d="M704 1008q0 -145 -57 -243.5t-152 -135.5l45 -821q2 -26 -16 -45t-44 -19h-192q-26 0 -44 19t-16 45l45 821q-95 37 -152 135.5t-57 243.5q0 128 42.5 249.5t117.5 200t160 78.5t160 -78.5t117.5 -200t42.5 -249.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 -93l640 349v636l-640 -233v-752zM832 772l698 254l-698 254l-698 -254zM1664 1024v-768q0 -35 -18 -65t-49 -47l-704 -384q-28 -16 -61 -16t-61 16l-704 384q-31 17 -49 47t-18 65v768q0 40 23 73t61 47l704 256q22 8 44 8t44 -8l704 -256q38 -14 61 -47t23 -73z " /> +<glyph unicode="" horiz-adv-x="2304" d="M640 -96l384 192v314l-384 -164v-342zM576 358l404 173l-404 173l-404 -173zM1664 -96l384 192v314l-384 -164v-342zM1600 358l404 173l-404 173l-404 -173zM1152 651l384 165v266l-384 -164v-267zM1088 1030l441 189l-441 189l-441 -189zM2176 512v-416q0 -36 -19 -67 t-52 -47l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-5 2 -7 4q-2 -2 -7 -4l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-33 16 -52 47t-19 67v416q0 38 21.5 70t56.5 48l434 186v400q0 38 21.5 70t56.5 48l448 192q23 10 50 10t50 -10l448 -192q35 -16 56.5 -48t21.5 -70 v-400l434 -186q36 -16 57 -48t21 -70z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1848 1197h-511v-124h511v124zM1596 771q-90 0 -146 -52.5t-62 -142.5h408q-18 195 -200 195zM1612 186q63 0 122 32t76 87h221q-100 -307 -427 -307q-214 0 -340.5 132t-126.5 347q0 208 130.5 345.5t336.5 137.5q138 0 240.5 -68t153 -179t50.5 -248q0 -17 -2 -47h-658 q0 -111 57.5 -171.5t166.5 -60.5zM277 236h296q205 0 205 167q0 180 -199 180h-302v-347zM277 773h281q78 0 123.5 36.5t45.5 113.5q0 144 -190 144h-260v-294zM0 1282h594q87 0 155 -14t126.5 -47.5t90 -96.5t31.5 -154q0 -181 -172 -263q114 -32 172 -115t58 -204 q0 -75 -24.5 -136.5t-66 -103.5t-98.5 -71t-121 -42t-134 -13h-611v1260z" /> +<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM499 1041h-371v-787h382q117 0 197 57.5t80 170.5q0 158 -143 200q107 52 107 164q0 57 -19.5 96.5 t-56.5 60.5t-79 29.5t-97 8.5zM477 723h-176v184h163q119 0 119 -90q0 -94 -106 -94zM486 388h-185v217h189q124 0 124 -113q0 -104 -128 -104zM1136 356q-68 0 -104 38t-36 107h411q1 10 1 30q0 132 -74.5 220.5t-203.5 88.5q-128 0 -210 -86t-82 -216q0 -135 79 -217 t213 -82q205 0 267 191h-138q-11 -34 -47.5 -54t-75.5 -20zM1126 722q113 0 124 -122h-254q4 56 39 89t91 33zM964 988h319v-77h-319v77z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1582 954q0 -101 -71.5 -172.5t-172.5 -71.5t-172.5 71.5t-71.5 172.5t71.5 172.5t172.5 71.5t172.5 -71.5t71.5 -172.5zM812 212q0 104 -73 177t-177 73q-27 0 -54 -6l104 -42q77 -31 109.5 -106.5t1.5 -151.5q-31 -77 -107 -109t-152 -1q-21 8 -62 24.5t-61 24.5 q32 -60 91 -96.5t130 -36.5q104 0 177 73t73 177zM1642 953q0 126 -89.5 215.5t-215.5 89.5q-127 0 -216.5 -89.5t-89.5 -215.5q0 -127 89.5 -216t216.5 -89q126 0 215.5 89t89.5 216zM1792 953q0 -189 -133.5 -322t-321.5 -133l-437 -319q-12 -129 -109 -218t-229 -89 q-121 0 -214 76t-118 192l-230 92v429l389 -157q79 48 173 48q13 0 35 -2l284 407q2 187 135.5 319t320.5 132q188 0 321.5 -133.5t133.5 -321.5z" /> +<glyph unicode="" d="M1242 889q0 80 -57 136.5t-137 56.5t-136.5 -57t-56.5 -136q0 -80 56.5 -136.5t136.5 -56.5t137 56.5t57 136.5zM632 301q0 -83 -58 -140.5t-140 -57.5q-56 0 -103 29t-72 77q52 -20 98 -40q60 -24 120 1.5t85 86.5q24 60 -1.5 120t-86.5 84l-82 33q22 5 42 5 q82 0 140 -57.5t58 -140.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v153l172 -69q20 -92 93.5 -152t168.5 -60q104 0 181 70t87 173l345 252q150 0 255.5 105.5t105.5 254.5q0 150 -105.5 255.5t-255.5 105.5 q-148 0 -253 -104.5t-107 -252.5l-225 -322q-9 1 -28 1q-75 0 -137 -37l-297 119v468q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5zM1289 887q0 -100 -71 -170.5t-171 -70.5t-170.5 70.5t-70.5 170.5t70.5 171t170.5 71q101 0 171.5 -70.5t70.5 -171.5z " /> +<glyph unicode="" horiz-adv-x="1792" d="M836 367l-15 -368l-2 -22l-420 29q-36 3 -67 31.5t-47 65.5q-11 27 -14.5 55t4 65t12 55t21.5 64t19 53q78 -12 509 -28zM449 953l180 -379l-147 92q-63 -72 -111.5 -144.5t-72.5 -125t-39.5 -94.5t-18.5 -63l-4 -21l-190 357q-17 26 -18 56t6 47l8 18q35 63 114 188 l-140 86zM1680 436l-188 -359q-12 -29 -36.5 -46.5t-43.5 -20.5l-18 -4q-71 -7 -219 -12l8 -164l-230 367l211 362l7 -173q170 -16 283 -5t170 33zM895 1360q-47 -63 -265 -435l-317 187l-19 12l225 356q20 31 60 45t80 10q24 -2 48.5 -12t42 -21t41.5 -33t36 -34.5 t36 -39.5t32 -35zM1550 1053l212 -363q18 -37 12.5 -76t-27.5 -74q-13 -20 -33 -37t-38 -28t-48.5 -22t-47 -16t-51.5 -14t-46 -12q-34 72 -265 436l313 195zM1407 1279l142 83l-220 -373l-419 20l151 86q-34 89 -75 166t-75.5 123.5t-64.5 80t-47 46.5l-17 13l405 -1 q31 3 58 -10.5t39 -28.5l11 -15q39 -61 112 -190z" /> +<glyph unicode="" horiz-adv-x="2048" d="M480 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM516 768h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5zM1888 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM2048 544v-384 q0 -14 -9 -23t-23 -9h-96v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-1024v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5t179 63.5h768q98 0 179 -63.5t104 -157.5 l105 -419h28q93 0 158.5 -65.5t65.5 -158.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1824 640q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-96v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-1024v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5 t179 63.5h128v224q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-224h128q98 0 179 -63.5t104 -157.5l105 -419h28zM320 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM516 640h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5z M1728 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47z" /> +<glyph unicode="" d="M1504 64q0 -26 -19 -45t-45 -19h-462q1 -17 6 -87.5t5 -108.5q0 -25 -18 -42.5t-43 -17.5h-320q-25 0 -43 17.5t-18 42.5q0 38 5 108.5t6 87.5h-462q-26 0 -45 19t-19 45t19 45l402 403h-229q-26 0 -45 19t-19 45t19 45l402 403h-197q-26 0 -45 19t-19 45t19 45l384 384 q19 19 45 19t45 -19l384 -384q19 -19 19 -45t-19 -45t-45 -19h-197l402 -403q19 -19 19 -45t-19 -45t-45 -19h-229l402 -403q19 -19 19 -45z" /> +<glyph unicode="" d="M1127 326q0 32 -30 51q-193 115 -447 115q-133 0 -287 -34q-42 -9 -42 -52q0 -20 13.5 -34.5t35.5 -14.5q5 0 37 8q132 27 243 27q226 0 397 -103q19 -11 33 -11q19 0 33 13.5t14 34.5zM1223 541q0 40 -35 61q-237 141 -548 141q-153 0 -303 -42q-48 -13 -48 -64 q0 -25 17.5 -42.5t42.5 -17.5q7 0 37 8q122 33 251 33q279 0 488 -124q24 -13 38 -13q25 0 42.5 17.5t17.5 42.5zM1331 789q0 47 -40 70q-126 73 -293 110.5t-343 37.5q-204 0 -364 -47q-23 -7 -38.5 -25.5t-15.5 -48.5q0 -31 20.5 -52t51.5 -21q11 0 40 8q133 37 307 37 q159 0 309.5 -34t253.5 -95q21 -12 40 -12q29 0 50.5 20.5t21.5 51.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1397 1408q58 0 98.5 -40.5t40.5 -98.5v-1258q0 -58 -40.5 -98.5t-98.5 -40.5h-1258q-58 0 -98.5 40.5t-40.5 98.5v1258q0 58 40.5 98.5t98.5 40.5h1258zM1465 11v1258q0 28 -20 48t-48 20h-1258q-28 0 -48 -20t-20 -48v-1258q0 -28 20 -48t48 -20h1258q28 0 48 20t20 48 zM694 749l188 -387l533 145v-496q0 -7 -5.5 -12.5t-12.5 -5.5h-1258q-7 0 -12.5 5.5t-5.5 12.5v141l711 195l-212 439q4 1 12 2.5t12 1.5q170 32 303.5 21.5t221 -46t143.5 -94.5q27 -28 -25 -42q-64 -16 -256 -62l-97 198q-111 7 -240 -16zM1397 1287q7 0 12.5 -5.5 t5.5 -12.5v-428q-85 30 -188 52q-294 64 -645 12l-18 -3l-65 134h-233l85 -190q-132 -51 -230 -137v560q0 7 5.5 12.5t12.5 5.5h1258zM286 387q-14 -3 -26 4.5t-14 21.5q-24 203 166 305l129 -270z" /> +<glyph unicode="" horiz-adv-x="2304" d="M784 164l16 241l-16 523q-1 10 -7.5 17t-16.5 7q-9 0 -16 -7t-7 -17l-14 -523l14 -241q1 -10 7.5 -16.5t15.5 -6.5q22 0 24 23zM1080 193l11 211l-12 586q0 16 -13 24q-8 5 -16 5t-16 -5q-13 -8 -13 -24l-1 -6l-10 -579q0 -1 11 -236v-1q0 -10 6 -17q9 -11 23 -11 q11 0 20 9q9 7 9 20zM35 533l20 -128l-20 -126q-2 -9 -9 -9t-9 9l-17 126l17 128q2 9 9 9t9 -9zM121 612l26 -207l-26 -203q-2 -9 -10 -9q-9 0 -9 10l-23 202l23 207q0 9 9 9q8 0 10 -9zM401 159zM213 650l25 -245l-25 -237q0 -11 -11 -11q-10 0 -12 11l-21 237l21 245 q2 12 12 12q11 0 11 -12zM307 657l23 -252l-23 -244q-2 -13 -14 -13q-13 0 -13 13l-21 244l21 252q0 13 13 13q12 0 14 -13zM401 639l21 -234l-21 -246q-2 -16 -16 -16q-6 0 -10.5 4.5t-4.5 11.5l-20 246l20 234q0 6 4.5 10.5t10.5 4.5q14 0 16 -15zM784 164zM495 785 l21 -380l-21 -246q0 -7 -5 -12.5t-12 -5.5q-16 0 -18 18l-18 246l18 380q2 18 18 18q7 0 12 -5.5t5 -12.5zM589 871l19 -468l-19 -244q0 -8 -5.5 -13.5t-13.5 -5.5q-18 0 -20 19l-16 244l16 468q2 19 20 19q8 0 13.5 -5.5t5.5 -13.5zM687 911l18 -506l-18 -242 q-2 -21 -22 -21q-19 0 -21 21l-16 242l16 506q0 9 6.5 15.5t14.5 6.5q9 0 15 -6.5t7 -15.5zM1079 169v0v0zM881 915l15 -510l-15 -239q0 -10 -7.5 -17.5t-17.5 -7.5t-17 7t-8 18l-14 239l14 510q0 11 7.5 18t17.5 7t17.5 -7t7.5 -18zM980 896l14 -492l-14 -236q0 -11 -8 -19 t-19 -8t-19 8t-9 19l-12 236l12 492q1 12 9 20t19 8t18.5 -8t8.5 -20zM1192 404l-14 -231v0q0 -13 -9 -22t-22 -9t-22 9t-10 22l-6 114l-6 117l12 636v3q2 15 12 24q9 7 20 7q8 0 15 -5q14 -8 16 -26zM2304 423q0 -117 -83 -199.5t-200 -82.5h-786q-13 2 -22 11t-9 22v899 q0 23 28 33q85 34 181 34q195 0 338 -131.5t160 -323.5q53 22 110 22q117 0 200 -83t83 -201z" /> +<glyph unicode="" d="M768 768q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 0q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127 t443 -43zM768 384q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 1536q208 0 385 -34.5t280 -93.5t103 -128v-128q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5 t-103 128v128q0 69 103 128t280 93.5t385 34.5z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M894 465q33 -26 84 -56q59 7 117 7q147 0 177 -49q16 -22 2 -52q0 -1 -1 -2l-2 -2v-1q-6 -38 -71 -38q-48 0 -115 20t-130 53q-221 -24 -392 -83q-153 -262 -242 -262q-15 0 -28 7l-24 12q-1 1 -6 5q-10 10 -6 36q9 40 56 91.5t132 96.5q14 9 23 -6q2 -2 2 -4q52 85 107 197 q68 136 104 262q-24 82 -30.5 159.5t6.5 127.5q11 40 42 40h21h1q23 0 35 -15q18 -21 9 -68q-2 -6 -4 -8q1 -3 1 -8v-30q-2 -123 -14 -192q55 -164 146 -238zM318 54q52 24 137 158q-51 -40 -87.5 -84t-49.5 -74zM716 974q-15 -42 -2 -132q1 7 7 44q0 3 7 43q1 4 4 8 q-1 1 -1 2t-0.5 1.5t-0.5 1.5q-1 22 -13 36q0 -1 -1 -2v-2zM592 313q135 54 284 81q-2 1 -13 9.5t-16 13.5q-76 67 -127 176q-27 -86 -83 -197q-30 -56 -45 -83zM1238 329q-24 24 -140 24q76 -28 124 -28q14 0 18 1q0 1 -2 3z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M233 768v-107h70l164 -661h159l128 485q7 20 10 46q2 16 2 24h4l3 -24q1 -3 3.5 -20t5.5 -26l128 -485h159l164 661h70v107h-300v-107h90l-99 -438q-5 -20 -7 -46l-2 -21h-4l-3 21q-1 5 -4 21t-5 25l-144 545h-114l-144 -545q-2 -9 -4.5 -24.5t-3.5 -21.5l-4 -21h-4l-2 21 q-2 26 -7 46l-99 438h90v107h-300z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M429 106v-106h281v106h-75l103 161q5 7 10 16.5t7.5 13.5t3.5 4h2q1 -4 5 -10q2 -4 4.5 -7.5t6 -8t6.5 -8.5l107 -161h-76v-106h291v106h-68l-192 273l195 282h67v107h-279v-107h74l-103 -159q-4 -7 -10 -16.5t-9 -13.5l-2 -3h-2q-1 4 -5 10q-6 11 -17 23l-106 159h76v107 h-290v-107h68l189 -272l-194 -283h-68z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M416 106v-106h327v106h-93v167h137q76 0 118 15q67 23 106.5 87t39.5 146q0 81 -37 141t-100 87q-48 19 -130 19h-368v-107h92v-555h-92zM769 386h-119v268h120q52 0 83 -18q56 -33 56 -115q0 -89 -62 -120q-31 -15 -78 -15z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M1280 320v-320h-1024v192l192 192l128 -128l384 384zM448 512q-80 0 -136 56t-56 136t56 136t136 56t136 -56t56 -136t-56 -136t-136 -56z" /> +<glyph unicode="" d="M640 1152v128h-128v-128h128zM768 1024v128h-128v-128h128zM640 896v128h-128v-128h128zM768 768v128h-128v-128h128zM1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400 v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-128v-128h-128v128h-512v-1536h1280zM781 593l107 -349q8 -27 8 -52q0 -83 -72.5 -137.5t-183.5 -54.5t-183.5 54.5t-72.5 137.5q0 25 8 52q21 63 120 396v128h128v-128h79 q22 0 39 -13t23 -34zM640 128q53 0 90.5 19t37.5 45t-37.5 45t-90.5 19t-90.5 -19t-37.5 -45t37.5 -45t90.5 -19z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M620 686q20 -8 20 -30v-544q0 -22 -20 -30q-8 -2 -12 -2q-12 0 -23 9l-166 167h-131q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h131l166 167q16 15 35 7zM1037 -3q31 0 50 24q129 159 129 363t-129 363q-16 21 -43 24t-47 -14q-21 -17 -23.5 -43.5t14.5 -47.5 q100 -123 100 -282t-100 -282q-17 -21 -14.5 -47.5t23.5 -42.5q18 -15 40 -15zM826 145q27 0 47 20q87 93 87 219t-87 219q-18 19 -45 20t-46 -17t-20 -44.5t18 -46.5q52 -57 52 -131t-52 -131q-19 -20 -18 -46.5t20 -44.5q20 -17 44 -17z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M768 768q52 0 90 -38t38 -90v-384q0 -52 -38 -90t-90 -38h-384q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h384zM1260 766q20 -8 20 -30v-576q0 -22 -20 -30q-8 -2 -12 -2q-14 0 -23 9l-265 266v90l265 266q9 9 23 9q4 0 12 -2z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M480 768q8 11 21 12.5t24 -6.5l51 -38q11 -8 12.5 -21t-6.5 -24l-182 -243l182 -243q8 -11 6.5 -24t-12.5 -21l-51 -38q-11 -8 -24 -6.5t-21 12.5l-226 301q-14 19 0 38zM1282 467q14 -19 0 -38l-226 -301q-8 -11 -21 -12.5t-24 6.5l-51 38q-11 8 -12.5 21t6.5 24l182 243 l-182 243q-8 11 -6.5 24t12.5 21l51 38q11 8 24 6.5t21 -12.5zM662 6q-13 2 -20.5 13t-5.5 24l138 831q2 13 13 20.5t24 5.5l63 -10q13 -2 20.5 -13t5.5 -24l-138 -831q-2 -13 -13 -20.5t-24 -5.5z" /> +<glyph unicode="" d="M1497 709v-198q-101 -23 -198 -23q-65 -136 -165.5 -271t-181.5 -215.5t-128 -106.5q-80 -45 -162 3q-28 17 -60.5 43.5t-85 83.5t-102.5 128.5t-107.5 184t-105.5 244t-91.5 314.5t-70.5 390h283q26 -218 70 -398.5t104.5 -317t121.5 -235.5t140 -195q169 169 287 406 q-142 72 -223 220t-81 333q0 192 104 314.5t284 122.5q178 0 273 -105.5t95 -297.5q0 -159 -58 -286q-7 -1 -19.5 -3t-46 -2t-63 6t-62 25.5t-50.5 51.5q31 103 31 184q0 87 -29 132t-79 45q-53 0 -85 -49.5t-32 -140.5q0 -186 105 -293.5t267 -107.5q62 0 121 14z" /> +<glyph unicode="" horiz-adv-x="1792" d="M216 367l603 -402v359l-334 223zM154 511l193 129l-193 129v-258zM973 -35l603 402l-269 180l-334 -223v-359zM896 458l272 182l-272 182l-272 -182zM485 733l334 223v359l-603 -402zM1445 640l193 -129v258zM1307 733l269 180l-603 402v-359zM1792 913v-546 q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1800 764q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94 q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55 t121.5 -21q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5 t-85 -189.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 1408q-190 0 -361 -90l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90zM218 279l194 194 q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361t90 -361zM896 -128q190 0 361 90l-194 194q-82 -28 -167 -28t-167 28l-194 -194q171 -90 361 -90zM896 256q159 0 271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5 t271.5 -112.5zM1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348q0 222 101 414.5t276.5 317t390.5 155.5v-260q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 q0 230 -145.5 406t-366.5 221v260q215 -31 390.5 -155.5t276.5 -317t101 -414.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" /> +<glyph unicode="" horiz-adv-x="1792" d="M874 -102v-66q-208 6 -385 109.5t-283 275.5l58 34q29 -49 73 -99l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13zM276 428l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385t98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212zM1528 251 l58 -34q-106 -172 -283 -275.5t-385 -109.5v66q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99zM1377 805l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162 q-67 77 -98 169l232 80q-14 42 -14 85t14 85l-233 80q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169zM874 1448v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98l-57 33 q106 172 282 275.5t385 109.5zM1705 640q0 -205 -98 -385l-57 33q27 52 49 112l-83 28q36 103 36 212q0 112 -35 212l82 28q-19 56 -49 112l57 33q98 -180 98 -385zM1585 1063l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13v66q209 -6 385 -109.5 t282 -275.5zM1748 640q0 173 -67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331t67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71 t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> +<glyph unicode="" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -25.5t19 -63.5zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -53 41 -77v-3q-113 -37 -113 -139q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392 q0 -50 -3 -75zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M595 22q0 100 -165 100q-158 0 -158 -104q0 -101 172 -101q151 0 151 105zM536 777q0 61 -30 102t-89 41q-124 0 -124 -145q0 -135 124 -135q119 0 119 137zM805 1101v-202q-36 -12 -79 -22q16 -43 16 -84q0 -127 -73 -216.5t-197 -112.5q-40 -8 -59.5 -27t-19.5 -58 q0 -31 22.5 -51.5t58 -32t78.5 -22t86 -25.5t78.5 -37.5t58 -64t22.5 -98.5q0 -304 -363 -304q-69 0 -130 12.5t-116 41t-87.5 82t-32.5 127.5q0 165 182 225v4q-67 41 -67 126q0 109 63 137v4q-72 24 -119.5 108.5t-47.5 165.5q0 139 95 231.5t235 92.5q96 0 178 -47 q98 0 218 47zM1123 220h-222q4 45 4 134v609q0 94 -4 128h222q-4 -33 -4 -124v-613q0 -89 4 -134zM1724 442v-196q-71 -39 -174 -39q-62 0 -107 20t-70 50t-39.5 78t-18.5 92t-4 103v351h2v4q-7 0 -19 1t-18 1q-21 0 -59 -6v190h96v76q0 54 -6 89h227q-6 -41 -6 -165h171 v-190q-15 0 -43.5 2t-42.5 2h-85v-365q0 -131 87 -131q61 0 109 33zM1148 1389q0 -58 -39 -101.5t-96 -43.5q-58 0 -98 43.5t-40 101.5q0 59 39.5 103t98.5 44q58 0 96.5 -44.5t38.5 -102.5z" /> +<glyph unicode="" d="M825 547l343 588h-150q-21 -39 -63.5 -118.5t-68 -128.5t-59.5 -118.5t-60 -128.5h-3q-21 48 -44.5 97t-52 105.5t-46.5 92t-54 104.5t-49 95h-150l323 -589v-435h134v436zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M842 964q0 -80 -57 -136.5t-136 -56.5q-60 0 -111 35q-62 -67 -115 -146q-247 -371 -202 -859q1 -22 -12.5 -38.5t-34.5 -18.5h-5q-20 0 -35 13.5t-17 33.5q-14 126 -3.5 247.5t29.5 217t54 186t69 155.5t74 125q61 90 132 165q-16 35 -16 77q0 80 56.5 136.5t136.5 56.5 t136.5 -56.5t56.5 -136.5zM1223 953q0 -158 -78 -292t-212.5 -212t-292.5 -78q-64 0 -131 14q-21 5 -32.5 23.5t-6.5 39.5q5 20 23 31.5t39 7.5q51 -13 108 -13q97 0 186 38t153 102t102 153t38 186t-38 186t-102 153t-153 102t-186 38t-186 -38t-153 -102t-102 -153 t-38 -186q0 -114 52 -218q10 -20 3.5 -40t-25.5 -30t-39.5 -3t-30.5 26q-64 123 -64 265q0 119 46.5 227t124.5 186t186 124t226 46q158 0 292.5 -78t212.5 -212.5t78 -292.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M270 730q-8 19 -8 52q0 20 11 49t24 45q-1 22 7.5 53t22.5 43q0 139 92.5 288.5t217.5 209.5q139 66 324 66q133 0 266 -55q49 -21 90 -48t71 -56t55 -68t42 -74t32.5 -84.5t25.5 -89.5t22 -98l1 -5q55 -83 55 -150q0 -14 -9 -40t-9 -38q0 -1 1.5 -3.5t3.5 -5t2 -3.5 q77 -114 120.5 -214.5t43.5 -208.5q0 -43 -19.5 -100t-55.5 -57q-9 0 -19.5 7.5t-19 17.5t-19 26t-16 26.5t-13.5 26t-9 17.5q-1 1 -3 1l-5 -4q-59 -154 -132 -223q20 -20 61.5 -38.5t69 -41.5t35.5 -65q-2 -4 -4 -16t-7 -18q-64 -97 -302 -97q-53 0 -110.5 9t-98 20 t-104.5 30q-15 5 -23 7q-14 4 -46 4.5t-40 1.5q-41 -45 -127.5 -65t-168.5 -20q-35 0 -69 1.5t-93 9t-101 20.5t-74.5 40t-32.5 64q0 40 10 59.5t41 48.5q11 2 40.5 13t49.5 12q4 0 14 2q2 2 2 4l-2 3q-48 11 -108 105.5t-73 156.5l-5 3q-4 0 -12 -20q-18 -41 -54.5 -74.5 t-77.5 -37.5h-1q-4 0 -6 4.5t-5 5.5q-23 54 -23 100q0 275 252 466z" /> +<glyph unicode="" horiz-adv-x="2048" d="M580 1075q0 41 -25 66t-66 25q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 66 24.5t25 65.5zM1323 568q0 28 -25.5 50t-65.5 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q40 0 65.5 22t25.5 51zM1087 1075q0 41 -24.5 66t-65.5 25 q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 65.5 24.5t24.5 65.5zM1722 568q0 28 -26 50t-65 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q39 0 65 22t26 51zM1456 965q-31 4 -70 4q-169 0 -311 -77t-223.5 -208.5t-81.5 -287.5 q0 -78 23 -152q-35 -3 -68 -3q-26 0 -50 1.5t-55 6.5t-44.5 7t-54.5 10.5t-50 10.5l-253 -127l72 218q-290 203 -290 490q0 169 97.5 311t264 223.5t363.5 81.5q176 0 332.5 -66t262 -182.5t136.5 -260.5zM2048 404q0 -117 -68.5 -223.5t-185.5 -193.5l55 -181l-199 109 q-150 -37 -218 -37q-169 0 -311 70.5t-223.5 191.5t-81.5 264t81.5 264t223.5 191.5t311 70.5q161 0 303 -70.5t227.5 -192t85.5 -263.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-453 185l-242 -295q-18 -23 -49 -23q-13 0 -22 4q-19 7 -30.5 23.5t-11.5 36.5v349l864 1059l-1069 -925l-395 162q-37 14 -40 55q-2 40 32 59l1664 960q15 9 32 9q20 0 36 -11z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-527 215l-298 -327q-18 -21 -47 -21q-14 0 -23 4q-19 7 -30 23.5t-11 36.5v452l-472 193q-37 14 -40 55q-3 39 32 59l1664 960q35 21 68 -2zM1422 26l221 1323l-1434 -827l336 -137 l863 639l-478 -797z" /> +<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298zM896 928v-448q0 -14 -9 -23 t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23z" /> +<glyph unicode="" d="M768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1682 -128q-44 0 -132.5 3.5t-133.5 3.5q-44 0 -132 -3.5t-132 -3.5q-24 0 -37 20.5t-13 45.5q0 31 17 46t39 17t51 7t45 15q33 21 33 140l-1 391q0 21 -1 31q-13 4 -50 4h-675q-38 0 -51 -4q-1 -10 -1 -31l-1 -371q0 -142 37 -164q16 -10 48 -13t57 -3.5t45 -15 t20 -45.5q0 -26 -12.5 -48t-36.5 -22q-47 0 -139.5 3.5t-138.5 3.5q-43 0 -128 -3.5t-127 -3.5q-23 0 -35.5 21t-12.5 45q0 30 15.5 45t36 17.5t47.5 7.5t42 15q33 23 33 143l-1 57v813q0 3 0.5 26t0 36.5t-1.5 38.5t-3.5 42t-6.5 36.5t-11 31.5t-16 18q-15 10 -45 12t-53 2 t-41 14t-18 45q0 26 12 48t36 22q46 0 138.5 -3.5t138.5 -3.5q42 0 126.5 3.5t126.5 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17 -43.5t-38.5 -14.5t-49.5 -4t-43 -13q-35 -21 -35 -160l1 -320q0 -21 1 -32q13 -3 39 -3h699q25 0 38 3q1 11 1 32l1 320q0 139 -35 160 q-18 11 -58.5 12.5t-66 13t-25.5 49.5q0 26 12.5 48t37.5 22q44 0 132 -3.5t132 -3.5q43 0 129 3.5t129 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17.5 -44t-40 -14.5t-51.5 -3t-44 -12.5q-35 -23 -35 -161l1 -943q0 -119 34 -140q16 -10 46 -13.5t53.5 -4.5t41.5 -15.5t18 -44.5 q0 -26 -12 -48t-36 -22z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1278 1347v-73q0 -29 -18.5 -61t-42.5 -32q-50 0 -54 -1q-26 -6 -32 -31q-3 -11 -3 -64v-1152q0 -25 -18 -43t-43 -18h-108q-25 0 -43 18t-18 43v1218h-143v-1218q0 -25 -17.5 -43t-43.5 -18h-108q-26 0 -43.5 18t-17.5 43v496q-147 12 -245 59q-126 58 -192 179 q-64 117 -64 259q0 166 88 286q88 118 209 159q111 37 417 37h479q25 0 43 -18t18 -43z" /> +<glyph unicode="" d="M352 128v-128h-352v128h352zM704 256q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM864 640v-128h-864v128h864zM224 1152v-128h-224v128h224zM1536 128v-128h-736v128h736zM576 1280q26 0 45 -19t19 -45v-256 q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1216 768q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1536 640v-128h-224v128h224zM1536 1152v-128h-864v128h864z" /> +<glyph unicode="" d="M1216 512q133 0 226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5t-226.5 93.5t-93.5 226.5q0 12 2 34l-360 180q-92 -86 -218 -86q-133 0 -226.5 93.5t-93.5 226.5t93.5 226.5t226.5 93.5q126 0 218 -86l360 180q-2 22 -2 34q0 133 93.5 226.5t226.5 93.5 t226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5q-126 0 -218 86l-360 -180q2 -22 2 -34t-2 -34l360 -180q92 86 218 86z" /> +<glyph unicode="" d="M1280 341q0 88 -62.5 151t-150.5 63q-84 0 -145 -58l-241 120q2 16 2 23t-2 23l241 120q61 -58 145 -58q88 0 150.5 63t62.5 151t-62.5 150.5t-150.5 62.5t-151 -62.5t-63 -150.5q0 -7 2 -23l-241 -120q-62 57 -145 57q-88 0 -150.5 -62.5t-62.5 -150.5t62.5 -150.5 t150.5 -62.5q83 0 145 57l241 -120q-2 -16 -2 -23q0 -88 63 -150.5t151 -62.5t150.5 62.5t62.5 150.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M571 947q-10 25 -34 35t-49 0q-108 -44 -191 -127t-127 -191q-10 -25 0 -49t35 -34q13 -5 24 -5q42 0 60 40q34 84 98.5 148.5t148.5 98.5q25 11 35 35t0 49zM1513 1303l46 -46l-244 -243l68 -68q19 -19 19 -45.5t-19 -45.5l-64 -64q89 -161 89 -343q0 -143 -55.5 -273.5 t-150 -225t-225 -150t-273.5 -55.5t-273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5q182 0 343 -89l64 64q19 19 45.5 19t45.5 -19l68 -68zM1521 1359q-10 -10 -22 -10q-13 0 -23 10l-91 90q-9 10 -9 23t9 23q10 9 23 9t23 -9l90 -91 q10 -9 10 -22.5t-10 -22.5zM1751 1129q-11 -9 -23 -9t-23 9l-90 91q-10 9 -10 22.5t10 22.5q9 10 22.5 10t22.5 -10l91 -90q9 -10 9 -23t-9 -23zM1792 1312q0 -14 -9 -23t-23 -9h-96q-14 0 -23 9t-9 23t9 23t23 9h96q14 0 23 -9t9 -23zM1600 1504v-96q0 -14 -9 -23t-23 -9 t-23 9t-9 23v96q0 14 9 23t23 9t23 -9t9 -23zM1751 1449l-91 -90q-10 -10 -22 -10q-13 0 -23 10q-10 9 -10 22.5t10 22.5l90 91q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M609 720l287 208l287 -208l-109 -336h-355zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM1515 186q149 203 149 454v3l-102 -89l-240 224l63 323 l134 -12q-150 206 -389 282l53 -124l-287 -159l-287 159l53 124q-239 -76 -389 -282l135 12l62 -323l-240 -224l-102 89v-3q0 -251 149 -454l30 132l326 -40l139 -298l-116 -69q117 -39 240 -39t240 39l-116 69l139 298l326 40z" /> +<glyph unicode="" horiz-adv-x="1792" d="M448 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM256 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM832 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM66 768q-28 0 -47 19t-19 46v129h514v-129q0 -27 -19 -46t-46 -19h-383zM1216 224v-192q0 -14 -9 -23t-23 -9h-192 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1600 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23 zM1408 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1016v-13h-514v10q0 104 -382 102q-382 -1 -382 -102v-10h-514v13q0 17 8.5 43t34 64t65.5 75.5t110.5 76t160 67.5t224 47.5t293.5 18.5t293 -18.5t224 -47.5 t160.5 -67.5t110.5 -76t65.5 -75.5t34 -64t8.5 -43zM1792 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 962v-129q0 -27 -19 -46t-46 -19h-384q-27 0 -46 19t-19 46v129h514z" /> +<glyph unicode="" horiz-adv-x="1792" d="M704 1216v-768q0 -26 -19 -45t-45 -19v-576q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v512l249 873q7 23 31 23h424zM1024 1216v-704h-256v704h256zM1792 320v-512q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v576q-26 0 -45 19t-19 45v768h424q24 0 31 -23z M736 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23zM1408 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1755 1083q37 -37 37 -90t-37 -91l-401 -400l150 -150l-160 -160q-163 -163 -389.5 -186.5t-411.5 100.5l-362 -362h-181v181l362 362q-124 185 -100.5 411.5t186.5 389.5l160 160l150 -150l400 401q38 37 91 37t90 -37t37 -90.5t-37 -90.5l-400 -401l234 -234l401 400 q38 37 91 37t90 -37z" /> +<glyph unicode="" horiz-adv-x="1792" d="M873 796q0 -83 -63.5 -142.5t-152.5 -59.5t-152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59t152.5 -59t63.5 -143zM1375 796q0 -83 -63 -142.5t-153 -59.5q-89 0 -152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59q90 0 153 -59t63 -143zM1600 616v667q0 87 -32 123.5 t-111 36.5h-1112q-83 0 -112.5 -34t-29.5 -126v-673q43 -23 88.5 -40t81 -28t81 -18.5t71 -11t70 -4t58.5 -0.5t56.5 2t44.5 2q68 1 95 -27q6 -6 10 -9q26 -25 61 -51q7 91 118 87q5 0 36.5 -1.5t43 -2t45.5 -1t53 1t54.5 4.5t61 8.5t62 13.5t67 19.5t67.5 27t72 34.5z M1763 621q-121 -149 -372 -252q84 -285 -23 -465q-66 -113 -183 -148q-104 -32 -182 15q-86 51 -82 164l-1 326v1q-8 2 -24.5 6t-23.5 5l-1 -338q4 -114 -83 -164q-79 -47 -183 -15q-117 36 -182 150q-105 180 -22 463q-251 103 -372 252q-25 37 -4 63t60 -1q3 -2 11 -7 t11 -8v694q0 72 47 123t114 51h1257q67 0 114 -51t47 -123v-694l21 15q39 27 60 1t-4 -63z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 1102v-434h-145v434h145zM1294 1102v-434h-145v434h145zM1294 342l253 254v795h-1194v-1049h326v-217l217 217h398zM1692 1536v-1013l-434 -434h-326l-217 -217h-217v217h-398v1158l109 289h1483z" /> +<glyph unicode="" d="M773 217v-127q-1 -292 -6 -305q-12 -32 -51 -40q-54 -9 -181.5 38t-162.5 89q-13 15 -17 36q-1 12 4 26q4 10 34 47t181 216q1 0 60 70q15 19 39.5 24.5t49.5 -3.5q24 -10 37.5 -29t12.5 -42zM624 468q-3 -55 -52 -70l-120 -39q-275 -88 -292 -88q-35 2 -54 36 q-12 25 -17 75q-8 76 1 166.5t30 124.5t56 32q13 0 202 -77q70 -29 115 -47l84 -34q23 -9 35.5 -30.5t11.5 -48.5zM1450 171q-7 -54 -91.5 -161t-135.5 -127q-37 -14 -63 7q-14 10 -184 287l-47 77q-14 21 -11.5 46t19.5 46q35 43 83 26q1 -1 119 -40q203 -66 242 -79.5 t47 -20.5q28 -22 22 -61zM778 803q5 -102 -54 -122q-58 -17 -114 71l-378 598q-8 35 19 62q41 43 207.5 89.5t224.5 31.5q40 -10 49 -45q3 -18 22 -305.5t24 -379.5zM1440 695q3 -39 -26 -59q-15 -10 -329 -86q-67 -15 -91 -23l1 2q-23 -6 -46 4t-37 32q-30 47 0 87 q1 1 75 102q125 171 150 204t34 39q28 19 65 2q48 -23 123 -133.5t81 -167.5v-3z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1024 1024h-384v-384h384v384zM1152 384v-128h-640v128h640zM1152 1152v-640h-640v640h640zM1792 384v-128h-512v128h512zM1792 640v-128h-512v128h512zM1792 896v-128h-512v128h512zM1792 1152v-128h-512v128h512zM256 192v960h-128v-960q0 -26 19 -45t45 -19t45 19 t19 45zM1920 192v1088h-1536v-1088q0 -33 -11 -64h1483q26 0 45 19t19 45zM2048 1408v-1216q0 -80 -56 -136t-136 -56h-1664q-80 0 -136 56t-56 136v1088h256v128h1792z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1024 13q-20 0 -93 73.5t-73 93.5q0 32 62.5 54t103.5 22t103.5 -22t62.5 -54q0 -20 -73 -93.5t-93 -73.5zM1294 284q-2 0 -40 25t-101.5 50t-128.5 25t-128.5 -25t-101 -50t-40.5 -25q-18 0 -93.5 75t-75.5 93q0 13 10 23q78 77 196 121t233 44t233 -44t196 -121 q10 -10 10 -23q0 -18 -75.5 -93t-93.5 -75zM1567 556q-11 0 -23 8q-136 105 -252 154.5t-268 49.5q-85 0 -170.5 -22t-149 -53t-113.5 -62t-79 -53t-31 -22q-17 0 -92 75t-75 93q0 12 10 22q132 132 320 205t380 73t380 -73t320 -205q10 -10 10 -22q0 -18 -75 -93t-92 -75z M1838 827q-11 0 -22 9q-179 157 -371.5 236.5t-420.5 79.5t-420.5 -79.5t-371.5 -236.5q-11 -9 -22 -9q-17 0 -92.5 75t-75.5 93q0 13 10 23q187 186 445 288t527 102t527 -102t445 -288q10 -10 10 -23q0 -18 -75.5 -93t-92.5 -75z" /> +<glyph unicode="" horiz-adv-x="1792" d="M384 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5zM384 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 768q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1536 0v384q0 52 -38 90t-90 38t-90 -38t-38 -90v-384q0 -52 38 -90t90 -38t90 38t38 90zM1152 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1536 1088v256q0 26 -19 45t-45 19h-1280q-26 0 -45 -19t-19 -45v-256q0 -26 19 -45t45 -19h1280q26 0 45 19t19 45zM1536 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1408v-1536q0 -52 -38 -90t-90 -38 h-1408q-52 0 -90 38t-38 90v1536q0 52 38 90t90 38h1408q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1112 1090q0 159 -237 159h-70q-32 0 -59.5 -21.5t-34.5 -52.5l-63 -276q-2 -5 -2 -16q0 -24 17 -39.5t41 -15.5h53q69 0 128.5 13t112.5 41t83.5 81.5t30.5 126.5zM1716 938q0 -265 -220 -428q-219 -161 -612 -161h-61q-32 0 -59 -21.5t-34 -52.5l-73 -316 q-8 -36 -40.5 -61.5t-69.5 -25.5h-213q-31 0 -53 20t-22 51q0 10 13 65h151q34 0 64 23.5t38 56.5l73 316q8 33 37.5 57t63.5 24h61q390 0 607 160t217 421q0 129 -51 207q183 -92 183 -335zM1533 1123q0 -264 -221 -428q-218 -161 -612 -161h-60q-32 0 -59.5 -22t-34.5 -53 l-73 -315q-8 -36 -40 -61.5t-69 -25.5h-214q-31 0 -52.5 19.5t-21.5 51.5q0 8 2 20l300 1301q8 36 40.5 61.5t69.5 25.5h444q68 0 125 -4t120.5 -15t113.5 -30t96.5 -50.5t77.5 -74t49.5 -103.5t18.5 -136z" /> +<glyph unicode="" horiz-adv-x="1792" d="M602 949q19 -61 31 -123.5t17 -141.5t-14 -159t-62 -145q-21 81 -67 157t-95.5 127t-99 90.5t-78.5 57.5t-33 19q-62 34 -81.5 100t14.5 128t101 81.5t129 -14.5q138 -83 238 -177zM927 1236q11 -25 20.5 -46t36.5 -100.5t42.5 -150.5t25.5 -179.5t0 -205.5t-47.5 -209.5 t-105.5 -208.5q-51 -72 -138 -72q-54 0 -98 31q-57 40 -69 109t28 127q60 85 81 195t13 199.5t-32 180.5t-39 128t-22 52q-31 63 -8.5 129.5t85.5 97.5q34 17 75 17q47 0 88.5 -25t63.5 -69zM1248 567q-17 -160 -72 -311q-17 131 -63 246q25 174 -5 361q-27 178 -94 342 q114 -90 212 -211q9 -37 15 -80q26 -179 7 -347zM1520 1440q9 -17 23.5 -49.5t43.5 -117.5t50.5 -178t34 -227.5t5 -269t-47 -300t-112.5 -323.5q-22 -48 -66 -75.5t-95 -27.5q-39 0 -74 16q-67 31 -92.5 100t4.5 136q58 126 90 257.5t37.5 239.5t-3.5 213.5t-26.5 180.5 t-38.5 138.5t-32.5 90t-15.5 32.5q-34 65 -11.5 135.5t87.5 104.5q37 20 81 20q49 0 91.5 -25.5t66.5 -70.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1975 546h-138q14 37 66 179l3 9q4 10 10 26t9 26l12 -55zM531 611l-58 295q-11 54 -75 54h-268l-2 -13q311 -79 403 -336zM710 960l-162 -438l-17 89q-26 70 -85 129.5t-131 88.5l135 -510h175l261 641h-176zM849 318h166l104 642h-166zM1617 944q-69 27 -149 27 q-123 0 -201 -59t-79 -153q-1 -102 145 -174q48 -23 67 -41t19 -39q0 -30 -30 -46t-69 -16q-86 0 -156 33l-22 11l-23 -144q74 -34 185 -34q130 -1 208.5 59t80.5 160q0 106 -140 174q-49 25 -71 42t-22 38q0 22 24.5 38.5t70.5 16.5q70 1 124 -24l15 -8zM2042 960h-128 q-65 0 -87 -54l-246 -588h174l35 96h212q5 -22 20 -96h154zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2304" d="M671 603h-13q-47 0 -47 -32q0 -22 20 -22q17 0 28 15t12 39zM1066 639h62v3q1 4 0.5 6.5t-1 7t-2 8t-4.5 6.5t-7.5 5t-11.5 2q-28 0 -36 -38zM1606 603h-12q-48 0 -48 -32q0 -22 20 -22q17 0 28 15t12 39zM1925 629q0 41 -30 41q-19 0 -31 -20t-12 -51q0 -42 28 -42 q20 0 32.5 20t12.5 52zM480 770h87l-44 -262h-56l32 201l-71 -201h-39l-4 200l-34 -200h-53l44 262h81l2 -163zM733 663q0 -6 -4 -42q-16 -101 -17 -113h-47l1 22q-20 -26 -58 -26q-23 0 -37.5 16t-14.5 42q0 39 26 60.5t73 21.5q14 0 23 -1q0 3 0.5 5.5t1 4.5t0.5 3 q0 20 -36 20q-29 0 -59 -10q0 4 7 48q38 11 67 11q74 0 74 -62zM889 721l-8 -49q-22 3 -41 3q-27 0 -27 -17q0 -8 4.5 -12t21.5 -11q40 -19 40 -60q0 -72 -87 -71q-34 0 -58 6q0 2 7 49q29 -8 51 -8q32 0 32 19q0 7 -4.5 11.5t-21.5 12.5q-43 20 -43 59q0 72 84 72 q30 0 50 -4zM977 721h28l-7 -52h-29q-2 -17 -6.5 -40.5t-7 -38.5t-2.5 -18q0 -16 19 -16q8 0 16 2l-8 -47q-21 -7 -40 -7q-43 0 -45 47q0 12 8 56q3 20 25 146h55zM1180 648q0 -23 -7 -52h-111q-3 -22 10 -33t38 -11q30 0 58 14l-9 -54q-30 -8 -57 -8q-95 0 -95 95 q0 55 27.5 90.5t69.5 35.5q35 0 55.5 -21t20.5 -56zM1319 722q-13 -23 -22 -62q-22 2 -31 -24t-25 -128h-56l3 14q22 130 29 199h51l-3 -33q14 21 25.5 29.5t28.5 4.5zM1506 763l-9 -57q-28 14 -50 14q-31 0 -51 -27.5t-20 -70.5q0 -30 13.5 -47t38.5 -17q21 0 48 13 l-10 -59q-28 -8 -50 -8q-45 0 -71.5 30.5t-26.5 82.5q0 70 35.5 114.5t91.5 44.5q26 0 61 -13zM1668 663q0 -18 -4 -42q-13 -79 -17 -113h-46l1 22q-20 -26 -59 -26q-23 0 -37 16t-14 42q0 39 25.5 60.5t72.5 21.5q15 0 23 -1q2 7 2 13q0 20 -36 20q-29 0 -59 -10q0 4 8 48 q38 11 67 11q73 0 73 -62zM1809 722q-14 -24 -21 -62q-23 2 -31.5 -23t-25.5 -129h-56l3 14q19 104 29 199h52q0 -11 -4 -33q15 21 26.5 29.5t27.5 4.5zM1950 770h56l-43 -262h-53l3 19q-23 -23 -52 -23q-31 0 -49.5 24t-18.5 64q0 53 27.5 92t64.5 39q31 0 53 -29z M2061 640q0 148 -72.5 273t-198 198t-273.5 73q-181 0 -328 -110q127 -116 171 -284h-50q-44 150 -158 253q-114 -103 -158 -253h-50q44 168 171 284q-147 110 -328 110q-148 0 -273.5 -73t-198 -198t-72.5 -273t72.5 -273t198 -198t273.5 -73q181 0 328 110 q-120 111 -165 264h50q46 -138 152 -233q106 95 152 233h50q-45 -153 -165 -264q147 -110 328 -110q148 0 273.5 73t198 198t72.5 273zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2304" d="M313 759q0 -51 -36 -84q-29 -26 -89 -26h-17v220h17q61 0 89 -27q36 -31 36 -83zM2089 824q0 -52 -64 -52h-19v101h20q63 0 63 -49zM380 759q0 74 -50 120.5t-129 46.5h-95v-333h95q74 0 119 38q60 51 60 128zM410 593h65v333h-65v-333zM730 694q0 40 -20.5 62t-75.5 42 q-29 10 -39.5 19t-10.5 23q0 16 13.5 26.5t34.5 10.5q29 0 53 -27l34 44q-41 37 -98 37q-44 0 -74 -27.5t-30 -67.5q0 -35 18 -55.5t64 -36.5q37 -13 45 -19q19 -12 19 -34q0 -20 -14 -33.5t-36 -13.5q-48 0 -71 44l-42 -40q44 -64 115 -64q51 0 83 30.5t32 79.5zM1008 604 v77q-37 -37 -78 -37q-49 0 -80.5 32.5t-31.5 82.5q0 48 31.5 81.5t77.5 33.5q43 0 81 -38v77q-40 20 -80 20q-74 0 -125.5 -50.5t-51.5 -123.5t51 -123.5t125 -50.5q42 0 81 19zM2240 0v527q-65 -40 -144.5 -84t-237.5 -117t-329.5 -137.5t-417.5 -134.5t-504 -118h1569 q26 0 45 19t19 45zM1389 757q0 75 -53 128t-128 53t-128 -53t-53 -128t53 -128t128 -53t128 53t53 128zM1541 584l144 342h-71l-90 -224l-89 224h-71l142 -342h35zM1714 593h184v56h-119v90h115v56h-115v74h119v57h-184v-333zM2105 593h80l-105 140q76 16 76 94q0 47 -31 73 t-87 26h-97v-333h65v133h9zM2304 1274v-1268q0 -56 -38.5 -95t-93.5 -39h-2040q-55 0 -93.5 39t-38.5 95v1268q0 56 38.5 95t93.5 39h2040q55 0 93.5 -39t38.5 -95z" /> +<glyph unicode="" horiz-adv-x="2304" d="M119 854h89l-45 108zM740 328l74 79l-70 79h-163v-49h142v-55h-142v-54h159zM898 406l99 -110v217zM1186 453q0 33 -40 33h-84v-69h83q41 0 41 36zM1475 457q0 29 -42 29h-82v-61h81q43 0 43 32zM1197 923q0 29 -42 29h-82v-60h81q43 0 43 31zM1656 854h89l-44 108z M699 1009v-271h-66v212l-94 -212h-57l-94 212v-212h-132l-25 60h-135l-25 -60h-70l116 271h96l110 -257v257h106l85 -184l77 184h108zM1255 453q0 -20 -5.5 -35t-14 -25t-22.5 -16.5t-26 -10t-31.5 -4.5t-31.5 -1t-32.5 0.5t-29.5 0.5v-91h-126l-80 90l-83 -90h-256v271h260 l80 -89l82 89h207q109 0 109 -89zM964 794v-56h-217v271h217v-57h-152v-49h148v-55h-148v-54h152zM2304 235v-229q0 -55 -38.5 -94.5t-93.5 -39.5h-2040q-55 0 -93.5 39.5t-38.5 94.5v678h111l25 61h55l25 -61h218v46l19 -46h113l20 47v-47h541v99l10 1q10 0 10 -14v-86h279 v23q23 -12 55 -18t52.5 -6.5t63 0.5t51.5 1l25 61h56l25 -61h227v58l34 -58h182v378h-180v-44l-25 44h-185v-44l-23 44h-249q-69 0 -109 -22v22h-172v-22q-24 22 -73 22h-628l-43 -97l-43 97h-198v-44l-22 44h-169l-78 -179v391q0 55 38.5 94.5t93.5 39.5h2040 q55 0 93.5 -39.5t38.5 -94.5v-678h-120q-51 0 -81 -22v22h-177q-55 0 -78 -22v22h-316v-22q-31 22 -87 22h-209v-22q-23 22 -91 22h-234l-54 -58l-50 58h-349v-378h343l55 59l52 -59h211v89h21q59 0 90 13v-102h174v99h8q8 0 10 -2t2 -10v-87h529q57 0 88 24v-24h168 q60 0 95 17zM1546 469q0 -23 -12 -43t-34 -29q25 -9 34 -26t9 -46v-54h-65v45q0 33 -12 43.5t-46 10.5h-69v-99h-65v271h154q48 0 77 -15t29 -58zM1269 936q0 -24 -12.5 -44t-33.5 -29q26 -9 34.5 -25.5t8.5 -46.5v-53h-65q0 9 0.5 26.5t0 25t-3 18.5t-8.5 16t-17.5 8.5 t-29.5 3.5h-70v-98h-64v271l153 -1q49 0 78 -14.5t29 -57.5zM1798 327v-56h-216v271h216v-56h-151v-49h148v-55h-148v-54zM1372 1009v-271h-66v271h66zM2065 357q0 -86 -102 -86h-126v58h126q34 0 34 25q0 16 -17 21t-41.5 5t-49.5 3.5t-42 22.5t-17 55q0 39 26 60t66 21 h130v-57h-119q-36 0 -36 -25q0 -16 17.5 -20.5t42 -4t49 -2.5t42 -21.5t17.5 -54.5zM2304 407v-101q-24 -35 -88 -35h-125v58h125q33 0 33 25q0 13 -12.5 19t-31 5.5t-40 2t-40 8t-31 24t-12.5 48.5q0 39 26.5 60t66.5 21h129v-57h-118q-36 0 -36 -25q0 -20 29 -22t68.5 -5 t56.5 -26zM2139 1008v-270h-92l-122 203v-203h-132l-26 60h-134l-25 -60h-75q-129 0 -129 133q0 138 133 138h63v-59q-7 0 -28 1t-28.5 0.5t-23 -2t-21.5 -6.5t-14.5 -13.5t-11.5 -23t-3 -33.5q0 -38 13.5 -58t49.5 -20h29l92 213h97l109 -256v256h99l114 -188v188h66z" /> +<glyph unicode="" horiz-adv-x="2304" d="M322 689h-15q-19 0 -19 18q0 28 19 85q5 15 15 19.5t28 4.5q77 0 77 -49q0 -41 -30.5 -59.5t-74.5 -18.5zM664 528q-47 0 -47 29q0 62 123 62l3 -3q-5 -88 -79 -88zM1438 687h-15q-19 0 -19 19q0 28 19 85q5 15 14.5 19t28.5 4q77 0 77 -49q0 -41 -30.5 -59.5 t-74.5 -18.5zM1780 527q-47 0 -47 30q0 62 123 62l3 -3q-5 -89 -79 -89zM373 894h-128q-8 0 -14.5 -4t-8.5 -7.5t-7 -12.5q-3 -7 -45 -190t-42 -192q0 -7 5.5 -12.5t13.5 -5.5h62q25 0 32.5 34.5l15 69t32.5 34.5q47 0 87.5 7.5t80.5 24.5t63.5 52.5t23.5 84.5 q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM719 798q-38 0 -74 -6q-2 0 -8.5 -1t-9 -1.5l-7.5 -1.5t-7.5 -2t-6.5 -3t-6.5 -4t-5 -5t-4.5 -7t-4 -9q-9 -29 -9 -39t9 -10q5 0 21.5 5t19.5 6q30 8 58 8q74 0 74 -36q0 -11 -10 -14q-8 -2 -18 -3t-21.5 -1.5t-17.5 -1.5 q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5q0 -38 26 -59.5t64 -21.5q24 0 45.5 6.5t33 13t38.5 23.5q-3 -7 -3 -15t5.5 -13.5t12.5 -5.5h56q1 1 7 3.5t7.5 3.5t5 3.5t5 5.5t2.5 8l45 194q4 13 4 30q0 81 -145 81zM1247 793h-74q-22 0 -39 -23q-5 -7 -29.5 -51 t-46.5 -81.5t-26 -38.5l-5 4q0 77 -27 166q-1 5 -3.5 8.5t-6 6.5t-6.5 5t-8.5 3t-8.5 1.5t-9.5 1t-9 0.5h-10h-8.5q-38 0 -38 -21l1 -5q5 -53 25 -151t25 -143q2 -16 2 -24q0 -19 -30.5 -61.5t-30.5 -58.5q0 -13 40 -13q61 0 76 25l245 415q10 20 10 26q0 9 -8 9zM1489 892 h-129q-18 0 -29 -23q-6 -13 -46.5 -191.5t-40.5 -190.5q0 -20 43 -20h7.5h9h9t9.5 1t8.5 2t8.5 3t6.5 4.5t5.5 6t3 8.5l21 91q2 10 10.5 17t19.5 7q47 0 87.5 7t80.5 24.5t63.5 52.5t23.5 84q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM1835 798q-26 0 -74 -6 q-38 -6 -48 -16q-7 -8 -11 -19q-8 -24 -8 -39q0 -10 8 -10q1 0 41 12q30 8 58 8q74 0 74 -36q0 -12 -10 -14q-4 -1 -57 -7q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5t26 -58.5t64 -21.5q24 0 45 6t34 13t38 24q-3 -15 -3 -16q0 -5 2 -8.5t6.5 -5.5t8 -3.5 t10.5 -2t9.5 -0.5h9.5h8q42 0 48 25l45 194q3 15 3 31q0 81 -145 81zM2157 889h-55q-25 0 -33 -40q-10 -44 -36.5 -167t-42.5 -190v-5q0 -16 16 -18h1h57q10 0 18.5 6.5t10.5 16.5l83 374h-1l1 5q0 7 -5.5 12.5t-13.5 5.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048 q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1597 633q0 -69 -21 -106q-19 -35 -52 -35q-23 0 -41 9v224q29 30 57 30q57 0 57 -122zM2035 669h-110q6 98 56 98q51 0 54 -98zM476 534q0 59 -33 91.5t-101 57.5q-36 13 -52 24t-16 25q0 26 38 26q58 0 124 -33l18 112q-67 32 -149 32q-77 0 -123 -38q-48 -39 -48 -109 q0 -58 32.5 -90.5t99.5 -56.5q39 -14 54.5 -25.5t15.5 -27.5q0 -31 -48 -31q-29 0 -70 12.5t-72 30.5l-18 -113q72 -41 168 -41q81 0 129 37q51 41 51 117zM771 749l19 111h-96v135l-129 -21l-18 -114l-46 -8l-17 -103h62v-219q0 -84 44 -120q38 -30 111 -30q32 0 79 11v118 q-32 -7 -44 -7q-42 0 -42 50v197h77zM1087 724v139q-15 3 -28 3q-32 0 -55.5 -16t-33.5 -46l-10 56h-131v-471h150v306q26 31 82 31q16 0 26 -2zM1124 389h150v471h-150v-471zM1746 638q0 122 -45 179q-40 52 -111 52q-64 0 -117 -56l-8 47h-132v-645l150 25v151 q36 -11 68 -11q83 0 134 56q61 65 61 202zM1278 986q0 33 -23 56t-56 23t-56 -23t-23 -56t23 -56.5t56 -23.5t56 23.5t23 56.5zM2176 629q0 113 -48 176q-50 64 -144 64q-96 0 -151.5 -66t-55.5 -180q0 -128 63 -188q55 -55 161 -55q101 0 160 40l-16 103q-57 -31 -128 -31 q-43 0 -63 19q-23 19 -28 66h248q2 14 2 52zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1558 684q61 -356 298 -556q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5zM1024 -176q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5zM2026 1424q8 -10 7.5 -23.5t-10.5 -22.5 l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5 l418 363q10 8 23.5 7t21.5 -11z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1040 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM503 315l877 760q-42 88 -132.5 146.5t-223.5 58.5q-93 0 -169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -384 -137 -645zM1856 128 q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5l149 129h757q-166 187 -227 459l111 97q61 -356 298 -556zM1942 1520l84 -96q8 -10 7.5 -23.5t-10.5 -22.5l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161 q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5l418 363q10 8 23.5 7t21.5 -11z" /> +<glyph unicode="" horiz-adv-x="1408" d="M512 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM768 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1024 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167 q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" /> +<glyph unicode="" d="M1150 462v-109q0 -50 -36.5 -89t-94 -60.5t-118 -32.5t-117.5 -11q-205 0 -342.5 139t-137.5 346q0 203 136 339t339 136q34 0 75.5 -4.5t93 -18t92.5 -34t69 -56.5t28 -81v-109q0 -16 -16 -16h-118q-16 0 -16 16v70q0 43 -65.5 67.5t-137.5 24.5q-140 0 -228.5 -91.5 t-88.5 -237.5q0 -151 91.5 -249.5t233.5 -98.5q68 0 138 24t70 66v70q0 7 4.5 11.5t10.5 4.5h119q6 0 11 -4.5t5 -11.5zM768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M972 761q0 108 -53.5 169t-147.5 61q-63 0 -124 -30.5t-110 -84.5t-79.5 -137t-30.5 -180q0 -112 53.5 -173t150.5 -61q96 0 176 66.5t122.5 166t42.5 203.5zM1536 640q0 -111 -37 -197t-98.5 -135t-131.5 -74.5t-145 -27.5q-6 0 -15.5 -0.5t-16.5 -0.5q-95 0 -142 53 q-28 33 -33 83q-52 -66 -131.5 -110t-173.5 -44q-161 0 -249.5 95.5t-88.5 269.5q0 157 66 290t179 210.5t246 77.5q87 0 155 -35.5t106 -99.5l2 19l11 56q1 6 5.5 12t9.5 6h118q5 0 13 -11q5 -5 3 -16l-120 -614q-5 -24 -5 -48q0 -39 12.5 -52t44.5 -13q28 1 57 5.5t73 24 t77 50t57 89.5t24 137q0 292 -174 466t-466 174q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51q228 0 405 144q11 9 24 8t21 -12l41 -49q8 -12 7 -24q-2 -13 -12 -22q-102 -83 -227.5 -128t-258.5 -45q-156 0 -298 61 t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q344 0 556 -212t212 -556z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1698 1442q94 -94 94 -226.5t-94 -225.5l-225 -223l104 -104q10 -10 10 -23t-10 -23l-210 -210q-10 -10 -23 -10t-23 10l-105 105l-603 -603q-37 -37 -90 -37h-203l-256 -128l-64 64l128 256v203q0 53 37 90l603 603l-105 105q-10 10 -10 23t10 23l210 210q10 10 23 10 t23 -10l104 -104l223 225q93 94 225.5 94t226.5 -94zM512 64l576 576l-192 192l-576 -576v-192h192z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1615 1536q70 0 122.5 -46.5t52.5 -116.5q0 -63 -45 -151q-332 -629 -465 -752q-97 -91 -218 -91q-126 0 -216.5 92.5t-90.5 219.5q0 128 92 212l638 579q59 54 130 54zM706 502q39 -76 106.5 -130t150.5 -76l1 -71q4 -213 -129.5 -347t-348.5 -134q-123 0 -218 46.5 t-152.5 127.5t-86.5 183t-29 220q7 -5 41 -30t62 -44.5t59 -36.5t46 -17q41 0 55 37q25 66 57.5 112.5t69.5 76t88 47.5t103 25.5t125 10.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 128v-384h-1792v384q45 0 85 14t59 27.5t47 37.5q30 27 51.5 38t56.5 11t55.5 -11t52.5 -38q29 -25 47 -38t58 -27t86 -14q45 0 85 14.5t58 27t48 37.5q21 19 32.5 27t31 15t43.5 7q35 0 56.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14t85 14t59 27.5t47 37.5 q30 27 51.5 38t56.5 11q34 0 55.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14zM1792 448v-192q-35 0 -55.5 11t-52.5 38q-29 25 -47 38t-58 27t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-22 -19 -33 -27t-31 -15t-44 -7q-35 0 -56.5 11t-51.5 38q-29 25 -47 38t-58 27 t-86 14q-45 0 -85 -14.5t-58 -27t-48 -37.5q-21 -19 -32.5 -27t-31 -15t-43.5 -7q-35 0 -56.5 11t-51.5 38q-28 24 -47 37.5t-59 27.5t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-30 -27 -51.5 -38t-56.5 -11v192q0 80 56 136t136 56h64v448h256v-448h256v448h256v-448h256v448 h256v-448h64q80 0 136 -56t56 -136zM512 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1024 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51 t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1536 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1664 1024l256 -896h-1664v576l448 576l576 -576z" /> +<glyph unicode="" horiz-adv-x="1792" d="M768 646l546 -546q-106 -108 -247.5 -168t-298.5 -60q-209 0 -385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103v-762zM955 640h773q0 -157 -60 -298.5t-168 -247.5zM1664 768h-768v768q209 0 385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1920 1248v-435q0 -21 -19.5 -29.5t-35.5 7.5l-121 121l-633 -633q-10 -10 -23 -10t-23 10l-233 233l-416 -416l-192 192l585 585q10 10 23 10t23 -10l233 -233l464 464l-121 121q-16 16 -7.5 35.5t29.5 19.5h435q14 0 23 -9 t9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1292 832q0 -6 10 -41q10 -29 25 -49.5t41 -34t44 -20t55 -16.5q325 -91 325 -332q0 -146 -105.5 -242.5t-254.5 -96.5q-59 0 -111.5 18.5t-91.5 45.5t-77 74.5t-63 87.5t-53.5 103.5t-43.5 103t-39.5 106.5t-35.5 95q-32 81 -61.5 133.5t-73.5 96.5t-104 64t-142 20 q-96 0 -183 -55.5t-138 -144.5t-51 -185q0 -160 106.5 -279.5t263.5 -119.5q177 0 258 95q56 63 83 116l84 -152q-15 -34 -44 -70l1 -1q-131 -152 -388 -152q-147 0 -269.5 79t-190.5 207.5t-68 274.5q0 105 43.5 206t116 176.5t172 121.5t204.5 46q87 0 159 -19t123.5 -50 t95 -80t72.5 -99t58.5 -117t50.5 -124.5t50 -130.5t55 -127q96 -200 233 -200q81 0 138.5 48.5t57.5 128.5q0 42 -19 72t-50.5 46t-72.5 31.5t-84.5 27t-87.5 34t-81 52t-65 82t-39 122.5q-3 16 -3 33q0 110 87.5 192t198.5 78q78 -3 120.5 -14.5t90.5 -53.5h-1 q12 -11 23 -24.5t26 -36t19 -27.5l-129 -99q-26 49 -54 70v1q-23 21 -97 21q-49 0 -84 -33t-35 -83z" /> +<glyph unicode="" d="M1432 484q0 173 -234 239q-35 10 -53 16.5t-38 25t-29 46.5q0 2 -2 8.5t-3 12t-1 7.5q0 36 24.5 59.5t60.5 23.5q54 0 71 -15h-1q20 -15 39 -51l93 71q-39 54 -49 64q-33 29 -67.5 39t-85.5 10q-80 0 -142 -57.5t-62 -137.5q0 -7 2 -23q16 -96 64.5 -140t148.5 -73 q29 -8 49 -15.5t45 -21.5t38.5 -34.5t13.5 -46.5v-5q1 -58 -40.5 -93t-100.5 -35q-97 0 -167 144q-23 47 -51.5 121.5t-48 125.5t-54 110.5t-74 95.5t-103.5 60.5t-147 24.5q-101 0 -192 -56t-144 -148t-50 -192v-1q4 -108 50.5 -199t133.5 -147.5t196 -56.5q186 0 279 110 q20 27 31 51l-60 109q-42 -80 -99 -116t-146 -36q-115 0 -191 87t-76 204q0 105 82 189t186 84q112 0 170 -53.5t104 -172.5q8 -21 25.5 -68.5t28.5 -76.5t31.5 -74.5t38.5 -74t45.5 -62.5t55.5 -53.5t66 -33t80 -13.5q107 0 183 69.5t76 174.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1152 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1920 640q0 104 -40.5 198.5 t-109.5 163.5t-163.5 109.5t-198.5 40.5h-386q119 -90 188.5 -224t69.5 -288t-69.5 -288t-188.5 -224h386q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM2048 640q0 -130 -51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5 t-136.5 204t-51 248.5t51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M0 640q0 130 51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5t-51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5t-136.5 204t-51 248.5zM1408 128q104 0 198.5 40.5t163.5 109.5 t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M762 384h-314q-40 0 -57.5 35t6.5 67l188 251q-65 31 -137 31q-132 0 -226 -94t-94 -226t94 -226t226 -94q115 0 203 72.5t111 183.5zM576 512h186q-18 85 -75 148zM1056 512l288 384h-480l-99 -132q105 -103 126 -252h165zM2176 448q0 132 -94 226t-226 94 q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94t226 94t94 226zM2304 448q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 97 39.5 183.5t109.5 149.5l-65 98l-353 -469 q-18 -26 -51 -26h-197q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q114 0 215 -55l137 183h-224q-26 0 -45 19t-19 45t19 45t45 19h384v-128h435l-85 128h-222q-26 0 -45 19t-19 45t19 45t45 19h256q33 0 53 -28l267 -400 q91 44 192 44q185 0 316.5 -131.5t131.5 -316.5z" /> +<glyph unicode="" d="M384 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1362 716l-72 384q-5 23 -22.5 37.5t-40.5 14.5 h-918q-23 0 -40.5 -14.5t-22.5 -37.5l-72 -384q-5 -30 14 -53t49 -23h1062q30 0 49 23t14 53zM1136 1328q0 20 -14 34t-34 14h-640q-20 0 -34 -14t-14 -34t14 -34t34 -14h640q20 0 34 14t14 34zM1536 603v-603h-128v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5v128h-768v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5v128h-128v603q0 112 25 223l103 454q9 78 97.5 137t230 89t312.5 30t312.5 -30t230 -89t97.5 -137l105 -454q23 -102 23 -223z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1463 704q0 -35 -25 -60.5t-61 -25.5h-702q-36 0 -61 25.5t-25 60.5t25 60.5t61 25.5h702q36 0 61 -25.5t25 -60.5zM1677 704q0 86 -23 170h-982q-36 0 -61 25t-25 60q0 36 25 61t61 25h908q-88 143 -235 227t-320 84q-177 0 -327.5 -87.5t-238 -237.5t-87.5 -327 q0 -86 23 -170h982q36 0 61 -25t25 -60q0 -36 -25 -61t-61 -25h-908q88 -143 235.5 -227t320.5 -84q132 0 253 51.5t208 139t139 208t52 253.5zM2048 959q0 -35 -25 -60t-61 -25h-131q17 -85 17 -170q0 -167 -65.5 -319.5t-175.5 -263t-262.5 -176t-319.5 -65.5 q-246 0 -448.5 133t-301.5 350h-189q-36 0 -61 25t-25 61q0 35 25 60t61 25h132q-17 85 -17 170q0 167 65.5 319.5t175.5 263t262.5 176t320.5 65.5q245 0 447.5 -133t301.5 -350h188q36 0 61 -25t25 -61z" /> +<glyph unicode="" horiz-adv-x="1280" d="M953 1158l-114 -328l117 -21q165 451 165 518q0 56 -38 56q-57 0 -130 -225zM654 471l33 -88q37 42 71 67l-33 5.5t-38.5 7t-32.5 8.5zM362 1367q0 -98 159 -521q18 10 49 10q15 0 75 -5l-121 351q-75 220 -123 220q-19 0 -29 -17.5t-10 -37.5zM283 608q0 -36 51.5 -119 t117.5 -153t100 -70q14 0 25.5 13t11.5 27q0 24 -32 102q-13 32 -32 72t-47.5 89t-61.5 81t-62 32q-20 0 -45.5 -27t-25.5 -47zM125 273q0 -41 25 -104q59 -145 183.5 -227t281.5 -82q227 0 382 170q152 169 152 427q0 43 -1 67t-11.5 62t-30.5 56q-56 49 -211.5 75.5 t-270.5 26.5q-37 0 -49 -11q-12 -5 -12 -35q0 -34 21.5 -60t55.5 -40t77.5 -23.5t87.5 -11.5t85 -4t70 0h23q24 0 40 -19q15 -19 19 -55q-28 -28 -96 -54q-61 -22 -93 -46q-64 -46 -108.5 -114t-44.5 -137q0 -31 18.5 -88.5t18.5 -87.5l-3 -12q-4 -12 -4 -14 q-137 10 -146 216q-8 -2 -41 -2q2 -7 2 -21q0 -53 -40.5 -89.5t-94.5 -36.5q-82 0 -166.5 78t-84.5 159q0 34 33 67q52 -64 60 -76q77 -104 133 -104q12 0 26.5 8.5t14.5 20.5q0 34 -87.5 145t-116.5 111q-43 0 -70 -44.5t-27 -90.5zM11 264q0 101 42.5 163t136.5 88 q-28 74 -28 104q0 62 61 123t122 61q29 0 70 -15q-163 462 -163 567q0 80 41 130.5t119 50.5q131 0 325 -581q6 -17 8 -23q6 16 29 79.5t43.5 118.5t54 127.5t64.5 123t70.5 86.5t76.5 36q71 0 112 -49t41 -122q0 -108 -159 -550q61 -15 100.5 -46t58.5 -78t26 -93.5 t7 -110.5q0 -150 -47 -280t-132 -225t-211 -150t-278 -55q-111 0 -223 42q-149 57 -258 191.5t-109 286.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M785 528h207q-14 -158 -98.5 -248.5t-214.5 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-203q-5 64 -35.5 99t-81.5 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t40 -51.5t66 -18q95 0 109 139zM1497 528h206 q-14 -158 -98 -248.5t-214 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-204q-4 64 -35 99t-81 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t39.5 -51.5t65.5 -18q49 0 76.5 38t33.5 101zM1856 647q0 207 -15.5 307 t-60.5 161q-6 8 -13.5 14t-21.5 15t-16 11q-86 63 -697 63q-625 0 -710 -63q-5 -4 -17.5 -11.5t-21 -14t-14.5 -14.5q-45 -60 -60 -159.5t-15 -308.5q0 -208 15 -307.5t60 -160.5q6 -8 15 -15t20.5 -14t17.5 -12q44 -33 239.5 -49t470.5 -16q610 0 697 65q5 4 17 11t20.5 14 t13.5 16q46 60 61 159t15 309zM2048 1408v-1536h-2048v1536h2048z" /> +<glyph unicode="" d="M992 912v-496q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v496q0 112 -80 192t-192 80h-272v-1152q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v1344q0 14 9 23t23 9h464q135 0 249 -66.5t180.5 -180.5t66.5 -249zM1376 1376v-880q0 -135 -66.5 -249t-180.5 -180.5 t-249 -66.5h-464q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h160q14 0 23 -9t9 -23v-768h272q112 0 192 80t80 192v880q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" /> +<glyph unicode="" d="M1311 694v-114q0 -24 -13.5 -38t-37.5 -14h-202q-24 0 -38 14t-14 38v114q0 24 14 38t38 14h202q24 0 37.5 -14t13.5 -38zM821 464v250q0 53 -32.5 85.5t-85.5 32.5h-133q-68 0 -96 -52q-28 52 -96 52h-130q-53 0 -85.5 -32.5t-32.5 -85.5v-250q0 -22 21 -22h55 q22 0 22 22v230q0 24 13.5 38t38.5 14h94q24 0 38 -14t14 -38v-230q0 -22 21 -22h54q22 0 22 22v230q0 24 14 38t38 14h97q24 0 37.5 -14t13.5 -38v-230q0 -22 22 -22h55q21 0 21 22zM1410 560v154q0 53 -33 85.5t-86 32.5h-264q-53 0 -86 -32.5t-33 -85.5v-410 q0 -21 22 -21h55q21 0 21 21v180q31 -42 94 -42h191q53 0 86 32.5t33 85.5zM1536 1176v-1072q0 -96 -68 -164t-164 -68h-1072q-96 0 -164 68t-68 164v1072q0 96 68 164t164 68h1072q96 0 164 -68t68 -164z" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> <glyph unicode="" horiz-adv-x="1792" /> </font> </defs></svg>
\ No newline at end of file diff --git a/assets/fonts/fontawesome-webfont.ttf b/assets/fonts/fontawesome-webfont.ttf Binary files differindex e89738de..96a3639c 100755 --- a/assets/fonts/fontawesome-webfont.ttf +++ b/assets/fonts/fontawesome-webfont.ttf diff --git a/assets/fonts/fontawesome-webfont.woff b/assets/fonts/fontawesome-webfont.woff Binary files differindex 8c1748aa..628b6a52 100755 --- a/assets/fonts/fontawesome-webfont.woff +++ b/assets/fonts/fontawesome-webfont.woff diff --git a/assets/js/app.js b/assets/js/app.js index 20af61eb..812cd034 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -9,22 +9,30 @@ var Kanboard = (function() { e.preventDefault(); e.stopPropagation(); - $.get(e.target.getAttribute("href"), function(content) { + var link = e.target.getAttribute("href"); - $("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>'); + if (! link) { + link = e.target.getAttribute("data-href"); + } - $("#popover-container").click(function() { - $(this).remove(); - }); + if (link) { + $.get(link, function(content) { - $("#popover-content").click(function(e) { - e.stopPropagation(); - }); + $("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>'); - if (callback) { - callback(); - } - }); + $("#popover-container").click(function() { + $(this).remove(); + }); + + $("#popover-content").click(function(e) { + e.stopPropagation(); + }); + + if (callback) { + callback(); + } + }); + } }, // Return true if the page is visible @@ -74,21 +82,33 @@ Kanboard.Board = (function() { { // Drag and drop $(".column").sortable({ + delay: 300, + distance: 5, connectWith: ".column", placeholder: "draggable-placeholder", stop: function(event, ui) { - board_save(ui.item.attr('data-task-id')); + board_save( + ui.item.attr('data-task-id'), + ui.item.parent().attr("data-column-id"), + ui.item.index() + 1 + ); } }); // Assignee change $(".assignee-popover").click(Kanboard.Popover); + // Category change + $(".category-popover").click(Kanboard.Popover); + // Task edit popover $(".task-edit-popover").click(function(e) { Kanboard.Popover(e, Kanboard.Task.Init); }); + // Description popover + $(".task-description-popover").click(Kanboard.Popover); + // Redirect to the task details page $("[data-task-id]").each(function() { $(this).click(function() { @@ -112,30 +132,22 @@ Kanboard.Board = (function() { } // Save and refresh the board - function board_save(selected_task_id) + function board_save(taskId, columnId, position) { - var data = []; var boardSelector = $("#board"); var projectId = boardSelector.attr("data-project-id"); board_unload_events(); - $(".column").each(function() { - var columnId = $(this).attr("data-column-id"); - - $("#column-" + columnId + " .task-board").each(function(index) { - data.push({ - "task_id": parseInt($(this).attr("data-task-id")), - "position": index + 1, - "column_id": parseInt(columnId) - }); - }); - }); - $.ajax({ cache: false, url: "?controller=board&action=save&project_id=" + projectId, - data: {"positions": data, "csrf_token": boardSelector.attr("data-csrf-token"), "selected_task_id": selected_task_id}, + data: { + "task_id": taskId, + "column_id": columnId, + "position": position, + "csrf_token": boardSelector.attr("data-csrf-token"), + }, type: "POST", success: function(data) { $("#board").remove(); @@ -263,7 +275,7 @@ Kanboard.Project = (function() { // Initialization $(function() { - +//alert($(window).width()); if ($("#board").length) { Kanboard.Board.Init(); } diff --git a/assets/js/jquery.ui.touch-punch.min.js b/assets/js/jquery.ui.touch-punch.min.js index 31272ce6..d538812f 100644 --- a/assets/js/jquery.ui.touch-punch.min.js +++ b/assets/js/jquery.ui.touch-punch.min.js @@ -1,11 +1,3 @@ -/*! - * jQuery UI Touch Punch 0.2.3 - * - * Copyright 2011–2014, Dave Furfero - * Dual licensed under the MIT or GPL Version 2 licenses. - * - * Depends: - * jquery.ui.widget.js - * jquery.ui.mouse.js - */ -!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
\ No newline at end of file +(function(b){function c(a,b){if(!(1<a.originalEvent.touches.length)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null);a.target.dispatchEvent(d)}}var f=!1,g,h;b.support.touch="ontouchend"in document;b.support.mspointer=window.navigator.msPointerEnabled;if(b.support.touch||b.support.mspointer){var d=b.ui.mouse.prototype,k=d._mouseInit,l=d._mouseDestroy,e;d._touchStart= +function(a){!e&&this._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,f=!1,g=+new Date,c(a,"mouseover"),c(a,"mousemove"),c(a,"mousedown"))};d._touchMove=function(a){e&&(f=!0,c(a,"mousemove"))};d._touchEnd=function(a){e&&(h=+new Date,c(a,"mouseup"),c(a,"mouseout"),(!f||300>h-g)&&c(a,"click"),e=!1)};d._mouseInit=function(){b.support.mspointer&&(this.element[0].style.msTouchAction="none");this.element.bind({touchstart:b.proxy(this,"_touchStart"),touchmove:b.proxy(this,"_touchMove"),touchend:b.proxy(this, +"_touchEnd")});k.call(this)};d._mouseDestroy=function(){this.element.unbind({touchstart:b.proxy(this,"_touchStart"),touchmove:b.proxy(this,"_touchMove"),touchend:b.proxy(this,"_touchEnd")});l.call(this)}}})(jQuery); diff --git a/config.default.php b/config.default.php index 6206f37e..80f965dc 100644 --- a/config.default.php +++ b/config.default.php @@ -55,10 +55,14 @@ define('LDAP_PORT', 389); // By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification. define('LDAP_SSL_VERIFY', true); -// LDAP username to connect with. NULL for anonymous bind (by default). +// LDAP bind type: "anonymous", "user" (use the given user/password from the form) and "proxy" (a specific user to browse the LDAP directory) +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 define('LDAP_USERNAME', null); -// LDAP password to connect with. NULL for anonymous bind (by default). +// LDAP password to connect with. null for anonymous bind (by default). define('LDAP_PASSWORD', null); // LDAP account base, i.e. root of all user account @@ -102,3 +106,6 @@ define('REVERSE_PROXY_USER_HEADER', 'REMOTE_USER'); // Username of the admin, by default blank define('REVERSE_PROXY_DEFAULT_ADMIN', ''); + +// Default domain to use for setting the email address +define('REVERSE_PROXY_DEFAULT_DOMAIN', ''); diff --git a/docs/api-json-rpc.markdown b/docs/api-json-rpc.markdown index c06943bb..9f4240dc 100644 --- a/docs/api-json-rpc.markdown +++ b/docs/api-json-rpc.markdown @@ -93,17 +93,72 @@ Procedures ### createProject - Purpose: **Create a new project** -- Parameters: **name** (string) +- Parameters: + - **name** (string, required) - Result on success: **true** - Result on failure: **false** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "createProject", + "id": 1797076613, + "params": { + "name": "PHP client" + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1797076613, + "result": true +} +``` + ### getProjectById - Purpose: **Get project information** -- Parameters: **project_id** (integer) +- Parameters: + - **project_id** (integer, required) - Result on success: **project properties** - Result on failure: **null** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getProjectById", + "id": 226760253, + "params": { + "project_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 226760253, + "result": { + "id": "1", + "name": "API test", + "is_active": "1", + "token": "", + "last_modified": "1410263246", + "is_public": "0" + } +} +``` + ### getProjectByName - Purpose: **Get project information** @@ -111,6 +166,36 @@ Procedures - Result on success: **project properties** - Result on failure: **null** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getProjectByName", + "id": 1620253806, + "params": { + "name": "Test" + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1620253806, + "result": { + "id": "1", + "name": "Test", + "is_active": "1", + "token": "", + "last_modified": "0", + "is_public": "0" + } +} +``` + ### getAllProjects - Purpose: **Get all available projects** @@ -118,20 +203,233 @@ Procedures - Result on success: **List of projects** - Result on failure: **false** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAllProjects", + "id": 134982303 +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 134982303, + "result": [ + { + "id": "2", + "name": "PHP client", + "is_active": "1", + "token": "", + "last_modified": "0", + "is_public": "0" + }, + { + "id": "1", + "name": "Test", + "is_active": "1", + "token": "", + "last_modified": "0", + "is_public": "0" + } + ] +} +``` + ### updateProject - Purpose: **Update a project** -- Parameters: Key/value pair composed of the **id** (integer), **name** (string), **is_active** (integer, optional) +- Parameters: + - **id** (integer, required) + - **name** (string, required) + - **is_active** (integer, optional) + - **token** (string, optional) + - **is_public** (integer, optional) - Result on success: **true** - Result on failure: **false** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "updateProject", + "id": 1853996288, + "params": { + "id": 1, + "name": "PHP client update" + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1853996288, + "result": true +} +``` + ### removeProject - Purpose: **Remove a project** -- Parameters: **project_id** (integer) +- Parameters: + **project_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeProject", + "id": 46285125, + "params": { + "project_id": "2" + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 46285125, + "result": true +} +``` + +### enableProject + +- Purpose: **Enable a project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "enableProject", + "id": 1775494839, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1775494839, + "result": true +} +``` + +### disableProject + +- Purpose: **Disable a project** +- Parameters: + - **project_id** (integer, required) - Result on success: **true** - Result on failure: **false** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "disableProject", + "id": 1734202312, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1734202312, + "result": true +} +``` + +### enableProjectPublicAccess + +- Purpose: **Enable public access for a given project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "enableProjectPublicAccess", + "id": 103792571, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 103792571, + "result": true +} +``` + +### disableProjectPublicAccess + +- Purpose: **Disable public access for a given project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "disableProjectPublicAccess", + "id": 942472945, + "params": [ + "1" + ] +} +``` + +Response example: + +```json + + "jsonrpc": "2.0", + "id": 942472945, + "result": true +} +``` @@ -211,54 +509,344 @@ Procedures ### createTask - Purpose: **Create a new task** -- Parameters: Key/value pair composed of the **title** (string), **description** (string, optional), **color_id** (string), **project_id** (integer), **column_id** (integer), **owner_id** (integer, optional), **score** (integer, optional), **date_due** (integer, optional), **category_id** (integer, optional) +- Parameters: + - **title** (string, required) + - **project_id** (integer, required) + - **color_id** (string, optional) + - **column_id** (integer, optional) + - **description** (string, optional) + - **owner_id** (integer, optional) + - **creator_id** (integer, optional) + - **score** (integer, optional) + - **date_due**: ISO8601 format (string, optional) + - **category_id** (integer, optional) - Result on success: **true** - Result on failure: **false** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "createTask", + "id": 1176509098, + "params": { + "owner_id": 1, + "creator_id": 0, + "date_due": "", + "description": "", + "category_id": 0, + "score": 0, + "title": "Test", + "project_id": 1, + "color_id": "green", + "column_id": 2 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1176509098, + "result": true +} +``` + ### getTask - Purpose: **Get task information** -- Parameters: **task_id** (integer) +- Parameters: + - **task_id** (integer, required) - Result on success: **task properties** - Result on failure: **null** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getTask", + "id": 700738119, + "params": { + "task_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 700738119, + "result": { + "id": "1", + "title": "Task #1", + "description": "", + "date_creation": "1409963206", + "color_id": "blue", + "project_id": "1", + "column_id": "2", + "owner_id": "1", + "position": "1", + "is_active": "1", + "date_completed": null, + "score": "0", + "date_due": "0", + "category_id": "0", + "creator_id": "0", + "date_modification": "1409963206" + } +} +``` + ### getAllTasks - Purpose: **Get all available tasks** -- Parameters: **project_id** (integer) +- Parameters: + - **project_id** (integer, required) + - **status**: List of status id, the value 1 for active tasks and 0 for inactive (list, required) - Result on success: **List of tasks** - Result on failure: **false** +Request example to fetch all tasks on the board: + +```json +{ + "jsonrpc": "2.0", + "method": "getAllTasks", + "id": 133280317, + "params": { + "project_id": 1, + "status": [ + 1 + ] + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 133280317, + "result": [ + { + "id": "1", + "title": "Task #1", + "description": "", + "date_creation": "1409961789", + "color_id": "blue", + "project_id": "1", + "column_id": "2", + "owner_id": "1", + "position": "1", + "is_active": "1", + "date_completed": null, + "score": "0", + "date_due": "0", + "category_id": "0", + "creator_id": "0", + "date_modification": "1409961789" + }, + { + "id": "2", + "title": "Test", + "description": "", + "date_creation": "1409962115", + "color_id": "green", + "project_id": "1", + "column_id": "2", + "owner_id": "1", + "position": "2", + "is_active": "1", + "date_completed": null, + "score": "0", + "date_due": "0", + "category_id": "0", + "creator_id": "0", + "date_modification": "1409962115" + }, + ... + ] +} +``` + ### updateTask - Purpose: **Update a task** -- Parameters: Key/value pair composed of the **id** (integer), **title** (string), **description** (string, optional), **color_id** (string), **project_id** (integer), **column_id** (integer), **owner_id** (integer, optional), **score** (integer, optional), **date_due** (integer, optional), **category_id** (integer, optional) +- Parameters: + - **id** (integer, required) + - **title** (string, optional) + - **color_id** (string, optional) + - **project_id** (integer, optional) + - **column_id** (integer, optional) + - **description** (string, optional) + - **owner_id** (integer, optional) + - **creator_id** (integer, optional) + - **score** (integer, optional) + - **date_due**: ISO8601 format (string, optional) + - **category_id** (integer, optional) - Result on success: **true** - Result on failure: **false** +Request example to change the task color: + +```json +{ + "jsonrpc": "2.0", + "method": "updateTask", + "id": 1406803059, + "params": { + "id": 1, + "color_id": "blue" + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1406803059, + "result": true +} +``` + ### openTask - Purpose: **Set a task to the status open** -- Parameters: **task_id** (integer) +- Parameters: + - **task_id** (integer, required) - Result on success: **true** - Result on failure: **false** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "openTask", + "id": 1888531925, + "params": { + "task_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1888531925, + "result": true +} +``` + ### closeTask - Purpose: **Set a task to the status close** -- Parameters: **task_id** (integer) +- Parameters: + - **task_id** (integer, required) - Result on success: **true** - Result on failure: **false** +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "closeTask", + "id": 1654396960, + "params": { + "task_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1654396960, + "result": true +} +``` + ### removeTask - Purpose: **Remove a task** -- Parameters: **task_id** (integer) +- Parameters: + - **task_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeTask", + "id": 1423501287, + "params": { + "task_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1423501287, + "result": true +} +``` + +### moveTaskPosition + +- Purpose: **Move a task to another column or another position** +- Parameters: + - **project_id** (integer, required) + - **task_id** (integer, required) + - **column_id** (integer, required) + - **position** (integer, required) - Result on success: **true** - Result on failure: **false** +Request example: +```json +{ + "jsonrpc": "2.0", + "method": "moveTaskPosition", + "id": 117211800, + "params": { + "project_id": 1, + "task_id": 1, + "column_id": 2, + "position": 1 + } +} +``` +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 117211800, + "result": true +} +``` ### createUser diff --git a/docs/automatic-actions.markdown b/docs/automatic-actions.markdown index e903e0b1..6d46b053 100644 --- a/docs/automatic-actions.markdown +++ b/docs/automatic-actions.markdown @@ -33,6 +33,7 @@ List of available actions - Assign the task to a specific user - Assign the task to the person who does the action - Duplicate the task to another project +- Move the task to another project - Assign a color to a specific user - Assign automatically a color based on a category - Assign automatically a category based on a color @@ -68,6 +69,14 @@ Let's say we have two projects "Customer orders" and "Production", once the orde - Choose the action: **Duplicate the task to another project** - Define the action parameters: **Column = Validated** and **Project = Production** +### When a task is moved to the last column, move the exact same task to another project + +Let's say we have two projects "Ideas" and "Development", once the idea is validated, swap it to the "Development" project. + +- Choose the event: **Move a task to another column** +- Choose the action: **Move the task to another project** +- Define the action parameters: **Column = Validated** and **Project = Development** + ### I want to assign automatically a color to the user Bob - Choose the event: **Task creation** diff --git a/docs/ldap-authentication.markdown b/docs/ldap-authentication.markdown index 989ee24d..0c4a5720 100644 --- a/docs/ldap-authentication.markdown +++ b/docs/ldap-authentication.markdown @@ -46,10 +46,14 @@ define('LDAP_PORT', 389); // By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification. define('LDAP_SSL_VERIFY', true); -// LDAP username to connect with. NULL for anonymous bind (by default). +// LDAP bind type: "anonymous", "user" (use the given user/password from the form) and "proxy" (a specific user to browse the LDAP directory) +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 like that %s@kanboard.local define('LDAP_USERNAME', null); -// LDAP password to connect with. NULL for anonymous bind (by default). +// LDAP password to connect with. null for anonymous bind (by default). define('LDAP_PASSWORD', null); // LDAP account base, i.e. root of all user account @@ -68,9 +72,58 @@ define('LDAP_ACCOUNT_FULLNAME', 'displayname'); define('LDAP_ACCOUNT_EMAIL', 'mail'); ``` +### LDAP bind type + +There is 3 possible ways to browse the LDAP directory: + +#### Anonymous browsing + +```php +define('LDAP_BIND_TYPE', 'anonymous'); +define('LDAP_USERNAME', null); +define('LDAP_PASSWORD', null); +``` + +This is the default value but some LDAP servers don't allow that. + +#### Proxy user + +A specific user is used to browse the LDAP directory. +By example, Novell eDirectory use that method. + +```php +define('LDAP_BIND_TYPE', 'proxy'); +define('LDAP_USERNAME', 'my proxy user'); +define('LDAP_PASSWORD', 'my proxy password'); +``` + +#### User credentials + +This method use the credentials provided by the end-user. +By example, Microsoft Active Directory doesn't allow anonymous browsing by default and if you don't want to use a proxy user you can use this method. + +```php +define('LDAP_BIND_TYPE', 'user'); +define('LDAP_USERNAME', '%s@mydomain.local'); +define('LDAP_PASSWORD', null); +``` + +Here, the `LDAP_USERNAME` is use to define a replacement pattern: + +```php +define('LDAP_USERNAME', '%s@mydomain.local'); + +// Another way to do the same: + +define('LDAP_USERNAME', 'MYDOMAIN\\%s'); +``` + ### Example for Microsoft Active Directory Let's say we have a domain `KANBOARD` (kanboard.local) and the primary controller is `myserver.kanboard.local`. +Microsoft Active Directory doesn't allow anonymous binding by default. + +First example with a proxy user: ```php <?php @@ -78,7 +131,8 @@ Let's say we have a domain `KANBOARD` (kanboard.local) and the primary controlle // Enable LDAP authentication (false by default) define('LDAP_AUTH', true); -// Set credentials for be allow to browse the LDAP directory +// Credentials to be allowed to browse the LDAP directory +define('LDAP_BIND_TYPE', 'proxy'); define('LDAP_USERNAME', 'administrator@kanboard.local'); define('LDAP_PASSWORD', 'my super secret password'); @@ -92,10 +146,35 @@ define('LDAP_ACCOUNT_FULLNAME', 'displayname'); define('LDAP_ACCOUNT_EMAIL', 'mail'); ``` +Another way with no proxy user: + +```php +<?php + +// Enable LDAP authentication (false by default) +define('LDAP_AUTH', true); + +// Credentials to be allowed to browse the LDAP directory +define('LDAP_BIND_TYPE', 'user'); +define('LDAP_USERNAME', '%s@kanboard.local'); // or 'KANBOARD\\%s' +define('LDAP_PASSWORD', null); + +// LDAP server hostname +define('LDAP_SERVER', 'myserver.kanboard.local'); + +// LDAP properties +define('LDAP_ACCOUNT_BASE', 'CN=Users,DC=kanboard,DC=local'); +define('LDAP_USER_PATTERN', '(&(objectClass=user)(sAMAccountName=%s))'); +define('LDAP_ACCOUNT_FULLNAME', 'displayname'); +define('LDAP_ACCOUNT_EMAIL', 'mail'); +``` + ### Example for OpenLDAP Here, our LDAP server is `myserver.example.com` and all users are stored in the hierarchy `ou=People,dc=example,dc=com`. +For this example with use the anonymous binding. + ```php <?php diff --git a/docs/reverse-proxy-authentication.markdown b/docs/reverse-proxy-authentication.markdown index c3243208..446adcb8 100644 --- a/docs/reverse-proxy-authentication.markdown +++ b/docs/reverse-proxy-authentication.markdown @@ -42,4 +42,9 @@ define('REVERSE_PROXY_USER_HEADER', 'REMOTE_USER'); // you should want to have a bootstrap admin user. define('REVERSE_PROXY_DEFAULT_ADMIN', 'myadmin'); +// The default domain to assume for the email address. +// In case the username is not an email address, it +// will be updated automatically as USER@mydomain.com +define('REVERSE_PROXY_DEFAULT_DOMAIN', 'mydomain.com'); + ``` diff --git a/docs/windows-iis-installation.markdown b/docs/windows-iis-installation.markdown new file mode 100644 index 00000000..f49c3c54 --- /dev/null +++ b/docs/windows-iis-installation.markdown @@ -0,0 +1,46 @@ +How to install Kanboard on Windows Server? +========================================== + +Windows 2008/2012 with IIS +--------------------------- + +### PHP installation + +- Install IIS on your server (Add new role and don't forget to enable CGI/FastCGI) +- Install PHP by following the official documentation: + - [Microsoft IIS 5.1 and IIS 6.0](http://php.net/manual/en/install.windows.iis6.php) + - [Microsoft IIS 7.0 and later](http://php.net/manual/en/install.windows.iis7.php) + - [PHP for Windows is available here](http://windows.php.net/download/) + +After the installation check if PHP runs correctly: + +Go the IIS document root `C:\inetpub\wwwroot` and create a file `phpinfo.php`: + +```php +<?php + +phpinfo(); + +?> +``` + +Open a browser at `http://localhost/phpinfo.php` and you should see the current PHP settings. +If you got an error 500, something is not correctly done in your installation. + +Notes: + +- If you use PHP < 5.4, you have to enable the short tags in your php.ini +- Don't forget to enable the required php extensions: `pdo_sqlite` and `mbstring` +- If you got an error about "the library MSVCP110.dll is missing", you probably need to download the Visual C++ Redistributable for Visual Studio from the Microsoft website. + +### Kanboard installation + +- Download the zip file +- Uncompress the archive in `C:\inetpub\wwwroot\kanboard` by example +- Make sure the directory `data` is writable by the IIS user +- You are done, open your web browser to use Kanboard + +### Tested configuration + +- Windows 2008 R2 Standard Edition / IIS 7.5 / PHP 5.5.16 +- Windows 2012 Standard Edition / IIS 8.5 / PHP 5.3.29 diff --git a/jsonrpc.php b/jsonrpc.php index c645650f..7c885334 100644 --- a/jsonrpc.php +++ b/jsonrpc.php @@ -41,6 +41,7 @@ if ($language !== 'en_US') Translator::load($language); $server = new Server; $server->authentication(array('jsonrpc' => $config->get('api_token'))); + /** * Project procedures */ @@ -62,7 +63,22 @@ $server->register('getAllProjects', function() use ($project) { return $project->getAll(); }); -$server->register('updateProject', function(array $values) use ($project) { +$server->register('updateProject', function($id, $name, $is_active = null, $is_public = null, $token = null) use ($project) { + + $values = array( + 'id' => $id, + 'name' => $name, + 'is_active' => $is_active, + 'is_public' => $is_public, + 'token' => $token, + ); + + foreach ($values as $key => $value) { + if (is_null($value)) { + unset($values[$key]); + } + } + list($valid,) = $project->validateModification($values); return $valid && $project->update($values); }); @@ -71,6 +87,26 @@ $server->register('removeProject', function($project_id) use ($project) { return $project->remove($project_id); }); +$server->register('enableProject', function($project_id) use ($project) { + return $project->enable($project_id); +}); + +$server->register('disableProject', function($project_id) use ($project) { + return $project->disable($project_id); +}); + +$server->register('enableProjectPublicAccess', function($project_id) use ($project) { + return $project->enablePublicAccess($project_id); +}); + +$server->register('disableProjectPublicAccess', function($project_id) use ($project) { + return $project->disablePublicAccess($project_id); +}); + + +/** + * Board procedures + */ $server->register('getBoard', function($project_id) use ($board) { return $board->get($project_id); }); @@ -116,7 +152,21 @@ $server->register('allowUser', function($project_id, $user_id) use ($project) { /** * Task procedures */ -$server->register('createTask', function(array $values) use ($task) { +$server->register('createTask', function($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0, $date_due = '', $description = '', $category_id = 0, $score = 0) use ($task) { + + $values = array( + 'title' => $title, + 'project_id' => $project_id, + 'color_id' => $color_id, + 'column_id' => $column_id, + 'owner_id' => $owner_id, + 'creator_id' => $creator_id, + 'date_due' => $date_due, + 'description' => $description, + 'category_id' => $category_id, + 'score' => $score, + ); + list($valid,) = $task->validateCreation($values); return $valid && $task->create($values) !== false; }); @@ -129,8 +179,29 @@ $server->register('getAllTasks', function($project_id, array $status) use ($task return $task->getAll($project_id, $status); }); -$server->register('updateTask', function($values) use ($task) { - list($valid,) = $task->validateModification($values); +$server->register('updateTask', function($id, $title = null, $project_id = null, $color_id = null, $column_id = null, $owner_id = null, $creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null) use ($task) { + + $values = array( + 'id' => $id, + 'title' => $title, + 'project_id' => $project_id, + 'color_id' => $color_id, + 'column_id' => $column_id, + 'owner_id' => $owner_id, + 'creator_id' => $creator_id, + 'date_due' => $date_due, + 'description' => $description, + 'category_id' => $category_id, + 'score' => $score, + ); + + foreach ($values as $key => $value) { + if (is_null($value)) { + unset($values[$key]); + } + } + + list($valid) = $task->validateModification($values); return $valid && $task->update($values); }); @@ -146,6 +217,10 @@ $server->register('removeTask', function($task_id) use ($task) { return $task->remove($task_id); }); +$server->register('moveTaskPosition', function($project_id, $task_id, $column_id, $position) use ($task) { + return $task->movePosition($project_id, $task_id, $column_id, $position); +}); + /** * User procedures diff --git a/scripts/create-random-tasks.php b/scripts/create-random-tasks.php new file mode 100755 index 00000000..4d665178 --- /dev/null +++ b/scripts/create-random-tasks.php @@ -0,0 +1,26 @@ +#!/usr/bin/env php +<?php + +require __DIR__.'/../app/common.php'; + +use Model\Task; + +$task_per_column = 250; +$taskModel = new Task($registry); + +foreach (array(1, 2, 3, 4) as $column_id) { + + for ($i = 1; $i <= $task_per_column; $i++) { + + $task = array( + 'title' => 'Task #'.$i.'-'.$column_id, + 'project_id' => 1, + 'column_id' => $column_id, + 'owner_id' => rand(0, 1), + 'color_id' => rand(0, 1) === 0 ? 'green' : 'purple', + 'score' => rand(0, 21), + ); + + $taskModel->create($task); + } +} diff --git a/scripts/sync-locales.php b/scripts/sync-locales.php new file mode 100755 index 00000000..00d6c897 --- /dev/null +++ b/scripts/sync-locales.php @@ -0,0 +1,43 @@ +#!/usr/bin/env php +<?php + +$reference_lang = 'fr_FR'; +$reference_file = 'app/Locales/'.$reference_lang.'/translations.php'; +$reference = include $reference_file; + + +function update_missing_locales(array $reference, $outdated_file) +{ + $outdated = include $outdated_file; + + $output = '<?php'.PHP_EOL.PHP_EOL; + $output .= 'return array('.PHP_EOL; + + foreach ($reference as $key => $value) { + + if (isset($outdated[$key])) { + //$output .= " '".str_replace("'", "\'", $key)."' => '".str_replace("'", "\'", $value)."',\n"; + $output .= " '".str_replace("'", "\'", $key)."' => '".str_replace("'", "\'", $outdated[$key])."',\n"; + } + else { + //$output .= " // '".str_replace("'", "\'", $key)."' => '".str_replace("'", "\'", $value)."',\n"; + $output .= " // '".str_replace("'", "\'", $key)."' => '',\n"; + } + } + + $output .= ");\n"; + return $output; +} + + +foreach (new DirectoryIterator('app/Locales') as $fileInfo) { + + if (! $fileInfo->isDot() && $fileInfo->isDir() && $fileInfo->getFilename() !== $reference_lang) { + + $filename = 'app/Locales/'.$fileInfo->getFilename().'/translations.php'; + + echo $fileInfo->getFilename().' ('.$filename.')'.PHP_EOL; + + file_put_contents($filename, update_missing_locales($reference, $filename)); + } +} diff --git a/tests/functionals/ApiTest.php b/tests/functionals/ApiTest.php index 6e23fd52..bc0bbe42 100644 --- a/tests/functionals/ApiTest.php +++ b/tests/functionals/ApiTest.php @@ -11,7 +11,7 @@ class Api extends PHPUnit_Framework_TestCase public function setUp() { - $this->client = new JsonRPC\Client(self::URL, 5, true); + $this->client = new JsonRPC\Client(self::URL); $this->client->authentication('jsonrpc', self::KEY); $pdo = new PDO('sqlite:data/db.sqlite'); @@ -24,7 +24,7 @@ class Api extends PHPUnit_Framework_TestCase if ($projects) { foreach ($projects as $project) { - $this->client->removeProject($project['id']); + $this->assertTrue($this->client->removeProject($project['id'])); } } } @@ -45,13 +45,13 @@ class Api extends PHPUnit_Framework_TestCase { $project = $this->client->getProjectById(1); $this->assertNotEmpty($project); - $this->assertTrue($this->client->updateProject(array('id' => 1, 'name' => 'API test 2', 'is_active' => 0))); + $this->assertTrue($this->client->execute('updateProject', array('id' => 1, 'name' => 'API test 2', 'is_active' => 0))); $project = $this->client->getProjectById(1); $this->assertEquals('API test 2', $project['name']); $this->assertEquals(0, $project['is_active']); - $this->assertTrue($this->client->updateProject(array('id' => 1, 'name' => 'API test', 'is_active' => 1))); + $this->assertTrue($this->client->execute('updateProject', array('id' => 1, 'name' => 'API test', 'is_active' => 1))); $project = $this->client->getProjectById(1); $this->assertEquals('API test', $project['name']); @@ -132,7 +132,7 @@ class Api extends PHPUnit_Framework_TestCase 'column_id' => 2, ); - $this->assertTrue($this->client->createTask($task)); + $this->assertTrue($this->client->execute('createTask', $task)); $task = array( 'title' => 'Task #1', @@ -140,7 +140,7 @@ class Api extends PHPUnit_Framework_TestCase 'owner_id' => 1, ); - $this->assertFalse($this->client->createTask($task)); + $this->assertNull($this->client->createTask($task)); } public function testGetTask() @@ -175,7 +175,7 @@ class Api extends PHPUnit_Framework_TestCase $task['description'] = 'test'; $task['date_due'] = ''; - $this->assertTrue($this->client->updateTask($task)); + $this->assertTrue($this->client->execute('updateTask', $task)); } public function testRemoveTask() @@ -278,7 +278,7 @@ class Api extends PHPUnit_Framework_TestCase 'column_id' => 1, ); - $this->assertTrue($this->client->createTask($task)); + $this->assertTrue($this->client->execute('createTask', $task)); $comment = array( 'task_id' => 1, @@ -396,23 +396,26 @@ class Api extends PHPUnit_Framework_TestCase $this->assertTrue(is_array($subtasks)); $this->assertEquals(1, count($subtasks)); } -/* - public function testAutomaticActions() + + public function testMoveTaskPosition() { $task = array( - 'title' => 'Task #1', + 'title' => 'Task to move', 'color_id' => 'blue', - 'owner_id' => 0, + 'owner_id' => 1, 'project_id' => 1, 'column_id' => 1, ); - $this->assertTrue($this->client->createTask($task)); + $this->assertTrue($this->client->execute('createTask', $task)); - $tasks = $this->client->getAllTasks(1, array(1)); - $task = $tasks[count($tasks) - 1]; - $task['column_id'] = 3; + $this->assertTrue($this->client->moveTaskPosition(1, 1, 3, 1)); - $this->assertTrue($this->client->updateTask($task)); - }*/ + $task = $this->client->getTask(1); + + $this->assertNotFalse($task); + $this->assertTrue(is_array($task)); + $this->assertEquals(1, $task['position']); + $this->assertEquals(3, $task['column_id']); + } } diff --git a/tests/units/ActionTaskMoveAnotherProjectTest.php b/tests/units/ActionTaskMoveAnotherProjectTest.php new file mode 100644 index 00000000..e6a938de --- /dev/null +++ b/tests/units/ActionTaskMoveAnotherProjectTest.php @@ -0,0 +1,84 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Model\Task; +use Model\Project; + +class ActionTaskMoveAnotherProject extends Base +{ + public function testBadProject() + { + $action = new Action\TaskMoveAnotherProject(3, new Task($this->registry)); + $action->setParam('column_id', 5); + + $event = array( + 'project_id' => 2, + 'task_id' => 3, + 'column_id' => 5, + ); + + $this->assertFalse($action->isExecutable($event)); + $this->assertFalse($action->execute($event)); + } + + public function testBadColumn() + { + $action = new Action\TaskMoveAnotherProject(3, new Task($this->registry)); + $action->setParam('column_id', 5); + + $event = array( + 'project_id' => 3, + 'task_id' => 3, + 'column_id' => 3, + ); + + $this->assertFalse($action->execute($event)); + } + + public function testExecute() + { + $action = new Action\TaskMoveAnotherProject(1, new Task($this->registry)); + + // We create a task in the first column + $t = new Task($this->registry); + $p = new Project($this->registry); + $this->assertEquals(1, $p->create(array('name' => 'project 1'))); + $this->assertEquals(2, $p->create(array('name' => 'project 2'))); + $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1))); + + // We create an event to move the task to the 2nd column + $event = array( + 'project_id' => 1, + 'task_id' => 1, + 'column_id' => 2, + ); + + // Our event should NOT be executed because we define the same project + $action->setParam('column_id', 2); + $action->setParam('project_id', 1); + $this->assertFalse($action->execute($event)); + + // Our task should be assigned to the project 1 + $task = $t->getById(1); + $this->assertNotEmpty($task); + $this->assertEquals(1, $task['project_id']); + + // We create an event to move the task to the 2nd column + $event = array( + 'project_id' => 1, + 'task_id' => 1, + 'column_id' => 2, + ); + + // Our event should be executed because we define a different project + $action->setParam('column_id', 2); + $action->setParam('project_id', 2); + $this->assertTrue($action->execute($event)); + + // Our task should be assigned to the project 2 + $task = $t->getById(1); + $this->assertNotEmpty($task); + $this->assertEquals(2, $task['project_id']); + } +} diff --git a/tests/units/ActionTest.php b/tests/units/ActionTest.php index 8108767d..b07af992 100644 --- a/tests/units/ActionTest.php +++ b/tests/units/ActionTest.php @@ -9,7 +9,7 @@ use Model\Task; use Model\Category; class ActionTest extends Base -{ +{/* public function testFetchActions() { $action = new Action($this->registry); @@ -84,7 +84,7 @@ class ActionTest extends Base $this->assertEquals(1, $t1['column_id']); // We move our task - $task->move(1, 4, 1); + $task->movePosition(1, 1, 4, 1); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_MOVE_COLUMN)); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_UPDATE)); @@ -94,7 +94,7 @@ class ActionTest extends Base $this->assertEquals(4, $t1['column_id']); $this->assertEquals(0, $t1['is_active']); } - +*/ public function testEventMovePosition() { $task = new Task($this->registry); @@ -140,35 +140,47 @@ class ActionTest extends Base $this->assertTrue($this->registry->event->hasListener(Task::EVENT_MOVE_POSITION, 'Action\TaskAssignColorCategory')); - // Our task should have the color red and position=0 + // Our task should have the color red and position=1 $t1 = $task->getById(1); - $this->assertEquals(0, $t1['position']); + $this->assertEquals(1, $t1['position']); $this->assertEquals(1, $t1['is_active']); $this->assertEquals('red', $t1['color_id']); $t1 = $task->getById(2); - $this->assertEquals(1, $t1['position']); + $this->assertEquals(2, $t1['position']); $this->assertEquals(1, $t1['is_active']); $this->assertEquals('yellow', $t1['color_id']); // We move our tasks - $task->move(1, 1, 1); // task #1 to position 1 - $task->move(2, 1, 0); // task #2 to position 0 + $this->assertTrue($task->movePosition(1, 1, 1, 10)); // task #1 to the end of the column + $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_MOVE_POSITION)); + + $t1 = $task->getById(1); + $this->assertEquals(2, $t1['position']); + $this->assertEquals(1, $t1['is_active']); + $this->assertEquals('green', $t1['color_id']); + $t1 = $task->getById(2); + $this->assertEquals(1, $t1['position']); + $this->assertEquals(1, $t1['is_active']); + $this->assertEquals('yellow', $t1['color_id']); + + $this->registry->event->clearTriggeredEvents(); + $this->assertTrue($task->movePosition(1, 2, 1, 44)); // task #2 to position 1 $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_MOVE_POSITION)); + $this->assertEquals('Action\TaskAssignColorCategory', $this->registry->event->getLastListenerExecuted()); - // Both tasks should be green $t1 = $task->getById(1); $this->assertEquals(1, $t1['position']); $this->assertEquals(1, $t1['is_active']); $this->assertEquals('green', $t1['color_id']); $t1 = $task->getById(2); - $this->assertEquals(0, $t1['position']); + $this->assertEquals(2, $t1['position']); $this->assertEquals(1, $t1['is_active']); $this->assertEquals('green', $t1['color_id']); } - +/* public function testExecuteMultipleActions() { $task = new Task($this->registry); @@ -223,7 +235,7 @@ class ActionTest extends Base $this->assertEquals(1, $t1['project_id']); // We move our task - $task->move(1, 4, 1); + $task->movePosition(1, 1, 4, 1); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_CLOSE)); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_MOVE_COLUMN)); @@ -240,5 +252,5 @@ class ActionTest extends Base $this->assertEquals(1, $t2['is_active']); $this->assertEquals(2, $t2['project_id']); $this->assertEquals('unit_test', $t2['title']); - } + }*/ } diff --git a/tests/units/Base.php b/tests/units/Base.php index 0fc0f99e..c566ce9a 100644 --- a/tests/units/Base.php +++ b/tests/units/Base.php @@ -28,7 +28,9 @@ require_once __DIR__.'/../../app/Core/Tool.php'; require_once __DIR__.'/../../app/Core/Listener.php'; require_once __DIR__.'/../../app/Core/Event.php'; require_once __DIR__.'/../../app/Core/Translator.php'; +require_once __DIR__.'/../../app/Core/Template.php'; require_once __DIR__.'/../../app/translator.php'; +require_once __DIR__.'/../../app/helpers.php'; require_once __DIR__.'/../../app/Model/Base.php'; require_once __DIR__.'/../../app/Model/Task.php'; @@ -39,6 +41,12 @@ require_once __DIR__.'/../../app/Model/User.php'; require_once __DIR__.'/../../app/Model/Board.php'; require_once __DIR__.'/../../app/Model/Action.php'; require_once __DIR__.'/../../app/Model/Category.php'; +require_once __DIR__.'/../../app/Model/SubTask.php'; +require_once __DIR__.'/../../app/Model/File.php'; +require_once __DIR__.'/../../app/Model/BaseHistory.php'; +require_once __DIR__.'/../../app/Model/TaskHistory.php'; +require_once __DIR__.'/../../app/Model/SubtaskHistory.php'; +require_once __DIR__.'/../../app/Model/CommentHistory.php'; require_once __DIR__.'/../../app/Action/Base.php'; require_once __DIR__.'/../../app/Action/TaskClose.php'; @@ -47,11 +55,14 @@ require_once __DIR__.'/../../app/Action/TaskAssignColorUser.php'; require_once __DIR__.'/../../app/Action/TaskAssignColorCategory.php'; require_once __DIR__.'/../../app/Action/TaskAssignCurrentUser.php'; require_once __DIR__.'/../../app/Action/TaskDuplicateAnotherProject.php'; +require_once __DIR__.'/../../app/Action/TaskMoveAnotherProject.php'; abstract class Base extends PHPUnit_Framework_TestCase { public function setUp() { + date_default_timezone_set('UTC'); + $this->registry = new \Core\Registry; $this->registry->db = $this->getDbConnection(); $this->registry->event = new \Core\Event; diff --git a/tests/units/ProjectTest.php b/tests/units/ProjectTest.php index cb7aee36..95894172 100644 --- a/tests/units/ProjectTest.php +++ b/tests/units/ProjectTest.php @@ -15,7 +15,81 @@ class ProjectTest extends Base $p = new Project($this->registry); $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - $this->assertNotEmpty($p->getById(1)); + + $project = $p->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals(1, $project['is_active']); + $this->assertEquals(0, $project['is_public']); + $this->assertEmpty($project['token']); + } + + public function testRemove() + { + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertTrue($p->remove(1)); + $this->assertFalse($p->remove(1234)); + } + + public function testEnable() + { + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertTrue($p->disable(1)); + + $project = $p->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals(0, $project['is_active']); + + $this->assertFalse($p->disable(1111)); + } + + public function testDisable() + { + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertTrue($p->disable(1)); + $this->assertTrue($p->enable(1)); + + $project = $p->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals(1, $project['is_active']); + + $this->assertFalse($p->enable(1234567)); + } + + public function testEnablePublicAccess() + { + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertTrue($p->enablePublicAccess(1)); + + $project = $p->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals(1, $project['is_public']); + $this->assertNotEmpty($project['token']); + + $this->assertFalse($p->enablePublicAccess(123)); + } + + public function testDisablePublicAccess() + { + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertTrue($p->enablePublicAccess(1)); + $this->assertTrue($p->disablePublicAccess(1)); + + $project = $p->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals(0, $project['is_public']); + $this->assertEmpty($project['token']); + + $this->assertFalse($p->disablePublicAccess(123)); } public function testAllowEverybody() @@ -66,8 +140,8 @@ class ProjectTest extends Base // We create a project $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - // We revoke our admin user - $this->assertTrue($p->revokeUser(1, 1)); + // We revoke our admin user (not existing row) + $this->assertFalse($p->revokeUser(1, 1)); // We should have nobody in the users list $this->assertEmpty($p->getAllowedUsers(1)); diff --git a/tests/units/SubtaskTest.php b/tests/units/SubtaskTest.php new file mode 100644 index 00000000..a74ee60a --- /dev/null +++ b/tests/units/SubtaskTest.php @@ -0,0 +1,56 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Model\Task; +use Model\SubTask; +use Model\Project; +use Model\Category; +use Model\User; + +class SubTaskTest extends Base +{ + public function testDuplicate() + { + $t = new Task($this->registry); + $s = new SubTask($this->registry); + $p = new Project($this->registry); + + // We create a project + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + + // We create 2 tasks + $this->assertEquals(1, $t->create(array('title' => 'test 1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1))); + $this->assertEquals(2, $t->create(array('title' => 'test 2', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 0))); + + // We create many subtasks for the first task + $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1, 'time_estimated' => 5, 'time_spent' => 3, 'status' => 1))); + $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1, 'time_estimated' => 0, 'time_spent' => 0, 'status' => 2, 'user_id' => 1))); + + // We duplicate our subtasks + $this->assertTrue($s->duplicate(1, 2)); + $subtasks = $s->getAll(2); + + $this->assertNotFalse($subtasks); + $this->assertNotEmpty($subtasks); + $this->assertEquals(2, count($subtasks)); + + $this->assertEquals('subtask #1', $subtasks[0]['title']); + $this->assertEquals('subtask #2', $subtasks[1]['title']); + + $this->assertEquals(2, $subtasks[0]['task_id']); + $this->assertEquals(2, $subtasks[1]['task_id']); + + $this->assertEquals(5, $subtasks[0]['time_estimated']); + $this->assertEquals(0, $subtasks[1]['time_estimated']); + + $this->assertEquals(0, $subtasks[0]['time_spent']); + $this->assertEquals(0, $subtasks[1]['time_spent']); + + $this->assertEquals(0, $subtasks[0]['status']); + $this->assertEquals(0, $subtasks[1]['status']); + + $this->assertEquals(0, $subtasks[0]['user_id']); + $this->assertEquals(0, $subtasks[1]['user_id']); + } +} diff --git a/tests/units/TaskHistoryTest.php b/tests/units/TaskHistoryTest.php new file mode 100644 index 00000000..0d24be58 --- /dev/null +++ b/tests/units/TaskHistoryTest.php @@ -0,0 +1,98 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Model\Task; +use Model\TaskHistory; +use Model\Project; + +class TaskHistoryTest extends Base +{ + public function testCreation() + { + $e = new TaskHistory($this->registry); + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); + $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1))); + + $this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE)); + $this->assertTrue($e->create(1, 2, 1, Task::EVENT_UPDATE)); + $this->assertFalse($e->create(1, 1, 0, Task::EVENT_OPEN)); + + $events = $e->getAllByProjectId(1); + + $this->assertNotEmpty($events); + $this->assertTrue(is_array($events)); + $this->assertEquals(2, count($events)); + $this->assertEquals(time(), $events[0]['date_creation']); + $this->assertEquals(Task::EVENT_UPDATE, $events[0]['event_name']); + $this->assertEquals(Task::EVENT_CLOSE, $events[1]['event_name']); + } + + public function testFetchAllContent() + { + $e = new TaskHistory($this->registry); + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); + + $nb_events = 80; + + for ($i = 0; $i < $nb_events; $i++) { + $this->assertTrue($e->create(1, 1, 1, Task::EVENT_UPDATE)); + } + + $events = $e->getAllContentByProjectId(1); + + $this->assertNotEmpty($events); + $this->assertTrue(is_array($events)); + $this->assertEquals(50, count($events)); + $this->assertEquals('admin', $events[0]['author']); + $this->assertNotEmpty($events[0]['event_title']); + $this->assertNotEmpty($events[0]['event_content']); + } + + public function testCleanup() + { + $e = new TaskHistory($this->registry); + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); + + $max = 15; + $nb_events = 100; + + for ($i = 0; $i < $nb_events; $i++) { + $this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE)); + } + + $this->assertEquals($nb_events, $this->registry->db->table('task_has_events')->count()); + $e->cleanup($max); + + $events = $e->getAllByProjectId(1); + + $this->assertNotEmpty($events); + $this->assertTrue(is_array($events)); + $this->assertEquals($max, count($events)); + $this->assertEquals(100, $events[0]['id']); + $this->assertEquals(99, $events[1]['id']); + $this->assertEquals(86, $events[14]['id']); + + // Cleanup during task creation + + $nb_events = TaskHistory::MAX_EVENTS + 10; + + for ($i = 0; $i < $nb_events; $i++) { + $this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE)); + } + + $this->assertEquals(TaskHistory::MAX_EVENTS, $this->registry->db->table('task_has_events')->count()); + } +} diff --git a/tests/units/TaskTest.php b/tests/units/TaskTest.php index 1ea03fca..c9468efd 100644 --- a/tests/units/TaskTest.php +++ b/tests/units/TaskTest.php @@ -5,9 +5,306 @@ require_once __DIR__.'/Base.php'; use Model\Task; use Model\Project; use Model\Category; +use Model\User; class TaskTest extends Base { + public function testCreation() + { + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); + + $task = $t->getById(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals('yellow', $task['color_id']); + $this->assertEquals(time(), $task['date_creation']); + $this->assertEquals(time(), $task['date_modification']); + + $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1))); + + $task = $t->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(2, $task['position']); + $this->assertEquals(time(), $task['date_creation']); + $this->assertEquals(time(), $task['date_modification']); + } + + public function testRemove() + { + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); + + $this->assertTrue($t->remove(1)); + $this->assertFalse($t->remove(1234)); + } + + public function testMoveTaskWithBadPreviousPosition() + { + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $this->registry->db->table('tasks')->insert(array('title' => 'A', 'column_id' => 1, 'project_id' => 1, 'position' => 1))); + + // Both tasks have the same position + $this->assertEquals(2, $this->registry->db->table('tasks')->insert(array('title' => 'B', 'column_id' => 2, 'project_id' => 1, 'position' => 1))); + $this->assertEquals(3, $this->registry->db->table('tasks')->insert(array('title' => 'C', 'column_id' => 2, 'project_id' => 1, 'position' => 1))); + + // Move the first column to the last position of the 2nd column + $this->assertTrue($t->movePosition(1, 1, 2, 3)); + + // Check tasks position + $task = $t->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(1, $task['position']); + + $task = $t->getById(3); + $this->assertEquals(3, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(2, $task['position']); + + $task = $t->getById(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(3, $task['position']); + } + + public function testMoveTaskTop() + { + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(3, $t->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(4, $t->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 1))); + + // Move the last task to the top + $this->assertTrue($t->movePosition(1, 4, 1, 1)); + + // Check tasks position + $task = $t->getById(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(2, $task['position']); + + $task = $t->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(3, $task['position']); + + $task = $t->getById(3); + $this->assertEquals(3, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(4, $task['position']); + + $task = $t->getById(4); + $this->assertEquals(4, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(1, $task['position']); + } + + public function testMoveTaskBottom() + { + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(3, $t->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(4, $t->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 1))); + + // Move the last task to hte top + $this->assertTrue($t->movePosition(1, 1, 1, 4)); + + // Check tasks position + $task = $t->getById(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(4, $task['position']); + + $task = $t->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(1, $task['position']); + + $task = $t->getById(3); + $this->assertEquals(3, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(2, $task['position']); + + $task = $t->getById(4); + $this->assertEquals(4, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(3, $task['position']); + } + + public function testMovePosition() + { + $t = new Task($this->registry); + $p = new Project($this->registry); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $counter = 1; + $task_per_column = 5; + + foreach (array(1, 2, 3, 4) as $column_id) { + + for ($i = 1; $i <= $task_per_column; $i++, $counter++) { + + $task = array( + 'title' => 'Task #'.$i.'-'.$column_id, + 'project_id' => 1, + 'column_id' => $column_id, + 'owner_id' => 0, + ); + + $this->assertEquals($counter, $t->create($task)); + + $task = $t->getById($counter); + $this->assertNotFalse($task); + $this->assertNotEmpty($task); + $this->assertEquals($i, $task['position']); + } + } + + // We move task id #4, column 1, position 4 to the column 2, position 3 + $this->assertTrue($t->movePosition(1, 4, 2, 3)); + + // We check the new position of the task + $task = $t->getById(4); + $this->assertEquals(4, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(3, $task['position']); + + // The tasks before have the correct position + $task = $t->getById(3); + $this->assertEquals(3, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(3, $task['position']); + + $task = $t->getById(7); + $this->assertEquals(7, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(2, $task['position']); + + // The tasks after have the correct position + $task = $t->getById(5); + $this->assertEquals(5, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(4, $task['position']); + + $task = $t->getById(8); + $this->assertEquals(8, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(4, $task['position']); + + // The number of tasks per column + $this->assertEquals($task_per_column - 1, $t->countByColumnId(1, 1)); + $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 2)); + $this->assertEquals($task_per_column, $t->countByColumnId(1, 3)); + $this->assertEquals($task_per_column, $t->countByColumnId(1, 4)); + + // We move task id #1, column 1, position 1 to the column 4, position 6 (last position) + $this->assertTrue($t->movePosition(1, 1, 4, $task_per_column + 1)); + + // We check the new position of the task + $task = $t->getById(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals(4, $task['column_id']); + $this->assertEquals($task_per_column + 1, $task['position']); + + // The tasks before have the correct position + $task = $t->getById(20); + $this->assertEquals(20, $task['id']); + $this->assertEquals(4, $task['column_id']); + $this->assertEquals($task_per_column, $task['position']); + + // The tasks after have the correct position + $task = $t->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(1, $task['position']); + + // The number of tasks per column + $this->assertEquals($task_per_column - 2, $t->countByColumnId(1, 1)); + $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 2)); + $this->assertEquals($task_per_column, $t->countByColumnId(1, 3)); + $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 4)); + + // Our previous moved task should stay at the same place + $task = $t->getById(4); + $this->assertEquals(4, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(3, $task['position']); + + // Test wrong position number + $this->assertFalse($t->movePosition(1, 2, 3, 0)); + $this->assertFalse($t->movePosition(1, 2, 3, -2)); + + // Wrong column + $this->assertFalse($t->movePosition(1, 2, 22, 2)); + + // Test position greater than the last position + $this->assertTrue($t->movePosition(1, 11, 1, 22)); + + $task = $t->getById(11); + $this->assertEquals(11, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals($t->countByColumnId(1, 1), $task['position']); + + $task = $t->getById(5); + $this->assertEquals(5, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals($t->countByColumnId(1, 1) - 1, $task['position']); + + $task = $t->getById(4); + $this->assertEquals(4, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(3, $task['position']); + + $this->assertEquals($task_per_column - 1, $t->countByColumnId(1, 1)); + $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 2)); + $this->assertEquals($task_per_column - 1, $t->countByColumnId(1, 3)); + $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 4)); + + // Our previous moved task should stay at the same place + $task = $t->getById(4); + $this->assertEquals(4, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(3, $task['position']); + + // Test moving task to position 1 + $this->assertTrue($t->movePosition(1, 14, 1, 1)); + + $task = $t->getById(14); + $this->assertEquals(14, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(1, $task['position']); + + $task = $t->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(2, $task['position']); + + $this->assertEquals($task_per_column, $t->countByColumnId(1, 1)); + $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 2)); + $this->assertEquals($task_per_column - 2, $t->countByColumnId(1, 3)); + $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 4)); + } + public function testExport() { $t = new Task($this->registry); @@ -131,21 +428,29 @@ class TaskTest extends Base $this->assertEquals('2014-03-05', date('Y-m-d', $t->parseDate('03/05/2014'))); } - public function testDuplicateTask() + public function testDuplicateToTheSameProject() { $t = new Task($this->registry); $p = new Project($this->registry); + $c = new Category($this->registry); // We create a task and a project $this->assertEquals(1, $p->create(array('name' => 'test1'))); + + // Some categories + $this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 1))); + $this->assertNotFalse($c->create(array('name' => 'Category #2', 'project_id' => 1))); + $this->assertTrue($c->exists(1, 1)); + $this->assertTrue($c->exists(2, 1)); + $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1, 'category_id' => 2))); $task = $t->getById(1); $this->assertNotEmpty($task); - $this->assertEquals(0, $task['position']); + $this->assertEquals(1, $task['position']); // We duplicate our task - $this->assertEquals(2, $t->duplicate(1)); + $this->assertEquals(2, $t->duplicateSameProject($task)); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_CREATE)); // Check the values of the duplicated task @@ -154,35 +459,88 @@ class TaskTest extends Base $this->assertEquals(Task::STATUS_OPEN, $task['is_active']); $this->assertEquals(1, $task['project_id']); $this->assertEquals(1, $task['owner_id']); - $this->assertEquals(1, $task['position']); $this->assertEquals(2, $task['category_id']); + $this->assertEquals(3, $task['column_id']); + $this->assertEquals(2, $task['position']); } public function testDuplicateToAnotherProject() { $t = new Task($this->registry); $p = new Project($this->registry); + $c = new Category($this->registry); // We create 2 projects $this->assertEquals(1, $p->create(array('name' => 'test1'))); $this->assertEquals(2, $p->create(array('name' => 'test2'))); + $this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 1))); + $this->assertTrue($c->exists(1, 1)); + // We create a task - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1, 'category_id' => 1))); + $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'owner_id' => 1, 'category_id' => 1))); + $task = $t->getById(1); // We duplicate our task to the 2nd project - $this->assertEquals(2, $t->duplicateToAnotherProject(1, 2)); + $this->assertEquals(2, $t->duplicateToAnotherProject(2, $task)); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_CREATE)); // Check the values of the duplicated task $task = $t->getById(2); $this->assertNotEmpty($task); - $this->assertEquals(0, $task['owner_id']); + $this->assertEquals(1, $task['owner_id']); $this->assertEquals(0, $task['category_id']); + $this->assertEquals(5, $task['column_id']); + $this->assertEquals(1, $task['position']); $this->assertEquals(2, $task['project_id']); $this->assertEquals('test', $task['title']); } + public function testMoveToAnotherProject() + { + $t = new Task($this->registry); + $p = new Project($this->registry); + $user = new User($this->registry); + + // We create a regular user + $user->create(array('username' => 'unittest1', 'password' => 'unittest')); + $user->create(array('username' => 'unittest2', 'password' => 'unittest')); + + // We create 2 projects + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(2, $p->create(array('name' => 'test2'))); + + // We create a task + $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1, 'category_id' => 10, 'position' => 333))); + $this->assertEquals(2, $t->create(array('title' => 'test2', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 3, 'category_id' => 10, 'position' => 333))); + + // We duplicate our task to the 2nd project + $task = $t->getById(1); + $this->assertEquals(1, $t->moveToAnotherProject(2, $task)); + //$this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_CREATE)); + + // Check the values of the duplicated task + $task = $t->getById(1); + $this->assertNotEmpty($task); + $this->assertEquals(1, $task['owner_id']); + $this->assertEquals(0, $task['category_id']); + $this->assertEquals(2, $task['project_id']); + $this->assertEquals(5, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals('test', $task['title']); + + // We allow only one user on the second project + $this->assertTrue($p->allowUser(2, 2)); + + // The owner should be reseted + $task = $t->getById(2); + $this->assertEquals(2, $t->moveToAnotherProject(2, $task)); + + $task = $t->getById(2); + $this->assertNotEmpty($task); + $this->assertEquals(0, $task['owner_id']); + } + public function testEvents() { $t = new Task($this->registry); @@ -209,15 +567,16 @@ class TaskTest extends Base $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_OPEN)); // We change the column of our task - $this->assertTrue($t->move(1, 2, 1)); + $this->assertTrue($t->movePosition(1, 1, 2, 1)); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_MOVE_COLUMN)); // We change the position of our task - $this->assertTrue($t->move(1, 2, 2)); + $this->assertEquals(2, $t->create(array('title' => 'test 2', 'project_id' => 1, 'column_id' => 2))); + $this->assertTrue($t->movePosition(1, 1, 2, 2)); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_MOVE_POSITION)); // We change the column and the position of our task - $this->assertTrue($t->move(1, 1, 3)); + $this->assertTrue($t->movePosition(1, 1, 1, 1)); $this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_MOVE_COLUMN)); } } diff --git a/vendor/JsonRPC/Client.php b/vendor/JsonRPC/Client.php index bbdb7200..dda78094 100644 --- a/vendor/JsonRPC/Client.php +++ b/vendor/JsonRPC/Client.php @@ -28,14 +28,6 @@ class Client private $timeout; /** - * Debug flag - * - * @access private - * @var bool - */ - private $debug; - - /** * Username for authentication * * @access private @@ -69,14 +61,12 @@ class Client * @access public * @param string $url Server URL * @param integer $timeout Server URL - * @param bool $debug Debug flag * @param array $headers Custom HTTP headers */ - public function __construct($url, $timeout = 5, $debug = false, $headers = array()) + public function __construct($url, $timeout = 5, $headers = array()) { $this->url = $url; $this->timeout = $timeout; - $this->debug = $debug; $this->headers = array_merge($this->headers, $headers); } @@ -133,9 +123,6 @@ class Client if (isset($result['id']) && $result['id'] == $id && array_key_exists('result', $result)) { return $result['result']; } - else if ($this->debug && isset($result['error'])) { - print_r($result['error']); - } return null; } diff --git a/vendor/JsonRPC/Server.php b/vendor/JsonRPC/Server.php index 93d46cdb..f80531fd 100644 --- a/vendor/JsonRPC/Server.php +++ b/vendor/JsonRPC/Server.php @@ -177,7 +177,7 @@ class Server $params[$name] = $request_params[$name]; } else if ($p->isDefaultValueAvailable()) { - continue; + $params[$name] = $p->getDefaultValue(); } else { return false; diff --git a/vendor/Michelf/Markdown.php b/vendor/Michelf/Markdown.php index 088b7cdd..c5245fdc 100644 --- a/vendor/Michelf/Markdown.php +++ b/vendor/Michelf/Markdown.php @@ -3,7 +3,7 @@ # Markdown - A text-to-HTML conversion tool for web writers # # PHP Markdown -# Copyright (c) 2004-2013 Michel Fortin +# Copyright (c) 2004-2014 Michel Fortin # <http://michelf.com/projects/php-markdown/> # # Original Markdown @@ -21,7 +21,7 @@ class Markdown implements MarkdownInterface { ### Version ### - const MARKDOWNLIB_VERSION = "1.4.0"; + const MARKDOWNLIB_VERSION = "1.4.1"; ### Simple Function Interface ### @@ -59,6 +59,9 @@ class Markdown implements MarkdownInterface { public $predef_urls = array(); public $predef_titles = array(); + # Optional filter function for URLs + public $url_filter_func = null; + ### Parser Implementation ### @@ -209,7 +212,7 @@ class Markdown implements MarkdownInterface { )? # title is optional (?:\n+|\Z) }xm', - array(&$this, '_stripLinkDefinitions_callback'), + array($this, '_stripLinkDefinitions_callback'), $text); return $text; } @@ -242,7 +245,7 @@ class Markdown implements MarkdownInterface { # $block_tags_a_re = 'ins|del'; $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. - 'script|noscript|form|fieldset|iframe|math|svg|'. + 'script|noscript|style|form|fieldset|iframe|math|svg|'. 'article|section|nav|aside|hgroup|header|footer|'. 'figure'; @@ -297,9 +300,9 @@ class Markdown implements MarkdownInterface { # match will start at the first `<div>` and stop at the first `</div>`. $text = preg_replace_callback('{(?> (?> - (?<=\n\n) # Starting after a blank line + (?<=\n) # Starting on its own line | # or - \A\n? # the beginning of the doc + \A\n? # the at beginning of the doc ) ( # save in $1 @@ -356,7 +359,7 @@ class Markdown implements MarkdownInterface { ) )}Sxmi', - array(&$this, '_hashHTMLBlocks_callback'), + array($this, '_hashHTMLBlocks_callback'), $text); return $text; @@ -500,7 +503,7 @@ class Markdown implements MarkdownInterface { protected function doHardBreaks($text) { # Do hard breaks: return preg_replace_callback('/ {2,}\n/', - array(&$this, '_doHardBreaks_callback'), $text); + array($this, '_doHardBreaks_callback'), $text); } protected function _doHardBreaks_callback($matches) { return $this->hashPart("<br$this->empty_element_suffix\n"); @@ -531,7 +534,7 @@ class Markdown implements MarkdownInterface { \] ) }xs', - array(&$this, '_doAnchors_reference_callback'), $text); + array($this, '_doAnchors_reference_callback'), $text); # # Next, inline-style links: [link text](url "optional title") @@ -558,7 +561,7 @@ class Markdown implements MarkdownInterface { \) ) }xs', - array(&$this, '_doAnchors_inline_callback'), $text); + array($this, '_doAnchors_inline_callback'), $text); # # Last, handle reference-style shortcuts: [link text] @@ -572,7 +575,7 @@ class Markdown implements MarkdownInterface { \] ) }xs', - array(&$this, '_doAnchors_reference_callback'), $text); + array($this, '_doAnchors_reference_callback'), $text); $this->in_anchor = false; return $text; @@ -593,7 +596,7 @@ class Markdown implements MarkdownInterface { if (isset($this->urls[$link_id])) { $url = $this->urls[$link_id]; - $url = $this->encodeAttribute($url); + $url = $this->encodeURLAttribute($url); $result = "<a href=\"$url\""; if ( isset( $this->titles[$link_id] ) ) { @@ -617,7 +620,13 @@ class Markdown implements MarkdownInterface { $url = $matches[3] == '' ? $matches[4] : $matches[3]; $title =& $matches[7]; - $url = $this->encodeAttribute($url); + // if the URL was of the form <s p a c e s> it got caught by the HTML + // tag parser and hashed. Need to reverse the process before using the URL. + $unhashed = $this->unhash($url); + if ($unhashed != $url) + $url = preg_replace('/^<(.*)>$/', '\1', $unhashed); + + $url = $this->encodeURLAttribute($url); $result = "<a href=\"$url\""; if (isset($title)) { @@ -654,7 +663,7 @@ class Markdown implements MarkdownInterface { ) }xs', - array(&$this, '_doImages_reference_callback'), $text); + array($this, '_doImages_reference_callback'), $text); # # Next, handle inline images:  @@ -683,7 +692,7 @@ class Markdown implements MarkdownInterface { \) ) }xs', - array(&$this, '_doImages_inline_callback'), $text); + array($this, '_doImages_inline_callback'), $text); return $text; } @@ -698,7 +707,7 @@ class Markdown implements MarkdownInterface { $alt_text = $this->encodeAttribute($alt_text); if (isset($this->urls[$link_id])) { - $url = $this->encodeAttribute($this->urls[$link_id]); + $url = $this->encodeURLAttribute($this->urls[$link_id]); $result = "<img src=\"$url\" alt=\"$alt_text\""; if (isset($this->titles[$link_id])) { $title = $this->titles[$link_id]; @@ -722,7 +731,7 @@ class Markdown implements MarkdownInterface { $title =& $matches[7]; $alt_text = $this->encodeAttribute($alt_text); - $url = $this->encodeAttribute($url); + $url = $this->encodeURLAttribute($url); $result = "<img src=\"$url\" alt=\"$alt_text\""; if (isset($title)) { $title = $this->encodeAttribute($title); @@ -743,7 +752,7 @@ class Markdown implements MarkdownInterface { # -------- # $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', - array(&$this, '_doHeaders_callback_setext'), $text); + array($this, '_doHeaders_callback_setext'), $text); # atx-style headers: # # Header 1 @@ -760,7 +769,7 @@ class Markdown implements MarkdownInterface { \#* # optional closing #\'s (not counted) \n+ }xm', - array(&$this, '_doHeaders_callback_atx'), $text); + array($this, '_doHeaders_callback_atx'), $text); return $text; } @@ -789,7 +798,6 @@ class Markdown implements MarkdownInterface { # Re-usable patterns to match list item bullets and number markers: $marker_ul_re = '[*+-]'; $marker_ol_re = '\d+[\.]'; - $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; $markers_relist = array( $marker_ul_re => $marker_ol_re, @@ -833,14 +841,14 @@ class Markdown implements MarkdownInterface { ^ '.$whole_list_re.' }mx', - array(&$this, '_doLists_callback'), $text); + array($this, '_doLists_callback'), $text); } else { $text = preg_replace_callback('{ (?:(?<=\n)\n|\A\n?) # Must eat the newline '.$whole_list_re.' }mx', - array(&$this, '_doLists_callback'), $text); + array($this, '_doLists_callback'), $text); } } @@ -907,7 +915,7 @@ class Markdown implements MarkdownInterface { (?:(\n+(?=\n))|\n) # tailing blank line = $5 (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) }xm', - array(&$this, '_processListItems_callback'), $list_str); + array($this, '_processListItems_callback'), $list_str); $this->list_level--; return $list_str; @@ -951,7 +959,7 @@ class Markdown implements MarkdownInterface { ) ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc }xm', - array(&$this, '_doCodeBlocks_callback'), $text); + array($this, '_doCodeBlocks_callback'), $text); return $text; } @@ -979,19 +987,19 @@ class Markdown implements MarkdownInterface { protected $em_relist = array( - '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)', - '*' => '(?<=\S|^)(?<!\*)\*(?!\*)', - '_' => '(?<=\S|^)(?<!_)_(?!_)', + '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?![\.,:;]?\s)', + '*' => '(?<![\s*])\*(?!\*)', + '_' => '(?<![\s_])_(?!_)', ); protected $strong_relist = array( - '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)', - '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)', - '__' => '(?<=\S|^)(?<!_)__(?!_)', + '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?![\.,:;]?\s)', + '**' => '(?<![\s*])\*\*(?!\*)', + '__' => '(?<![\s_])__(?!_)', ); protected $em_strong_relist = array( - '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)', - '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)', - '___' => '(?<=\S|^)(?<!_)___(?!_)', + '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?![\.,:;]?\s)', + '***' => '(?<![\s*])\*\*\*(?!\*)', + '___' => '(?<![\s_])___(?!_)', ); protected $em_strong_prepared_relist; @@ -1151,7 +1159,7 @@ class Markdown implements MarkdownInterface { )+ ) /xm', - array(&$this, '_doBlockQuotes_callback'), $text); + array($this, '_doBlockQuotes_callback'), $text); return $text; } @@ -1165,7 +1173,7 @@ class Markdown implements MarkdownInterface { # These leading spaces cause problem with <pre> content, # so we need to fix that: $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', - array(&$this, '_doBlockQuotes_callback2'), $bq); + array($this, '_doBlockQuotes_callback2'), $bq); return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n"; } @@ -1255,6 +1263,33 @@ class Markdown implements MarkdownInterface { $text = str_replace('"', '"', $text); return $text; } + + + protected function encodeURLAttribute($url, &$text = null) { + # + # Encode text for a double-quoted HTML attribute containing a URL, + # applying the URL filter if set. Also generates the textual + # representation for the URL (removing mailto: or tel:) storing it in $text. + # This function is *not* suitable for attributes enclosed in single quotes. + # + if ($this->url_filter_func) + $url = call_user_func($this->url_filter_func, $url); + + if (preg_match('{^mailto:}i', $url)) + $url = $this->encodeEntityObfuscatedAttribute($url, $text, 7); + else if (preg_match('{^tel:}i', $url)) + { + $url = $this->encodeAttribute($url); + $text = substr($url, 4); + } + else + { + $url = $this->encodeAttribute($url); + $text = $url; + } + + return $url; + } protected function encodeAmpsAndAngles($text) { @@ -1279,8 +1314,8 @@ class Markdown implements MarkdownInterface { protected function doAutoLinks($text) { - $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', - array(&$this, '_doAutoLinks_url_callback'), $text); + $text = preg_replace_callback('{<((https?|ftp|dict|tel):[^\'">\s]+)>}i', + array($this, '_doAutoLinks_url_callback'), $text); # Email addresses: <address@domain.foo> $text = preg_replace_callback('{ @@ -1301,49 +1336,47 @@ class Markdown implements MarkdownInterface { ) > }xi', - array(&$this, '_doAutoLinks_email_callback'), $text); - $text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array(&$this, '_doAutoLinks_tel_callback'), $text); + array($this, '_doAutoLinks_email_callback'), $text); return $text; } - protected function _doAutoLinks_tel_callback($matches) { - $url = $this->encodeAttribute($matches[1]); - $tel = $this->encodeAttribute($matches[2]); - $link = "<a href=\"$url\">$tel</a>"; - return $this->hashPart($link); - } protected function _doAutoLinks_url_callback($matches) { - $url = $this->encodeAttribute($matches[1]); - $link = "<a href=\"$url\">$url</a>"; + $url = $this->encodeURLAttribute($matches[1], $text); + $link = "<a href=\"$url\">$text</a>"; return $this->hashPart($link); } protected function _doAutoLinks_email_callback($matches) { - $address = $matches[1]; - $link = $this->encodeEmailAddress($address); + $addr = $matches[1]; + $url = $this->encodeURLAttribute("mailto:$addr", $text); + $link = "<a href=\"$url\">$text</a>"; return $this->hashPart($link); } - protected function encodeEmailAddress($addr) { + protected function encodeEntityObfuscatedAttribute($text, &$tail = null, $head_length = 0) { # - # Input: an email address, e.g. "foo@example.com" + # Input: some text to obfuscate, e.g. "mailto:foo@example.com" # - # Output: the email address as a mailto link, with each character - # of the address encoded as either a decimal or hex entity, in - # the hopes of foiling most address harvesting spam bots. E.g.: + # Output: the same text but with most characters encoded as either a + # decimal or hex entity, in the hopes of foiling most address + # harvesting spam bots. E.g.: # - # <p><a href="mailto:foo + # mailto:foo # @example.co - # m">foo@exampl - # e.com</a></p> + # m + # + # Note: the additional output $tail is assigned the same value as the + # ouput, minus the number of characters specified by $head_length. # # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. - # With some optimizations by Milian Wolff. + # With some optimizations by Milian Wolff. Forced encoding of HTML + # attribute special characters by Allan Odgaard. # - $addr = "mailto:" . $addr; - $chars = preg_split('/(?<!^)(?!$)/', $addr); - $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed. - + if ($text == "") return $tail = ""; + + $chars = preg_split('/(?<!^)(?!$)/', $text); + $seed = (int)abs(crc32($text) / strlen($text)); # Deterministic seed. + foreach ($chars as $key => $char) { $ord = ord($char); # Ignore non-ascii chars. @@ -1351,17 +1384,17 @@ class Markdown implements MarkdownInterface { $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. # roughly 10% raw, 45% hex, 45% dec # '@' *must* be encoded. I insist. - if ($r > 90 && $char != '@') /* do nothing */; + # '"' and '>' have to be encoded inside the attribute + if ($r > 90 && strpos('@"&>', $char) === false) /* do nothing */; else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; else $chars[$key] = '&#'.$ord.';'; } } - - $addr = implode('', $chars); - $text = implode('', array_slice($chars, 7)); # text without `mailto:` - $addr = "<a href=\"$addr\">$text</a>"; - return $addr; + $text = implode('', $chars); + $tail = $head_length ? implode('', array_slice($chars, $head_length)) : $text; + + return $text; } @@ -1470,7 +1503,7 @@ class Markdown implements MarkdownInterface { # appropriate number of space between each blocks. $text = preg_replace_callback('/^.*\t.*$/m', - array(&$this, '_detab_callback'), $text); + array($this, '_detab_callback'), $text); return $text; } @@ -1510,7 +1543,7 @@ class Markdown implements MarkdownInterface { # Swap back in all the tags hashed by _HashHTMLBlocks. # return preg_replace_callback('/(.)\x1A[0-9]+\1/', - array(&$this, '_unhash_callback'), $text); + array($this, '_unhash_callback'), $text); } protected function _unhash_callback($matches) { return $this->html_hashes[$matches[0]]; @@ -1716,7 +1749,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { (?:[ ]* '.$this->id_class_attr_catch_re.' )? # $5 = extra id & class attr (?:\n+|\Z) }xm', - array(&$this, '_stripLinkDefinitions_callback'), + array($this, '_stripLinkDefinitions_callback'), $text); return $text; } @@ -1733,17 +1766,17 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { ### HTML Block Parser ### # Tags that are always treated as block tags: - protected $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption'; + protected $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption|figure'; # Tags treated as block tags only if the opening tag is alone on its line: - protected $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video'; + protected $context_block_tags_re = 'script|noscript|style|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video'; # Tags where markdown="1" default to span mode: protected $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; # Tags which must not have their contents modified, no matter where # they appear: - protected $clean_tags_re = 'script|math|svg'; + protected $clean_tags_re = 'script|style|math|svg'; # Tags that do not need to be closed. protected $auto_close_tags_re = 'hr|img|param|source|track'; @@ -2227,7 +2260,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { \] ) }xs', - array(&$this, '_doAnchors_reference_callback'), $text); + array($this, '_doAnchors_reference_callback'), $text); # # Next, inline-style links: [link text](url "optional title") @@ -2255,7 +2288,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes ) }xs', - array(&$this, '_doAnchors_inline_callback'), $text); + array($this, '_doAnchors_inline_callback'), $text); # # Last, handle reference-style shortcuts: [link text] @@ -2269,7 +2302,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { \] ) }xs', - array(&$this, '_doAnchors_reference_callback'), $text); + array($this, '_doAnchors_reference_callback'), $text); $this->in_anchor = false; return $text; @@ -2290,7 +2323,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { if (isset($this->urls[$link_id])) { $url = $this->urls[$link_id]; - $url = $this->encodeAttribute($url); + $url = $this->encodeURLAttribute($url); $result = "<a href=\"$url\""; if ( isset( $this->titles[$link_id] ) ) { @@ -2317,8 +2350,13 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { $title =& $matches[7]; $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]); + // if the URL was of the form <s p a c e s> it got caught by the HTML + // tag parser and hashed. Need to reverse the process before using the URL. + $unhashed = $this->unhash($url); + if ($unhashed != $url) + $url = preg_replace('/^<(.*)>$/', '\1', $unhashed); - $url = $this->encodeAttribute($url); + $url = $this->encodeURLAttribute($url); $result = "<a href=\"$url\""; if (isset($title)) { @@ -2356,7 +2394,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { ) }xs', - array(&$this, '_doImages_reference_callback'), $text); + array($this, '_doImages_reference_callback'), $text); # # Next, handle inline images:  @@ -2386,7 +2424,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes ) }xs', - array(&$this, '_doImages_inline_callback'), $text); + array($this, '_doImages_inline_callback'), $text); return $text; } @@ -2401,7 +2439,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { $alt_text = $this->encodeAttribute($alt_text); if (isset($this->urls[$link_id])) { - $url = $this->encodeAttribute($this->urls[$link_id]); + $url = $this->encodeURLAttribute($this->urls[$link_id]); $result = "<img src=\"$url\" alt=\"$alt_text\""; if (isset($this->titles[$link_id])) { $title = $this->titles[$link_id]; @@ -2428,7 +2466,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]); $alt_text = $this->encodeAttribute($alt_text); - $url = $this->encodeAttribute($url); + $url = $this->encodeURLAttribute($url); $result = "<img src=\"$url\" alt=\"$alt_text\""; if (isset($title)) { $title = $this->encodeAttribute($title); @@ -2458,7 +2496,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer }mx', - array(&$this, '_doHeaders_callback_setext'), $text); + array($this, '_doHeaders_callback_setext'), $text); # atx-style headers: # # Header 1 {#header1} @@ -2477,7 +2515,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { [ ]* \n+ }xm', - array(&$this, '_doHeaders_callback_atx'), $text); + array($this, '_doHeaders_callback_atx'), $text); return $text; } @@ -2528,7 +2566,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { ) (?=\n|\Z) # Stop at final double newline. }xm', - array(&$this, '_doTable_leadingPipe_callback'), $text); + array($this, '_doTable_leadingPipe_callback'), $text); # # Find tables without leading pipe. @@ -2554,7 +2592,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { ) (?=\n|\Z) # Stop at final double newline. }xm', - array(&$this, '_DoTable_callback'), $text); + array($this, '_DoTable_callback'), $text); return $text; } @@ -2678,7 +2716,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { (?>\A\n?|(?<=\n\n)) '.$whole_list_re.' }mx', - array(&$this, '_doDefLists_callback'), $text); + array($this, '_doDefLists_callback'), $text); return $text; } @@ -2716,7 +2754,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed # with a definition mark. }xm', - array(&$this, '_processDefListItems_callback_dt'), $list_str); + array($this, '_processDefListItems_callback_dt'), $list_str); # Process actual definitions. $list_str = preg_replace_callback('{ @@ -2733,7 +2771,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { ) ) }xm', - array(&$this, '_processDefListItems_callback_dd'), $list_str); + array($this, '_processDefListItems_callback_dd'), $list_str); return $list_str; } @@ -2801,7 +2839,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { # Closing marker. \1 [ ]* (?= \n ) }xm', - array(&$this, '_doFencedCodeBlocks_callback'), $text); + array($this, '_doFencedCodeBlocks_callback'), $text); return $text; } @@ -2811,7 +2849,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { $codeblock = $matches[4]; $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); $codeblock = preg_replace_callback('/^\n+/', - array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock); + array($this, '_doFencedCodeBlocks_newlines'), $codeblock); if ($classname != "") { if ($classname{0} == '.') @@ -2837,19 +2875,19 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { # work in the middle of a word. # protected $em_relist = array( - '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)', - '*' => '(?<=\S|^)(?<!\*)\*(?!\*)', - '_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])', + '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?![\.,:;]?\s)', + '*' => '(?<![\s*])\*(?!\*)', + '_' => '(?<![\s_])_(?![a-zA-Z0-9_])', ); protected $strong_relist = array( - '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)', - '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)', - '__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])', + '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?![\.,:;]?\s)', + '**' => '(?<![\s*])\*\*(?!\*)', + '__' => '(?<![\s_])__(?![a-zA-Z0-9_])', ); protected $em_strong_relist = array( - '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)', - '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)', - '___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])', + '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?![\.,:;]?\s)', + '***' => '(?<![\s*])\*\*\*(?!\*)', + '___' => '(?<![\s_])___(?![a-zA-Z0-9_])', ); @@ -2908,13 +2946,13 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { .+ # actual text | \n # newlines but - (?!\[\^.+?\]:\s)# negative lookahead for footnote marker. + (?!\[.+?\][ ]?:\s)# negative lookahead for footnote or link definition marker. (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed # by non-indented content )* ) }xm', - array(&$this, '_stripFootnotes_callback'), + array($this, '_stripFootnotes_callback'), $text); return $text; } @@ -2942,7 +2980,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { # Append footnote list to text. # $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array(&$this, '_appendFootnotes_callback'), $text); + array($this, '_appendFootnotes_callback'), $text); if (!empty($this->footnotes_ordered)) { $text .= "\n\n"; @@ -2974,7 +3012,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { $footnote .= "\n"; # Need to append newline before parsing. $footnote = $this->runBlockGamut("$footnote\n"); $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array(&$this, '_appendFootnotes_callback'), $footnote); + array($this, '_appendFootnotes_callback'), $footnote); $attr = str_replace("%%", ++$num, $attr); $note_id = $this->encodeAttribute($note_id); @@ -3057,7 +3095,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1 (.*) # text = $2 (no blank lines allowed) }xm', - array(&$this, '_stripAbbreviations_callback'), + array($this, '_stripAbbreviations_callback'), $text); return $text; } @@ -3084,7 +3122,7 @@ abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { '(?:'.$this->abbr_word_re.')'. '(?![\w\x1A])'. '}', - array(&$this, '_doAbbreviations_callback'), $text); + array($this, '_doAbbreviations_callback'), $text); } return $text; } diff --git a/vendor/Michelf/MarkdownExtra.php b/vendor/Michelf/MarkdownExtra.php index 04e45292..89822e4c 100644 --- a/vendor/Michelf/MarkdownExtra.php +++ b/vendor/Michelf/MarkdownExtra.php @@ -3,7 +3,7 @@ # Markdown Extra - A text-to-HTML conversion tool for web writers # # PHP Markdown Extra -# Copyright (c) 2004-2013 Michel Fortin +# Copyright (c) 2004-2014 Michel Fortin # <http://michelf.com/projects/php-markdown/> # # Original Markdown diff --git a/vendor/Michelf/MarkdownInterface.php b/vendor/Michelf/MarkdownInterface.php index 22c571a0..e4c2931f 100644 --- a/vendor/Michelf/MarkdownInterface.php +++ b/vendor/Michelf/MarkdownInterface.php @@ -3,7 +3,7 @@ # Markdown - A text-to-HTML conversion tool for web writers # # PHP Markdown -# Copyright (c) 2004-2013 Michel Fortin +# Copyright (c) 2004-2014 Michel Fortin # <http://michelf.com/projects/php-markdown/> # # Original Markdown @@ -32,6 +32,3 @@ interface MarkdownInterface { public function transform($text); } - - -?>
\ No newline at end of file diff --git a/vendor/OAuth/Common/Token/AbstractToken.php b/vendor/OAuth/Common/Token/AbstractToken.php index 8d448a18..7a362473 100755 --- a/vendor/OAuth/Common/Token/AbstractToken.php +++ b/vendor/OAuth/Common/Token/AbstractToken.php @@ -118,4 +118,11 @@ abstract class AbstractToken implements TokenInterface { $this->refreshToken = $refreshToken; } + + public function isExpired() + { + return ($this->getEndOfLife() !== TokenInterface::EOL_NEVER_EXPIRES + && $this->getEndOfLife() !== TokenInterface::EOL_UNKNOWN + && time() > $this->getEndOfLife()); + } } diff --git a/vendor/OAuth/OAuth1/Service/AbstractService.php b/vendor/OAuth/OAuth1/Service/AbstractService.php index 0bff5558..43c9c9f6 100755 --- a/vendor/OAuth/OAuth1/Service/AbstractService.php +++ b/vendor/OAuth/OAuth1/Service/AbstractService.php @@ -82,10 +82,6 @@ abstract class AbstractService extends BaseAbstractService implements ServiceInt } $this->signature->setTokenSecret($tokenSecret); - $extraAuthenticationHeaders = array( - 'oauth_token' => $token, - ); - $bodyParams = array( 'oauth_verifier' => $verifier, ); @@ -207,10 +203,8 @@ abstract class AbstractService extends BaseAbstractService implements ServiceInt } $parameters = array_merge($parameters, array('oauth_token' => $token->getAccessToken())); - - $mergedParams = (is_array($bodyParams)) ? array_merge($parameters, $bodyParams) : $parameters; - - $parameters['oauth_signature'] = $this->signature->getSignature($uri, $mergedParams, $method); + $parameters = (is_array($bodyParams)) ? array_merge($parameters, $bodyParams) : $parameters; + $parameters['oauth_signature'] = $this->signature->getSignature($uri, $parameters, $method); $authorizationHeader = 'OAuth '; $delimiter = ''; diff --git a/vendor/OAuth/OAuth1/Service/Etsy.php b/vendor/OAuth/OAuth1/Service/Etsy.php index 884358eb..30dc331c 100755 --- a/vendor/OAuth/OAuth1/Service/Etsy.php +++ b/vendor/OAuth/OAuth1/Service/Etsy.php @@ -13,6 +13,9 @@ use OAuth\Common\Http\Client\ClientInterface; class Etsy extends AbstractService { + + protected $scopes = array(); + public function __construct( CredentialsInterface $credentials, ClientInterface $httpClient, @@ -32,7 +35,14 @@ class Etsy extends AbstractService */ public function getRequestTokenEndpoint() { - return new Uri($this->baseApiUri . 'oauth/request_token'); + $uri = new Uri($this->baseApiUri . 'oauth/request_token'); + $scopes = $this->getScopes(); + + if (count($scopes)) { + $uri->setQuery('scope=' . implode('%20', $scopes)); + } + + return $uri; } /** @@ -93,4 +103,30 @@ class Etsy extends AbstractService return $token; } + + /** + * Set the scopes for permissions + * @see https://www.etsy.com/developers/documentation/getting_started/oauth#section_permission_scopes + * @param array $scopes + * + * @return $this + */ + public function setScopes(array $scopes) + { + if (!is_array($scopes)) { + $scopes = array(); + } + + $this->scopes = $scopes; + return $this; + } + + /** + * Return the defined scopes + * @return array + */ + public function getScopes() + { + return $this->scopes; + } } diff --git a/vendor/OAuth/OAuth2/Service/Buffer.php b/vendor/OAuth/OAuth2/Service/Buffer.php new file mode 100644 index 00000000..5905678e --- /dev/null +++ b/vendor/OAuth/OAuth2/Service/Buffer.php @@ -0,0 +1,151 @@ +<?php + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Client\ClientInterface; + +/** + * Buffer API. + * @author Sumukh Sridhara <@sumukhsridhara> + * @link https://bufferapp.com/developers/api + */ +class Buffer extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + if ($baseApiUri === null) { + $this->baseApiUri = new Uri('https://api.bufferapp.com/1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://bufferapp.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.bufferapp.com/1/oauth2/token.json'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function requestRequestToken() + { + $responseBody = $this->httpClient->retrieveResponse( + $this->getRequestTokenEndpoint(), + array( + 'client_key' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $code = $this->parseRequestTokenResponse($responseBody); + + return $code; + } + + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['code'])) { + throw new TokenResponseException('Error in retrieving code.'); + } + return $data['code']; + } + + public function requestAccessToken($code) + { + $bodyParams = array( + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'code' => $code, + 'grant_type' => 'authorization_code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if ($data === null || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/OAuth/OAuth2/Service/GitHub.php b/vendor/OAuth/OAuth2/Service/GitHub.php index 3791a275..9fee2ba0 100755 --- a/vendor/OAuth/OAuth2/Service/GitHub.php +++ b/vendor/OAuth/OAuth2/Service/GitHub.php @@ -51,6 +51,13 @@ class GitHub extends AbstractService const SCOPE_REPO = 'repo'; /** + * Grants access to deployment statuses for public and private repositories. + * This scope is only necessary to grant other users or services access to deployment statuses, + * without granting access to the code. + */ + const SCOPE_REPO_DEPLOYMENT = 'repo_deployment'; + + /** * Read/write access to public and private repository commit statuses. This scope is only necessary to grant other * users or services access to private repository commit statuses without granting access to the code. The repo and * public_repo scopes already include access to commit status for private and public repositories, respectively. @@ -71,22 +78,52 @@ class GitHub extends AbstractService * Write access to gists. */ const SCOPE_GIST = 'gist'; - + /** * Grants read and ping access to hooks in public or private repositories. */ const SCOPE_HOOKS_READ = 'read:repo_hook'; - + /** * Grants read, write, and ping access to hooks in public or private repositories. */ const SCOPE_HOOKS_WRITE = 'write:repo_hook'; - + /** * Grants read, write, ping, and delete access to hooks in public or private repositories. */ const SCOPE_HOOKS_ADMIN = 'admin:repo_hook'; + /** + * Read-only access to organization, teams, and membership. + */ + const SCOPE_ORG_READ = 'read:org'; + + /** + * Publicize and unpublicize organization membership. + */ + const SCOPE_ORG_WRITE = 'write:org'; + + /** + * Fully manage organization, teams, and memberships. + */ + const SCOPE_ORG_ADMIN = 'admin:org'; + + /** + * List and view details for public keys. + */ + const SCOPE_PUBLIC_KEY_READ = 'read:public_key'; + + /** + * Create, list, and view details for public keys. + */ + const SCOPE_PUBLIC_KEY_WRITE = 'write:public_key'; + + /** + * Fully manage public keys. + */ + const SCOPE_PUBLIC_KEY_ADMIN = 'admin:public_key'; + public function __construct( CredentialsInterface $credentials, ClientInterface $httpClient, diff --git a/vendor/OAuth/OAuth2/Service/Google.php b/vendor/OAuth/OAuth2/Service/Google.php index fbfc1f29..096876b6 100755 --- a/vendor/OAuth/OAuth2/Service/Google.php +++ b/vendor/OAuth/OAuth2/Service/Google.php @@ -26,6 +26,11 @@ class Google extends AbstractService // Google+ const SCOPE_GPLUS_ME = 'https://www.googleapis.com/auth/plus.me'; const SCOPE_GPLUS_LOGIN = 'https://www.googleapis.com/auth/plus.login'; + const SCOPE_GPLUS_CIRCLES_READ = 'https://www.googleapis.com/auth/plus.circles.read'; + const SCOPE_GPLUS_CIRCLES_WRITE = 'https://www.googleapis.com/auth/plus.circles.write'; + const SCOPE_GPLUS_STREAM_READ = 'https://www.googleapis.com/auth/plus.stream.read'; + const SCOPE_GPLUS_STREAM_WRITE = 'https://www.googleapis.com/auth/plus.stream.write'; + const SCOPE_GPLUS_MEDIA = 'https://www.googleapis.com/auth/plus.media.upload'; // Google Drive const SCOPE_DOCUMENTSLIST = 'https://docs.google.com/feeds/'; @@ -57,6 +62,7 @@ class Google extends AbstractService const SCOPE_CONTACT = 'https://www.google.com/m8/feeds/'; const SCOPE_CHROMEWEBSTORE = 'https://www.googleapis.com/auth/chromewebstore.readonly'; const SCOPE_GMAIL = 'https://mail.google.com/mail/feed/atom'; + const SCOPE_GMAIL_IMAP_SMTP = 'https://mail.google.com'; const SCOPE_PICASAWEB = 'https://picasaweb.google.com/data/'; const SCOPE_SITES = 'https://sites.google.com/feeds/'; const SCOPE_URLSHORTENER = 'https://www.googleapis.com/auth/urlshortener'; @@ -83,8 +89,8 @@ class Google extends AbstractService const SCOPE_YOUTUBE = 'https://www.googleapis.com/auth/youtube'; const SCOPE_YOUTUBE_READ_ONLY = 'https://www.googleapis.com/auth/youtube.readonly'; const SCOPE_YOUTUBE_UPLOAD = 'https://www.googleapis.com/auth/youtube.upload'; - const SCOPE_YOUTUBE_PATNER = 'https://www.googleapis.com/auth/youtubepartner'; - const SCOPE_YOUTUBE_PARTNER_EDIT = 'https://www.googleapis.com/auth/youtubepartner-channel-edit'; + const SCOPE_YOUTUBE_PARTNER = 'https://www.googleapis.com/auth/youtubepartner'; + const SCOPE_YOUTUBE_PARTNER_AUDIT = 'https://www.googleapis.com/auth/youtubepartner-channel-audit'; // Google Glass const SCOPE_GLASS_TIMELINE = 'https://www.googleapis.com/auth/glass.timeline'; diff --git a/vendor/OAuth/OAuth2/Service/Harvest.php b/vendor/OAuth/OAuth2/Service/Harvest.php index 86e8993c..96fb0f2d 100755 --- a/vendor/OAuth/OAuth2/Service/Harvest.php +++ b/vendor/OAuth/OAuth2/Service/Harvest.php @@ -2,13 +2,14 @@ namespace OAuth\OAuth2\Service; -use OAuth\OAuth2\Token\StdOAuth2Token; -use OAuth\Common\Http\Exception\TokenResponseException; -use OAuth\Common\Http\Uri\Uri; use OAuth\Common\Consumer\CredentialsInterface; use OAuth\Common\Http\Client\ClientInterface; -use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; use OAuth\Common\Http\Uri\UriInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Token\TokenInterface; +use OAuth\OAuth2\Token\StdOAuth2Token; class Harvest extends AbstractService { @@ -23,8 +24,32 @@ class Harvest extends AbstractService parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); if (null === $baseApiUri) { - $this->baseApiUri = new Uri('https://api.github.com/'); + $this->baseApiUri = new Uri('https://api.harvestapp.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'state' => 'optional-csrf-token', + 'response_type' => 'code', + ) + ); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); } + + return $url; } /** @@ -66,7 +91,8 @@ class Harvest extends AbstractService $token = new StdOAuth2Token(); $token->setAccessToken($data['access_token']); - $token->setEndOfLife($data['expires_in']); + $token->setLifetime($data['expires_in']); + $token->setRefreshToken($data['refresh_token']); unset($data['access_token']); @@ -76,10 +102,56 @@ class Harvest extends AbstractService } /** + * Refreshes an OAuth2 access token. + * + * @param TokenInterface $token + * + * @return TokenInterface $token + * + * @throws MissingRefreshTokenException + */ + public function refreshAccessToken(TokenInterface $token) + { + $refreshToken = $token->getRefreshToken(); + + if (empty($refreshToken)) { + throw new MissingRefreshTokenException(); + } + + $parameters = array( + 'grant_type' => 'refresh_token', + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'refresh_token' => $refreshToken, + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $parameters, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** * @return array */ protected function getExtraOAuthHeaders() { return array('Accept' => 'application/json'); } + + /** + * Return any additional headers always needed for this service implementation's API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array('Accept' => 'application/json'); + } } diff --git a/vendor/OAuth/OAuth2/Service/Salesforce.php b/vendor/OAuth/OAuth2/Service/Salesforce.php index 7d74db9d..583e4347 100755 --- a/vendor/OAuth/OAuth2/Service/Salesforce.php +++ b/vendor/OAuth/OAuth2/Service/Salesforce.php @@ -1,6 +1,6 @@ <?php -namespace OAuth\Common\Service; +namespace OAuth\OAuth2\Service; use OAuth\OAuth2\Service\AbstractService; use OAuth\OAuth2\Token\StdOAuth2Token; diff --git a/vendor/OAuth/OAuth2/Service/Ustream.php b/vendor/OAuth/OAuth2/Service/Ustream.php new file mode 100644 index 00000000..7e558153 --- /dev/null +++ b/vendor/OAuth/OAuth2/Service/Ustream.php @@ -0,0 +1,98 @@ +<?php + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +class Ustream extends AbstractService +{ + /** + * Scopes + * + * @var string + */ + const SCOPE_OFFLINE = 'offline'; + const SCOPE_BROADCASTER = 'broadcaster'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.ustream.tv/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.ustream.tv/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.ustream.tv/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Authorization' => 'Basic ' . $this->credentials->getConsumerSecret()); + } +} diff --git a/vendor/OAuth/OAuth2/Service/Vkontakte.php b/vendor/OAuth/OAuth2/Service/Vkontakte.php index ddf7a8e6..4a7744ec 100755 --- a/vendor/OAuth/OAuth2/Service/Vkontakte.php +++ b/vendor/OAuth/OAuth2/Service/Vkontakte.php @@ -17,6 +17,7 @@ class Vkontakte extends AbstractService * * @link http://vk.com/dev/permissions */ + const SCOPE_EMAIL = 'email'; const SCOPE_NOTIFY = 'notify'; const SCOPE_FRIENDS = 'friends'; const SCOPE_PHOTOS = 'photos'; diff --git a/vendor/PicoDb/Schema.php b/vendor/PicoDb/Schema.php index b75366ea..a054ac09 100644 --- a/vendor/PicoDb/Schema.php +++ b/vendor/PicoDb/Schema.php @@ -36,20 +36,15 @@ class Schema $function_name = '\Schema\version_'.$i; if (function_exists($function_name)) { - call_user_func($function_name, $this->db->getConnection()); $this->db->getConnection()->setSchemaVersion($i); } - else { - - throw new \LogicException('To execute a database migration, you need to create this function: "'.$function_name.'".'); - } } $this->db->closeTransaction(); } catch (\PDOException $e) { - + $this->db->setLogMessage($function_name.' => '.$e->getMessage()); $this->db->cancelTransaction(); return false; } diff --git a/vendor/PicoDb/Table.php b/vendor/PicoDb/Table.php index 60a1280c..b333feb4 100644 --- a/vendor/PicoDb/Table.php +++ b/vendor/PicoDb/Table.php @@ -70,11 +70,7 @@ class Table $result = $this->db->execute($sql, $values); - if ($result !== false/* && $result->rowCount() > 0*/) { - return true; - } - - return false; + return $result !== false && $result->rowCount() > 0; } @@ -106,7 +102,9 @@ class Table $this->conditions() ); - return false !== $this->db->execute($sql, $this->values); + $result = $this->db->execute($sql, $this->values); + + return $result !== false && $result->rowCount() > 0; } diff --git a/vendor/swiftmailer/preferences.php b/vendor/swiftmailer/preferences.php index 42239437..e5195014 100644 --- a/vendor/swiftmailer/preferences.php +++ b/vendor/swiftmailer/preferences.php @@ -14,18 +14,8 @@ $preferences->setCharset('utf-8'); // Without these lines the default caching mechanism is "array" but this uses a lot of memory. // If possible, use a disk cache to enable attaching large attachments etc. // You can override the default temporary directory by setting the TMPDIR environment variable. - -// The @ operator in front of is_writable calls is to avoid PHP warnings -// when using open_basedir -$tmp = getenv('TMPDIR'); -if ($tmp && @is_writable($tmp)) { - $preferences - ->setTempDir($tmp) - ->setCacheType('disk'); -} elseif (function_exists('sys_get_temp_dir') && @is_writable(sys_get_temp_dir())) { - $preferences - ->setTempDir(sys_get_temp_dir()) - ->setCacheType('disk'); +if (@is_writable($tmpDir = sys_get_temp_dir())) { + $preferences->setTempDir($tmpDir)->setCacheType('disk'); } // this should only be done when Swiftmailer won't use the native QP content encoder diff --git a/vendor/swiftmailer/swiftmailer_generate_mimes_config.php b/vendor/swiftmailer/swiftmailer_generate_mimes_config.php index 695d94d3..ef3cc80b 100755 --- a/vendor/swiftmailer/swiftmailer_generate_mimes_config.php +++ b/vendor/swiftmailer/swiftmailer_generate_mimes_config.php @@ -35,13 +35,10 @@ function generateUpToDateMimeArray() 'php3' => 'application/x-php', 'php4' => 'application/x-php', 'php5' => 'application/x-php', - 'pdf' => 'application/pdf', 'zip' => 'application/zip', 'gif' => 'image/gif', - 'jpg' => 'image/jpeg', 'png' => 'image/png', 'css' => 'text/css', - 'html' => 'text/html', 'js' => 'text/javascript', 'txt' => 'text/plain', 'xml' => 'text/xml', @@ -58,7 +55,6 @@ function generateUpToDateMimeArray() 'aps' => 'application/postscript', 'exe' => 'application/x-ms-dos-executable', 'flv' => 'video/x-flv', - 'gif' => 'image/gif', 'gz' => 'application/x-gzip', 'hqx' => 'application/stuffit', 'htm' => 'text/html', @@ -82,7 +78,6 @@ function generateUpToDateMimeArray() 'ods' => 'vnd.oasis.opendocument.spreadsheet', 'ogg' => 'audio/ogg', 'pdf' => 'application/pdf', - 'png' => 'image/png', 'ppt' => 'application/vnd.ms-powerpoint', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'ps' => 'application/postscript', @@ -94,15 +89,13 @@ function generateUpToDateMimeArray() 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ttf' => 'application/x-font-truetype', - 'txt' => 'text/plain', 'vcf' => 'text/x-vcard', 'wav' => 'audio/wav', 'wma' => 'audio/x-ms-wma', 'wmv' => 'audio/x-ms-wmv', 'xls' => 'application/excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'xml' => 'application/xml', - 'zip' => 'application/zip' + 'xml' => 'application/xml' ); // wrap array for generating file |
