diff --git a/.htaccess b/.htaccess index 3d02921..f1e9744 100644 --- a/.htaccess +++ b/.htaccess @@ -1,8 +1,13 @@ php_flag display_errors on Options -Indexes -RedirectMatch 404 /\.git +DirectorySlash Off RewriteEngine On -RewriteRule ^api/(.*)?$ index.php?api=$1&$2 [L,QSA] -RewriteRule ^((?!((js|css|img|fonts|api)($|\/)))(.*)?)$ index.php?site=$1&$2 [L,QSA] +RewriteRule ^api(/.*)?$ /index.php?api=$1 [L,QSA] + +RewriteEngine On +RewriteOptions AllowNoSlash +RewriteRule ^((\.idea|\.git|src|test|core)(/.*)?)$ /index.php?site=$1 [L,QSA] + +FallbackResource /index.php \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..e7e9d11 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dictionaries/webbase.xml b/.idea/dictionaries/webbase.xml new file mode 100644 index 0000000..2c93c11 --- /dev/null +++ b/.idea/dictionaries/webbase.xml @@ -0,0 +1,8 @@ + + + + adminlte + navbar + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..24eb271 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..53df8cb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..7341688 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/web-base.iml b/.idea/web-base.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/web-base.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 7ac7ad6..63be0bd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,19 @@ # Web-Base -Web-Base is a php framework which provides basic web functionalities. +Web-Base is a php framework which provides basic web functionalities and a modern ReactJS Admin Dashboard. + +### Requirements +- PHP >= 7.4 +- One of these php extensions: mysqli, postgres ### Current Functionalities: - Installation Guide with automatic database setup - REST API -- Account managment -- New: Now supporting MySQL + PostgreSQL +- Account management +- Supporting MySQL + PostgreSQL +- New: Page Routing +- New: Admin Dashboard +- New: Account & User functions ### Upcoming: I actually don't know what i want to implement here. There are quite to many CMS out there with alot of vulnerabilities. There also exist some frameworks already. This project is meant to provide a stable project base to implement what ever a developer wants to: Dynamic Homepages, Webshops, .. diff --git a/core/Api/ApiKey/Create.class.php b/core/Api/ApiKey/Create.class.php deleted file mode 100644 index 916bd19..0000000 --- a/core/Api/ApiKey/Create.class.php +++ /dev/null @@ -1,44 +0,0 @@ -apiKeyAllowed = false; - $this->loginRequired = true; - } - - public function execute($values = array()) { - - if(!parent::execute($values)) { - return false; - } - - $apiKey = generateRandomString(64); - $sql = $this->user->getSQL(); - $validUntil = (new \DateTime())->modify("+30 DAY"); - - $this->success = $sql->insert("ApiKey", array("user_id", "api_key", "valid_until")) - ->addRow($this->user->getId(), $apiKey, $validUntil) - ->returning("uid") - ->execute(); - - $this->lastError = $sql->getLastError(); - - if ($this->success) { - $this->result["api_key"] = array( - "api_key" => $apiKey, - "valid_until" => $validUntil->getTimestamp(), - "uid" => $sql->getLastInsertId(), - ); - } else { - $this->result["api_key"] = null; - } - return $this->success; - } -}; - -?> diff --git a/core/Api/ApiKey/Fetch.class.php b/core/Api/ApiKey/Fetch.class.php deleted file mode 100644 index 350a24e..0000000 --- a/core/Api/ApiKey/Fetch.class.php +++ /dev/null @@ -1,46 +0,0 @@ -loginRequired = true; - } - - public function execute($values = array()) { - if(!parent::execute($values)) { - return false; - } - - $sql = $this->user->getSQL(); - $res = $sql->select("uid", "api_key", "valid_until") - ->from("ApiKey") - ->where(new Compare("user_id", $this->user->getId())) - ->where(new Compare("valid_until", $sql->currentTimestamp(), ">")) - ->where(new Compare("active", true)) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if($this->success) { - $this->result["api_keys"] = array(); - foreach($res as $row) { - $this->result["api_keys"][] = array( - "uid" => $row["uid"], - "api_key" => $row["api_key"], - "valid_until" => (new \DateTime($row["valid_until"]))->getTimestamp(), - ); - } - } - - return $this->success; - } -}; - -?> diff --git a/core/Api/ApiKey/Refresh.class.php b/core/Api/ApiKey/Refresh.class.php deleted file mode 100644 index 21eabe7..0000000 --- a/core/Api/ApiKey/Refresh.class.php +++ /dev/null @@ -1,67 +0,0 @@ - new Parameter("id", Parameter::TYPE_INT), - )); - $this->loginRequired = true; - } - - private function apiKeyExists() { - $id = $this->getParam("id"); - - $sql = $this->user->getSQL(); - $res = $sql->select($sql->count()) - ->from("ApiKey") - ->where(new Compare("uid", $id)) - ->where(new Compare("user_id", $this->user->getId())) - ->where(new Compare("valid_until", $sql->currentTimestamp(), ">")) - ->where(new Compare("active", 1)) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if($this->success && $res[0]["count"] === 0) { - $this->success = false; - $this->lastError = "This API-Key does not exist."; - } - - return $this->success; - } - - public function execute($values = array()) { - if(!parent::execute($values)) { - return false; - } - - $id = $this->getParam("id"); - if(!$this->apiKeyExists()) - return false; - - $validUntil = (new \DateTime)->modify("+30 DAY"); - $sql = $this->user->getSQL(); - $this->success = $sql->update("ApiKey") - ->set("valid_until", $validUntil) - ->where(new Compare("uid", $id)) - ->where(new Compare("user_id", $this->user->getId())) - ->execute(); - $this->lastError = $sql->getLastError(); - - if ($this->success) { - $this->result["valid_until"] = $validUntil->getTimestamp(); - } - - return $this->success; - } -}; - -?> diff --git a/core/Api/ApiKey/Revoke.class.php b/core/Api/ApiKey/Revoke.class.php deleted file mode 100644 index 7bcf89c..0000000 --- a/core/Api/ApiKey/Revoke.class.php +++ /dev/null @@ -1,62 +0,0 @@ - new Parameter("id", Parameter::TYPE_INT), - )); - $this->loginRequired = true; - } - - private function apiKeyExists() { - $id = $this->getParam("id"); - - $sql = $this->user->getSQL(); - $res = $sql->select($sql->count()) - ->from("ApiKey") - ->where(new Compare("uid", $id)) - ->where(new Compare("user_id", $this->user->getId())) - ->where(new Compare("valid_until", $sql->currentTimestamp(), ">")) - ->where(new Compare("active", 1)) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if($this->success && $res[0]["count"] === 0) { - $this->success = false; - $this->lastError = "This API-Key does not exist."; - } - - return $this->success; - } - - public function execute($aValues = array()) { - if(!parent::execute($aValues)) { - return false; - } - - $id = $this->getParam("id"); - if(!$this->apiKeyExists()) - return false; - - $sql = $this->user->getSQL(); - $this->success = $sql->update("ApiKey") - ->set("active", false) - ->where(new Compare("uid", $id)) - ->where(new Compare("user_id", $this->user->getId())) - ->execute(); - $this->lastError = $sql->getLastError(); - - return $this->success; - } -}; - -?> diff --git a/core/Api/ApiKeyAPI.class.php b/core/Api/ApiKeyAPI.class.php new file mode 100644 index 0000000..a75d5b8 --- /dev/null +++ b/core/Api/ApiKeyAPI.class.php @@ -0,0 +1,187 @@ +user->getSQL(); + $res = $sql->select($sql->count()) + ->from("ApiKey") + ->where(new Compare("uid", $id)) + ->where(new Compare("user_id", $this->user->getId())) + ->where(new Compare("valid_until", $sql->currentTimestamp(), ">")) + ->where(new Compare("active", 1)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if($this->success && $res[0]["count"] === 0) { + return $this->createError("This API-Key does not exist."); + } + + return $this->success; + } + } +} + +namespace Api\ApiKey { + + use Api\ApiKeyAPI; + use Api\Parameter\Parameter; + use DateTime; + use Driver\SQL\Condition\Compare; + use Exception; + + class Create extends ApiKeyAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array()); + $this->apiKeyAllowed = false; + $this->loginRequired = true; + } + + public function execute($values = array()) { + + if(!parent::execute($values)) { + return false; + } + + $apiKey = generateRandomString(64); + $sql = $this->user->getSQL(); + $validUntil = (new \DateTime())->modify("+30 DAY"); + + $this->success = $sql->insert("ApiKey", array("user_id", "api_key", "valid_until")) + ->addRow($this->user->getId(), $apiKey, $validUntil) + ->returning("uid") + ->execute(); + + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->result["api_key"] = array( + "api_key" => $apiKey, + "valid_until" => $validUntil->getTimestamp(), + "uid" => $sql->getLastInsertId(), + ); + } else { + $this->result["api_key"] = null; + } + return $this->success; + } + } + + class Fetch extends ApiKeyAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array()); + $this->loginRequired = true; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $sql = $this->user->getSQL(); + $res = $sql->select("uid", "api_key", "valid_until") + ->from("ApiKey") + ->where(new Compare("user_id", $this->user->getId())) + ->where(new Compare("valid_until", $sql->currentTimestamp(), ">")) + ->where(new Compare("active", true)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if($this->success) { + $this->result["api_keys"] = array(); + foreach($res as $row) { + try { + $validUntil = (new DateTime($row["valid_until"]))->getTimestamp(); + } catch (Exception $e) { + $validUntil = $row["valid_until"]; + } + + $this->result["api_keys"][] = array( + "uid" => intval($row["uid"]), + "api_key" => $row["api_key"], + "valid_until" => $validUntil, + ); + } + } + + return $this->success; + } + } + + class Refresh extends ApiKeyAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + "id" => new Parameter("id", Parameter::TYPE_INT), + )); + $this->loginRequired = true; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $id = $this->getParam("id"); + if(!$this->apiKeyExists($id)) + return false; + + $validUntil = (new \DateTime)->modify("+30 DAY"); + $sql = $this->user->getSQL(); + $this->success = $sql->update("ApiKey") + ->set("valid_until", $validUntil) + ->where(new Compare("uid", $id)) + ->where(new Compare("user_id", $this->user->getId())) + ->execute(); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->result["valid_until"] = $validUntil->getTimestamp(); + } + + return $this->success; + } + } + + class Revoke extends ApiKeyAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + "id" => new Parameter("id", Parameter::TYPE_INT), + )); + $this->loginRequired = true; + } + + public function execute($aValues = array()) { + if(!parent::execute($aValues)) { + return false; + } + + $id = $this->getParam("id"); + if(!$this->apiKeyExists($id)) + return false; + + $sql = $this->user->getSQL(); + $this->success = $sql->update("ApiKey") + ->set("active", false) + ->where(new Compare("uid", $id)) + ->where(new Compare("user_id", $this->user->getId())) + ->execute(); + $this->lastError = $sql->getLastError(); + + return $this->success; + } + } + + +} \ No newline at end of file diff --git a/core/Api/ContactAPI.class.php b/core/Api/ContactAPI.class.php new file mode 100644 index 0000000..e0b04a0 --- /dev/null +++ b/core/Api/ContactAPI.class.php @@ -0,0 +1,115 @@ + new StringType('fromName', 32), + 'fromEmail' => new Parameter('fromEmail', Parameter::TYPE_EMAIL), + 'message' => new StringType('message', 512), + ); + + $settings = $user->getConfiguration()->getSettings(); + if ($settings->isRecaptchaEnabled()) { + $parameters["captcha"] = new StringType("captcha"); + } + + parent::__construct($user, $externalCall, $parameters); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $settings = $this->user->getConfiguration()->getSettings(); + if ($settings->isRecaptchaEnabled()) { + $captcha = $this->getParam("captcha"); + $req = new VerifyCaptcha($this->user); + if (!$req->execute(array("captcha" => $captcha, "action" => "contact"))) { + return $this->createError($req->getLastError()); + } + } + + if (!$this->insertContactRequest()) { + return false; + } + + $this->createNotification(); + + if (!$this->success) { + return $this->createError("The contact request was saved, but the server was unable to create a notification."); + } + + return $this->success; + } + + private function insertContactRequest() { + $sql = $this->user->getSQL(); + $name = $this->getParam("fromName"); + $email = $this->getParam("fromEmail"); + $message = $this->getParam("message"); + + $res = $sql->insert("ContactRequest", array("from_name", "from_email", "message")) + ->addRow($name, $email, $message) + ->returning("uid") + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->contactRequestId = $sql->getLastInsertId(); + } + + return $this->success; + } + + private function createNotification() { + $sql = $this->user->getSQL(); + $name = $this->getParam("fromName"); + $email = $this->getParam("fromEmail"); + $message = $this->getParam("message"); + + $res = $sql->insert("Notification", array("title", "message", "type")) + ->addRow("New Contact Request from: $name", "$name ($email) wrote:\n$message", "message") + ->returning("uid") + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->notificationId = $sql->getLastInsertId(); + + $res = $sql->insert("GroupNotification", array("group_id", "notification_id")) + ->addRow(USER_GROUP_ADMIN, $this->notificationId) + ->addRow(USER_GROUP_SUPPORT, $this->notificationId) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + } + + return $this->success; + } + } + +} \ No newline at end of file diff --git a/core/Api/GetLanguages.class.php b/core/Api/GetLanguages.class.php deleted file mode 100644 index d10be01..0000000 --- a/core/Api/GetLanguages.class.php +++ /dev/null @@ -1,39 +0,0 @@ -user->getSQL(); - $res = $sql->select("uid", "code", "name") - ->from("Language") - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if($this->success) { - $this->result['languages'] = array(); - if(empty($res) === 0) { - $this->lastError = L("No languages found"); - } else { - foreach($res as $row) { - $this->result['languages'][$row['uid']] = $row; - } - } - } - - return $this->success; - } -}; - -?> diff --git a/core/Api/GroupsAPI.class.php b/core/Api/GroupsAPI.class.php new file mode 100644 index 0000000..3a81581 --- /dev/null +++ b/core/Api/GroupsAPI.class.php @@ -0,0 +1,194 @@ +user->getSQL(); + $res = $sql->select($sql->count()) + ->from("Group") + ->where(new Compare("name", $name)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success && $res[0]["count"] > 0; + } + } +} + +namespace Api\Groups { + + use Api\GroupsAPI; + use Api\Parameter\Parameter; + use Api\Parameter\StringType; + use Driver\SQL\Condition\Compare; + + class Fetch extends GroupsAPI { + + private int $groupCount; + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'page' => new Parameter('page', Parameter::TYPE_INT, true, 1), + 'count' => new Parameter('count', Parameter::TYPE_INT, true, 20) + )); + + $this->groupCount = 0; + } + + private function getGroupCount() { + + $sql = $this->user->getSQL(); + $res = $sql->select($sql->count())->from("Group")->execute(); + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->groupCount = $res[0]["count"]; + } + + return $this->success; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $page = $this->getParam("page"); + if($page < 1) { + return $this->createError("Invalid page count"); + } + + $count = $this->getParam("count"); + if($count < 1 || $count > 50) { + return $this->createError("Invalid fetch count"); + } + + if (!$this->getGroupCount()) { + return false; + } + + $sql = $this->user->getSQL(); + $res = $sql->select("Group.uid as groupId", "Group.name as groupName", "Group.color as groupColor", $sql->count("UserGroup.user_id")) + ->from("Group") + ->leftJoin("UserGroup", "UserGroup.group_id", "Group.uid") + ->groupBy("Group.uid") + ->orderBy("Group.uid") + ->ascending() + ->limit($count) + ->offset(($page - 1) * $count) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if($this->success) { + $this->result["groups"] = array(); + foreach($res as $row) { + $groupId = intval($row["groupId"]); + $groupName = $row["groupName"]; + $groupColor = $row["groupColor"]; + $memberCount = $row["usergroup_user_id_count"]; + $this->result["groups"][$groupId] = array( + "name" => $groupName, + "memberCount" => $memberCount, + "color" => $groupColor, + ); + } + $this->result["pageCount"] = intval(ceil($this->groupCount / $count)); + $this->result["totalCount"] = $this->groupCount; + } + + return $this->success; + } + } + + class Create extends GroupsAPI { + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'name' => new StringType('name', 32), + 'color' => new StringType('color', 10), + )); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $name = $this->getParam("name"); + if (preg_match("/^[a-zA-Z][a-zA-Z0-9_-]*$/", $name) !== 1) { + return $this->createError("Invalid name"); + } + + $color = $this->getParam("color"); + if (preg_match("/^#[a-fA-F0-9]{3,6}$/", $color) !== 1) { + return $this->createError("Invalid color"); + } + + $exists = $this->groupExists($name); + if (!$this->success) { + return false; + } else if ($exists) { + return $this->createError("A group with this name already exists"); + } + + $sql = $this->user->getSQL(); + $res = $sql->insert("Group", array("name", "color")) + ->addRow($name, $color) + ->returning("uid") + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->result["uid"] = $sql->getLastInsertId(); + } + + return $this->success; + } + } + + class Delete extends GroupsAPI { + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'uid' => new Parameter('uid', Parameter::TYPE_INT) + )); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $id = $this->getParam("uid"); + if (in_array($id, DEFAULT_GROUPS)) { + return $this->createError("You cannot delete a default group."); + } + + $sql = $this->user->getSQL(); + $res = $sql->select($sql->count()) + ->from("Group") + ->where(new Compare("uid", $id)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success && $res[0]["count"] === 0) { + return $this->createError("This group does not exist."); + } + + $res = $sql->delete("Group")->where(new Compare("uid", $id))->execute(); + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + } +} \ No newline at end of file diff --git a/core/Api/LanguageAPI.class.php b/core/Api/LanguageAPI.class.php new file mode 100644 index 0000000..89d6487 --- /dev/null +++ b/core/Api/LanguageAPI.class.php @@ -0,0 +1,128 @@ +user->getSQL(); + $res = $sql->select("uid", "code", "name") + ->from("Language") + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if($this->success) { + $this->result['languages'] = array(); + if(empty($res) === 0) { + $this->lastError = L("No languages found"); + } else { + foreach($res as $row) { + $this->result['languages'][$row['uid']] = $row; + } + } + } + + return $this->success; + } + } + + class Set extends LanguageAPI { + + private Language $language; + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'langId' => new Parameter('langId', Parameter::TYPE_INT, true, NULL), + 'langCode' => new StringType('langCode', 5, true, NULL), + )); + + } + + private function checkLanguage() { + $langId = $this->getParam("langId"); + $langCode = $this->getParam("langCode"); + + if(is_null($langId) && is_null($langCode)) { + return $this->createError(L("Either langId or langCode must be given")); + } + + $res = $this->user->getSQL() + ->select("uid", "code", "name") + ->from("Language") + ->where(new CondOr(new Compare("uid", $langId), new Compare("code", $langCode))) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $this->user->getSQL()->getLastError(); + + if ($this->success) { + if(count($res) == 0) { + return $this->createError(L("This Language does not exist")); + } else { + $row = $res[0]; + $this->language = Language::newInstance($row['uid'], $row['code'], $row['name']); + if(!$this->language) { + return $this->createError(L("Error while loading language")); + } + } + } + + return $this->success; + } + + private function updateLanguage() { + $languageId = $this->language->getId(); + $userId = $this->user->getId(); + $sql = $this->user->getSQL(); + + $this->success = $sql->update("User") + ->set("language_id", $languageId) + ->where(new Compare("uid", $userId)) + ->execute(); + $this->lastError = $sql->getLastError(); + return $this->success; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + if(!$this->checkLanguage()) + return false; + + if($this->user->isLoggedIn()) { + $this->updateLanguage(); + } + + $this->user->setLanguage($this->language); + return $this->success; + } + } + +} \ No newline at end of file diff --git a/core/Api/MailAPI.class.php b/core/Api/MailAPI.class.php new file mode 100644 index 0000000..13211fa --- /dev/null +++ b/core/Api/MailAPI.class.php @@ -0,0 +1,120 @@ + new Parameter("receiver", Parameter::TYPE_EMAIL) + )); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $receiver = $this->getParam("receiver"); + $req = new \Api\Mail\Send($this->user); + $this->success = $req->execute(array( + "to" => $receiver, + "subject" => "Test E-Mail", + "body" => "Hey! If you receive this e-mail, your mail configuration seems to be working." + )); + + $this->lastError = $req->getLastError(); + return $this->success; + } + } + + class Send extends MailAPI { + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'to' => new Parameter('to', Parameter::TYPE_EMAIL), + 'subject' => new StringType('subject', -1), + 'body' => new StringType('body', -1), + )); + $this->isPublic = false; + } + + private function getMailConfig() : ?ConnectionData { + $req = new \Api\Settings\Get($this->user); + $this->success = $req->execute(array("key" => "^mail_")); + $this->lastError = $req->getLastError(); + + if ($this->success) { + $settings = $req->getResult()["settings"]; + + if (!isset($settings["mail_enabled"]) || $settings["mail_enabled"] !== "1") { + $this->createError("Mail is not configured yet."); + return null; + } + + $host = $settings["mail_host"] ?? "localhost"; + $port = intval($settings["mail_port"] ?? "25"); + $login = $settings["mail_username"] ?? ""; + $password = $settings["mail_password"] ?? ""; + $connectionData = new ConnectionData($host, $port, $login, $password); + $connectionData->setProperty("from", $settings["mail_from"] ?? ""); + return $connectionData; + } + + return null; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $mailConfig = $this->getMailConfig(); + if (!$this->success) { + return false; + } + + try { + $mail = new PHPMailer; + $mail->IsSMTP(); + $mail->setFrom($mailConfig->getProperty("from")); + $mail->addAddress($this->getParam('to')); + $mail->Subject = $this->getParam('subject'); + $mail->SMTPDebug = 0; + $mail->Host = $mailConfig->getHost(); + $mail->Port = $mailConfig->getPort(); + $mail->SMTPAuth = true; + $mail->Username = $mailConfig->getLogin(); + $mail->Password = $mailConfig->getPassword(); + $mail->SMTPSecure = 'tls'; + $mail->IsHTML(true); + $mail->CharSet = 'UTF-8'; + $mail->Body = $this->getParam('body'); + + $this->success = @$mail->Send(); + if (!$this->success) { + $this->lastError = "Error sending Mail: $mail->ErrorInfo"; + error_log("sendMail() failed: $mail->ErrorInfo"); + } + } catch (Exception $e) { + $this->success = false; + $this->lastError = "Error sending Mail: $e"; + } + + return $this->success; + } + } +} \ No newline at end of file diff --git a/core/Api/Notifications/Create.class.php b/core/Api/Notifications/Create.class.php deleted file mode 100644 index 7470d1e..0000000 --- a/core/Api/Notifications/Create.class.php +++ /dev/null @@ -1,135 +0,0 @@ - new Parameter('groupId', Parameter::TYPE_INT, true), - 'userId' => new Parameter('userId', Parameter::TYPE_INT, true), - 'title' => new StringType('title', 32), - 'message' => new StringType('message', 256), - )); - $this->isPublic = false; - } - - private function checkUser($userId) { - $sql = $this->user->getSQL(); - $res = $sql->select($sql->count()) - ->from("User") - ->where(new Compare("uid", $userId)) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if ($this->success) { - if ($res[0]["count"] == 0) { - $this->success = false; - $this->lastError = "User not found"; - } - } - - return $this->success; - } - - private function insertUserNotification($userId, $notificationId) { - $sql = $this->user->getSQL(); - $res = $sql->insert("UserNotification", array("user_id", "notification_id")) - ->addRow($userId, $notificationId) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - return $this->success; - } - - private function checkGroup($groupId) { - $sql = $this->user->getSQL(); - $res = $sql->select($sql->count()) - ->from("Group") - ->where(new Compare("uid", $groupId)) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if ($this->success) { - if ($res[0]["count"] == 0) { - $this->success = false; - $this->lastError = "Group not found"; - } - } - - return $this->success; - } - - private function insertGroupNotification($groupId, $notificationId) { - $sql = $this->user->getSQL(); - $res = $sql->insert("GroupNotification", array("group_id", "notification_id")) - ->addRow($groupId, $notificationId) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - return $this->success; - } - - private function createNotification($title, $message) { - $sql = $this->user->getSQL(); - $res = $sql->insert("Notification", array("title", "message")) - ->addRow($title, $message) - ->returning("uid") - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if ($this->success) { - return $sql->getLastInsertId(); - } - - return $this->success; - } - - public function execute($values = array()) { - if(!parent::execute($values)) { - return false; - } - - $userId = $this->getParam("userId"); - $groupId = $this->getParam("groupId"); - $title = $this->getParam("title"); - $message = $this->getParam("message"); - - if (is_null($userId) && is_null($groupId)) { - return $this->createError("Either userId or groupId must be specified."); - } else if(!is_null($userId) && !is_null($groupId)) { - return $this->createError("Only one of userId and groupId must be specified."); - } else if(!is_null($userId)) { - if ($this->checkUser($userId)) { - $id = $this->createNotification($title, $message); - if ($this->success) { - return $this->insertUserNotification($userId, $id); - } - } - } else if(!is_null($groupId)) { - if ($this->checkGroup($groupId)) { - $id = $this->createNotification($title, $message); - if ($this->success) { - return $this->insertGroupNotification($groupId, $id); - } - } - } - - return $this->success; - } -}; - -?> diff --git a/core/Api/Notifications/Fetch.class.php b/core/Api/Notifications/Fetch.class.php deleted file mode 100644 index 6e0bdd8..0000000 --- a/core/Api/Notifications/Fetch.class.php +++ /dev/null @@ -1,94 +0,0 @@ -loginRequired = true; - } - - private function fetchUserNotifications() { - $userId = $this->user->getId(); - $sql = $this->user->getSQL(); - $res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message") - ->from("Notification") - ->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.uid") - ->where(new Compare("UserNotification.user_id", $userId)) - ->where(new Compare("UserNotification.seen", false)) - ->orderBy("created_at")->descending() - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if ($this->success) { - foreach($res as $row) { - $id = $row["uid"]; - if (!isset($this->notifications[$id])) { - $this->notifications[$id] = array( - "uid" => $id, - "title" => $row["title"], - "message" => $row["message"], - "created_at" => $row["created_at"], - ); - } - } - } - - return $this->success; - } - - private function fetchGroupNotifications() { - $userId = $this->user->getId(); - $sql = $this->user->getSQL(); - $res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message") - ->from("Notification") - ->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.uid") - ->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id") - ->where(new Compare("UserGroup.user_id", $userId)) - ->where(new Compare("GroupNotification.seen", false)) - ->orderBy("created_at")->descending() - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if ($this->success) { - foreach($res as $row) { - $id = $row["uid"]; - if (!isset($this->notifications[$id])) { - $this->notifications[$id] = array( - "uid" => $id, - "title" => $row["title"], - "message" => $row["message"], - "created_at" => $row["created_at"], - ); - } - } - } - - return $this->success; - } - - public function execute($values = array()) { - if(!parent::execute($values)) { - return false; - } - - $this->notifications = array(); - if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) { - $this->result["notifications"] = $this->notifications; - } - - return $this->success; - } -}; - -?> diff --git a/core/Api/NotificationsAPI.class.php b/core/Api/NotificationsAPI.class.php new file mode 100644 index 0000000..8b12b10 --- /dev/null +++ b/core/Api/NotificationsAPI.class.php @@ -0,0 +1,268 @@ + new Parameter('groupId', Parameter::TYPE_INT, true), + 'userId' => new Parameter('userId', Parameter::TYPE_INT, true), + 'title' => new StringType('title', 32), + 'message' => new StringType('message', 256), + )); + $this->isPublic = false; + } + + private function checkUser($userId) { + $sql = $this->user->getSQL(); + $res = $sql->select($sql->count()) + ->from("User") + ->where(new Compare("uid", $userId)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + if ($res[0]["count"] == 0) { + $this->success = false; + $this->lastError = "User not found"; + } + } + + return $this->success; + } + + private function insertUserNotification($userId, $notificationId) { + $sql = $this->user->getSQL(); + $res = $sql->insert("UserNotification", array("user_id", "notification_id")) + ->addRow($userId, $notificationId) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + + private function checkGroup($groupId) { + $sql = $this->user->getSQL(); + $res = $sql->select($sql->count()) + ->from("Group") + ->where(new Compare("uid", $groupId)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + if ($res[0]["count"] == 0) { + $this->success = false; + $this->lastError = "Group not found"; + } + } + + return $this->success; + } + + private function insertGroupNotification($groupId, $notificationId) { + $sql = $this->user->getSQL(); + $res = $sql->insert("GroupNotification", array("group_id", "notification_id")) + ->addRow($groupId, $notificationId) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + + private function createNotification($title, $message) { + $sql = $this->user->getSQL(); + $res = $sql->insert("Notification", array("title", "message")) + ->addRow($title, $message) + ->returning("uid") + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + return $sql->getLastInsertId(); + } + + return $this->success; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $userId = $this->getParam("userId"); + $groupId = $this->getParam("groupId"); + $title = $this->getParam("title"); + $message = $this->getParam("message"); + + if (is_null($userId) && is_null($groupId)) { + return $this->createError("Either userId or groupId must be specified."); + } else if(!is_null($userId) && !is_null($groupId)) { + return $this->createError("Only one of userId and groupId must be specified."); + } else if(!is_null($userId)) { + if ($this->checkUser($userId)) { + $id = $this->createNotification($title, $message); + if ($this->success) { + return $this->insertUserNotification($userId, $id); + } + } + } else if(!is_null($groupId)) { + if ($this->checkGroup($groupId)) { + $id = $this->createNotification($title, $message); + if ($this->success) { + return $this->insertGroupNotification($groupId, $id); + } + } + } + + return $this->success; + } + } + + class Fetch extends NotificationsAPI { + + private array $notifications; + private array $notificationids; + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'new' => new Parameter('new', Parameter::TYPE_BOOLEAN, true, true) + )); + $this->loginRequired = true; + } + + private function fetchUserNotifications() { + $onlyNew = $this->getParam('new'); + $userId = $this->user->getId(); + $sql = $this->user->getSQL(); + $query = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message", "type") + ->from("Notification") + ->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.uid") + ->where(new Compare("UserNotification.user_id", $userId)) + ->orderBy("created_at")->descending(); + + if ($onlyNew) { + $query->where(new Compare("UserNotification.seen", false)); + } + + return $this->fetchNotifications($query); + } + + private function fetchGroupNotifications() { + $onlyNew = $this->getParam('new'); + $userId = $this->user->getId(); + $sql = $this->user->getSQL(); + $query = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message", "type") + ->from("Notification") + ->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.uid") + ->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id") + ->where(new Compare("UserGroup.user_id", $userId)) + ->orderBy("created_at")->descending(); + + if ($onlyNew) { + $query->where(new Compare("GroupNotification.seen", false)); + } + + return $this->fetchNotifications($query); + } + + private function fetchNotifications(Select $query) { + $sql = $this->user->getSQL(); + $res = $query->execute(); + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + foreach($res as $row) { + $id = $row["uid"]; + if (!in_array($id, $this->notificationids)) { + $this->notificationids[] = $id; + $this->notifications[] = array( + "uid" => $id, + "title" => $row["title"], + "message" => $row["message"], + "created_at" => $row["created_at"], + "type" => $row["type"] + ); + } + } + } + + return $this->success; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $this->notifications = array(); + $this->notificationids = array(); + if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) { + $this->result["notifications"] = $this->notifications; + } + + return $this->success; + } + } + + class Seen extends NotificationsAPI { + + public function __construct(User $user, bool $externalCall = false) { + parent::__construct($user, $externalCall, array()); + $this->loginRequired = true; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $sql = $this->user->getSQL(); + $res = $sql->update("UserNotification") + ->set("seen", true) + ->where(new Compare("user_id", $this->user->getId())) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $res = $sql->update("GroupNotification") + ->set("seen", true) + ->where(new CondIn("group_id", + $sql->select("group_id") + ->from("UserGroup") + ->where(new Compare("user_id", $this->user->getId())))) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + } + + return $this->success; + } + } +} \ No newline at end of file diff --git a/core/Api/Parameter/Parameter.class.php b/core/Api/Parameter/Parameter.class.php index 7b70e90..1fdcb7d 100644 --- a/core/Api/Parameter/Parameter.class.php +++ b/core/Api/Parameter/Parameter.class.php @@ -2,6 +2,8 @@ namespace Api\Parameter; +use DateTime; + class Parameter { const TYPE_INT = 0; const TYPE_FLOAT = 1; @@ -14,15 +16,17 @@ class Parameter { // only internal access const TYPE_RAW = 8; + + // only json will work here i guess const TYPE_ARRAY = 9; const names = array('Integer', 'Float', 'Boolean', 'String', 'Date', 'Time', 'DateTime', 'E-Mail', 'Raw', 'Array'); - public $name; + public string $name; public $value; public $optional; - public $type; - public $typeName; + public int $type; + public string $typeName; public function __construct($name, $type, $optional = FALSE, $defaultValue = NULL) { $this->name = $name; @@ -59,11 +63,11 @@ class Parameter { return Parameter::TYPE_BOOLEAN; else if(is_a($value, 'DateTime')) return Parameter::TYPE_DATE_TIME; - else if(($d = \DateTime::createFromFormat('Y-m-d', $value)) && $d->format('Y-m-d') === $value) + else if(($d = DateTime::createFromFormat('Y-m-d', $value)) && $d->format('Y-m-d') === $value) return Parameter::TYPE_DATE; - else if(($d = \DateTime::createFromFormat('H:i:s', $value)) && $d->format('H:i:s') === $value) + else if(($d = DateTime::createFromFormat('H:i:s', $value)) && $d->format('H:i:s') === $value) return Parameter::TYPE_TIME; - else if(($d = \DateTime::createFromFormat('Y-m-d H:i:s', $value)) && $d->format('Y-m-d H:i:s') === $value) + else if(($d = DateTime::createFromFormat('Y-m-d H:i:s', $value)) && $d->format('Y-m-d H:i:s') === $value) return Parameter::TYPE_DATE_TIME; else if (filter_var($value, FILTER_VALIDATE_EMAIL)) return Parameter::TYPE_EMAIL; @@ -156,6 +160,4 @@ class Parameter { return true; } } -} - -?> +} \ No newline at end of file diff --git a/core/Api/Parameter/StringType.class.php b/core/Api/Parameter/StringType.class.php index 2d92808..dc5bc35 100644 --- a/core/Api/Parameter/StringType.class.php +++ b/core/Api/Parameter/StringType.class.php @@ -4,10 +4,10 @@ namespace Api\Parameter; class StringType extends Parameter { - public $maxLength; + public int $maxLength; public function __construct($name, $maxLength = -1, $optional = FALSE, $defaultValue = NULL) { - parent::__construct($name, Parameter::TYPE_STRING, $optional, $defaultValue); $this->maxLength = $maxLength; + parent::__construct($name, Parameter::TYPE_STRING, $optional, $defaultValue); } public function parseParam($value) { @@ -38,6 +38,4 @@ class StringType extends Parameter { return $str; } -} - -?> +} \ No newline at end of file diff --git a/core/Api/PermissionAPI.class.php b/core/Api/PermissionAPI.class.php new file mode 100644 index 0000000..18e3930 --- /dev/null +++ b/core/Api/PermissionAPI.class.php @@ -0,0 +1,213 @@ +user->isLoggedIn() || !$this->user->hasGroup(USER_GROUP_ADMIN)) { + return $this->createError("Permission denied."); + } + + return true; + } + } +} + +namespace Api\Permission { + + use Api\Parameter\Parameter; + use Api\Parameter\StringType; + use Api\PermissionAPI; + use Driver\SQL\Column\Column; + use Driver\SQL\Condition\Compare; + use Driver\SQL\Condition\CondIn; + use Driver\SQL\Condition\CondNot; + use Driver\SQL\Strategy\UpdateStrategy; + use Objects\User; + + class Check extends PermissionAPI { + + public function __construct(User $user, bool $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'method' => new StringType('method', 323) + )); + + $this->isPublic = false; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $method = $this->getParam("method"); + $sql = $this->user->getSQL(); + $res = $sql->select("groups") + ->from("ApiPermission") + ->where(new Compare("method", $method)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + if (empty($res)) { + return true; + } + + $groups = json_decode($res[0]["groups"]); + if (empty($groups)) { + return true; + } + + if (!$this->user->isLoggedIn() || empty(array_intersect($groups, array_keys($this->user->getGroups())))) { + header('HTTP 1.1 401 Unauthorized'); + return $this->createError("Permission denied."); + } + } + + return $this->success; + } + } + + class Fetch extends PermissionAPI { + + private array $groups; + + public function __construct(User $user, bool $externalCall = false) { + parent::__construct($user, $externalCall, array()); + } + + private function fetchGroups() { + $sql = $this->user->getSQL(); + $res = $sql->select("uid", "name", "color") + ->from("Group") + ->orderBy("uid") + ->ascending() + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->groups = array(); + foreach($res as $row) { + $groupId = $row["uid"]; + $groupName = $row["name"]; + $groupColor = $row["color"]; + $this->groups[$groupId] = array("name" => $groupName, "color" => $groupColor); + } + } + + return $this->success; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if (!$this->checkStaticPermission()) { + return false; + } + + if (!$this->fetchGroups()) { + return false; + } + + $sql = $this->user->getSQL(); + $res = $sql->select("method", "groups", "description") + ->from("ApiPermission") + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $permissions = array(); + foreach ($res as $row) { + $method = $row["method"]; + $description = $row["description"]; + $groups = json_decode($row["groups"]); + $permissions[] = array( + "method" => $method, + "groups" => $groups, + "description" => $description + ); + } + $this->result["permissions"] = $permissions; + $this->result["groups"] = $this->groups; + } + + return $this->success; + } + } + + class Save extends PermissionAPI { + + public function __construct(User $user, bool $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'permissions' => new Parameter('permissions', Parameter::TYPE_ARRAY) + )); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if (!$this->checkStaticPermission()) { + return false; + } + + $permissions = $this->getParam("permissions"); + $sql = $this->user->getSQL(); + $methodParam = new StringType('method', 32); + $groupsParam = new Parameter('groups', Parameter::TYPE_ARRAY); + + $updateQuery = $sql->insert("ApiPermission", array("method", "groups")) + ->onDuplicateKeyStrategy(new UpdateStrategy(array("method"), array( "groups" => new Column("groups") ))); + + $insertedMethods = array(); + + foreach($permissions as $permission) { + if (!is_array($permission)) { + return $this->createError("Invalid data type found in parameter: permissions, expected: object"); + } else if(!isset($permission["method"]) || !array_key_exists("groups", $permission)) { + return $this->createError("Invalid object found in parameter: permissions, expected keys 'method' and 'groups'"); + } else if (!$methodParam->parseParam($permission["method"])) { + $expectedType = $methodParam->getTypeName(); + return $this->createError("Invalid data type found for attribute 'method', expected: $expectedType"); + } else if(!$groupsParam->parseParam($permission["groups"])) { + $expectedType = $groupsParam->getTypeName(); + return $this->createError("Invalid data type found for attribute 'groups', expected: $expectedType"); + } else if(empty(trim($methodParam->value))) { + return $this->createError("Method cannot be empty."); + } else { + $method = $methodParam->value; + $groups = $groupsParam->value; + $updateQuery->addRow($method, $groups); + $insertedMethods[] = $method; + } + } + + if (!empty($permissions)) { + $res = $updateQuery->execute(); + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + } + + if ($this->success) { + $res = $sql->delete("ApiPermission") + ->where(new Compare("description", "")) // only delete non default permissions + ->where(new CondNot(new CondIn("method", $insertedMethods))) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + } + + return $this->success; + } + } +} \ No newline at end of file diff --git a/core/Api/Request.class.php b/core/Api/Request.class.php index cb82284..d6e58dd 100644 --- a/core/Api/Request.class.php +++ b/core/Api/Request.class.php @@ -2,36 +2,41 @@ namespace Api; +use Objects\User; + class Request { - protected $user; - protected $params; - protected $lastError; - protected $result; - protected $success; - protected $isPublic; - protected $loginRequired; - protected $variableParamCount; - protected $isDisabled; - protected $apiKeyAllowed; + protected User $user; + protected array $params; + protected string $lastError; + protected array $result; + protected bool $success; + protected bool $isPublic; + protected bool $loginRequired; + protected bool $variableParamCount; + protected bool $isDisabled; + protected bool $apiKeyAllowed; + protected bool $csrfTokenRequired; - private $aDefaultParams; - private $allowedMethods; - private $externCall; + private array $aDefaultParams; + private array $allowedMethods; + private bool $externalCall; - public function __construct($user, $externCall = false, $params = array()) { + public function __construct(User $user, bool $externalCall = false, array $params = array()) { $this->user = $user; $this->aDefaultParams = $params; - $this->lastError = ''; + $this->success = false; $this->result = array(); - $this->externCall = $externCall; + $this->externalCall = $externalCall; $this->isPublic = true; $this->isDisabled = false; $this->loginRequired = false; $this->variableParamCount = false; $this->apiKeyAllowed = true; $this->allowedMethods = array("GET", "POST"); + $this->lastError = ""; + $this->csrfTokenRequired = true; } protected function forbidMethod($method) { @@ -40,33 +45,20 @@ class Request { } } - public function getParamsString() { - $str = ""; - $count = count($this->params); - $i = 0; - foreach($this->params as $param) { - $str .= $param->toString(); - if($i < $count - 1) $str .= ", "; - $i++; - } - - return "($str)"; - } - public function parseParams($values) { - foreach($this->params as $name => $param) { - $value = (isset($values[$name]) ? $values[$name] : NULL); - if(!$param->optional && is_null($value)) { - $this->lastError = 'Missing parameter: ' . $name; - return false; + foreach($this->params as $name => $param) { + $value = $values[$name] ?? NULL; + + $isEmpty = (is_string($value) || is_array($value)) && empty($value); + if(!$param->optional && (is_null($value) || $isEmpty)) { + return $this->createError("Missing parameter: $name"); } - if(!is_null($value)) { + if(!is_null($value) && !$isEmpty) { if(!$param->parseParam($value)) { $value = print_r($value, true); - $this->lastError = "Invalid Type for parameter: $name '$value' (Required: " . $param->getTypeName() . ")"; - return false; + return $this->createError("Invalid Type for parameter: $name '$value' (Required: " . $param->getTypeName() . ")"); } } } @@ -93,11 +85,17 @@ class Request { $this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds(); } - if($this->externCall) { + if($this->externalCall) { $values = $_REQUEST; if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SERVER["CONTENT_TYPE"]) && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"]))) { $jsonData = json_decode(file_get_contents('php://input'), true); - $values = array_merge($values, $jsonData); + if ($jsonData) { + $values = array_merge($values, $jsonData); + } else { + $this->lastError = 'Invalid request body.'; + header('HTTP 1.1 400 Bad Request'); + return false; + } } } @@ -106,7 +104,7 @@ class Request { return false; } - if($this->externCall && !$this->isPublic) { + if($this->externalCall && !$this->isPublic) { $this->lastError = 'This function is private.'; header('HTTP 1.1 403 Forbidden'); return false; @@ -118,18 +116,42 @@ class Request { return false; } + if($this->externalCall) { + $apiKeyAuthorized = false; - if($this->loginRequired) { - $authorized = false; - if(isset($values['api_key']) && $this->apiKeyAllowed) { - $apiKey = $values['api_key']; - $authorized = $this->user->authorize($apiKey); + // Logged in or api key authorized? + if ($this->loginRequired) { + if(isset($values['api_key']) && $this->apiKeyAllowed) { + $apiKey = $values['api_key']; + $apiKeyAuthorized = $this->user->authorize($apiKey); + } + + if(!$this->user->isLoggedIn() && !$apiKeyAuthorized) { + $this->lastError = 'You are not logged in.'; + header('HTTP 1.1 401 Unauthorized'); + return false; + } } - if(!$this->user->isLoggedIn() && !$authorized) { - $this->lastError = 'You are not logged in.'; - header('HTTP 1.1 401 Unauthorized'); - return false; + // CSRF Token + if($this->csrfTokenRequired && $this->user->isLoggedIn()) { + // csrf token required + external call + // if it's not a call with API_KEY, check for csrf_token + if (!isset($values["csrf_token"]) || strcmp($values["csrf_token"], $this->user->getSession()->getCsrfToken()) !== 0) { + $this->lastError = "CSRF-Token mismatch"; + header('HTTP 1.1 403 Forbidden'); + return false; + } + } + + // Check for permission + if (!($this instanceof \Api\Permission\Save)) { + $req = new \Api\Permission\Check($this->user); + $this->success = $req->execute(array("method" => $this->getMethod())); + $this->lastError = $req->getLastError(); + if (!$this->success) { + return false; + } } } @@ -149,47 +171,32 @@ class Request { return true; } - protected function isValidString($str, $regex) { - return preg_replace($regex, "", $str) === $str; - } - protected function createError($err) { $this->success = false; $this->lastError = $err; return false; } - // - // public static function callDirectly($class, $db) { - // header('Content-Type: application/json'); - // require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/api/objects/User.php'; - // require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/sql.php'; - // require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/conf/sql.php'; - // - // $sql = connectSQL(getSqlData($db)); - // $user = new CUser($sql); - // $request = new $class($user, true); - // $request->execute(); - // $sql->close(); - // $user->sendCookies(); - // return $request->getJsonResult(); - // } - protected function getParam($name) { return isset($this->params[$name]) ? $this->params[$name]->value : NULL; } + protected function getParam($name) { + return isset($this->params[$name]) ? $this->params[$name]->value : NULL; + } + public function isPublic() { return $this->isPublic; } - public function getDescription() { return ''; } - public function getSection() { return 'Default'; } public function getLastError() { return $this->lastError; } public function getResult() { return $this->result; } public function success() { return $this->success; } public function loginRequired() { return $this->loginRequired; } - public function isExternCall() { return $this->externCall; } + public function isExternalCall() { return $this->externalCall; } + + private function getMethod() { + $class = str_replace("\\", "/", get_class($this)); + $class = substr($class, strlen("api/")); + return $class; + } public function getJsonResult() { $this->result['success'] = $this->success; $this->result['msg'] = $this->lastError; return json_encode($this->result); } -}; - - -?> +} \ No newline at end of file diff --git a/core/Api/RoutesAPI.class.php b/core/Api/RoutesAPI.class.php new file mode 100644 index 0000000..ef4c0a2 --- /dev/null +++ b/core/Api/RoutesAPI.class.php @@ -0,0 +1,217 @@ +user->getSQL(); + + $res = $sql + ->select("uid", "request", "action", "target", "extra", "active") + ->from("Route") + ->orderBy("uid") + ->ascending() + ->execute(); + + $this->lastError = $sql->getLastError(); + $this->success = ($res !== FALSE); + + if ($this->success) { + $routes = array(); + foreach($res as $row) { + $routes[] = array( + "uid" => intval($row["uid"]), + "request" => $this->formatRegex($row["request"], false), + "action" => $row["action"], + "target" => $row["target"], + "extra" => $row["extra"] ?? "", + "active" => intval($sql->parseBool($row["active"])), + ); + } + + $this->result["routes"] = $routes; + } + + return $this->success; + } +} + + class Find extends RoutesAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'request' => new StringType('request', 128, true, '/') + )); + + $this->isPublic = false; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $request = $this->getParam('request'); + if (!startsWith($request, '/')) { + $request = "/$request"; + } + + $sql = $this->user->getSQL(); + + $res = $sql + ->select("uid", "request", "action", "target", "extra") + ->from("Route") + ->where(new CondBool("active")) + ->where(new CondRegex($request, new Column("request"))) + ->limit(1) + ->execute(); + + $this->lastError = $sql->getLastError(); + $this->success = ($res !== FALSE); + + if ($this->success) { + if (!empty($res)) { + $row = $res[0]; + $this->result["route"] = array( + "uid" => intval($row["uid"]), + "request" => $row["request"], + "action" => $row["action"], + "target" => $row["target"], + "extra" => $row["extra"] + ); + } else { + $this->result["route"] = NULL; + } + } + + return $this->success; + } + } + + class Save extends RoutesAPI { + + private array $routes; + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'routes' => new Parameter('routes',Parameter::TYPE_ARRAY, false) + )); + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + if (!$this->validateRoutes()) { + return false; + } + + $sql = $this->user->getSQL(); + + // DELETE old rules + $this->success = ($sql->truncate("Route")->execute() !== FALSE); + $this->lastError = $sql->getLastError(); + + // INSERT new routes + if ($this->success) { + $stmt = $sql->insert("Route", array("request", "action", "target", "extra", "active")); + + foreach($this->routes as $route) { + $stmt->addRow($route["request"], $route["action"], $route["target"], $route["extra"], $route["active"]); + } + $this->success = ($stmt->execute() !== FALSE); + $this->lastError = $sql->getLastError(); + } + + return $this->success; + } + + private function validateRoutes() { + + $this->routes = array(); + $keys = array( + "request" => Parameter::TYPE_STRING, + "action" => Parameter::TYPE_STRING, + "target" => Parameter::TYPE_STRING, + "extra" => Parameter::TYPE_STRING, + "active" => Parameter::TYPE_BOOLEAN + ); + + $actions = array( + "redirect_temporary", "redirect_permanently", "static", "dynamic" + ); + + foreach($this->getParam("routes") as $index => $route) { + foreach($keys as $key => $expectedType) { + if (!array_key_exists($key, $route)) { + return $this->createError("Route $index missing key: $key"); + } + + $value = $route[$key]; + $type = Parameter::parseType($value); + if ($type !== $expectedType) { + $expectedTypeName = Parameter::names[$expectedType]; + $gotTypeName = Parameter::names[$type]; + return $this->createError("Route $index has invalid value for key: $key, expected: $expectedTypeName, got: $gotTypeName"); + } + } + + $action = $route["action"]; + if (!in_array($action, $actions)) { + return $this->createError("Invalid action: $action"); + } + + if(empty($route["request"])) { + return $this->createError("Request cannot be empty."); + } + + if(empty($route["target"])) { + return $this->createError("Target cannot be empty."); + } + + // add start- and end pattern for database queries + $route["request"] = $this->formatRegex($route["request"], true); + $this->routes[] = $route; + } + + return true; + } + } +} + diff --git a/core/Api/SendMail.class.php b/core/Api/SendMail.class.php deleted file mode 100644 index e093a89..0000000 --- a/core/Api/SendMail.class.php +++ /dev/null @@ -1,58 +0,0 @@ - new Parameter('from', Parameter::TYPE_EMAIL), - 'to' => new Parameter('to', Parameter::TYPE_EMAIL), - 'subject' => new StringType('subject', -1), - 'body' => new StringType('body', -1), - 'fromName' => new StringType('fromName', -1, true, ''), - 'replyTo' => new Parameter('to', Parameter::TYPE_EMAIL, true, ''), - )); - $this->isPublic = false; - } - - public function execute($values = array()) { - if(!parent::execute($values)) { - return false; - } - - $mailConfig = $this->user->getConfiguration()->getMail(); - $mail = new \External\PHPMailer\PHPMailer; - $mail->IsSMTP(); - $mail->setFrom($this->getParam('from'), $this->getParam('fromName')); - $mail->addAddress($this->getParam('to')); - $mail->Subject = $this->getParam('subject'); - $mail->SMTPDebug = 0; - $mail->Host = $mailConfig->getHost(); - $mail->Port = $mailConfig->getPort(); - $mail->SMTPAuth = true; - $mail->Username = $mailConfig->getLogin(); - $mail->Password = $mailConfig->getPassword(); - $mail->SMTPSecure = 'tls'; - $mail->IsHTML(true); - $mail->CharSet = 'UTF-8'; - $mail->Body = $this->getParam('body'); - - $replyTo = $this->getParam('replyTo'); - if(!is_null($replyTo) && !empty($replyTo)) { - $mail->AddReplyTo($replyTo, $this->getParam('fromName')); - } - - $this->success = @$mail->Send(); - if (!$this->success) { - $this->lastError = 'Error sending Mail: ' . $mail->ErrorInfo; - error_log("sendMail() failed: " . $mail->ErrorInfo); - } - - return $this->success; - } -}; - -?> diff --git a/core/Api/SetLanguage.class.php b/core/Api/SetLanguage.class.php deleted file mode 100644 index c12d69a..0000000 --- a/core/Api/SetLanguage.class.php +++ /dev/null @@ -1,83 +0,0 @@ - new Parameter('langId', Parameter::TYPE_INT, true, NULL), - 'langCode' => new StringType('langCode', 5, true, NULL), - )); - } - - private function checkLanguage() { - $langId = $this->getParam("langId"); - $langCode = $this->getParam("langCode"); - - if(is_null($langId) && is_null($langCode)) { - return $this->createError(L("Either langId or langCode must be given")); - } - - $res = $this->user->getSQL() - ->select("uid", "code", "name") - ->from("Language") - ->where(new CondOr(new Compare("uid", $langId), new Compare("code", $langCode))) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $this->user->getSQL()->getLastError(); - - if ($this->success) { - if(count($res) == 0) { - return $this->createError(L("This Language does not exist")); - } else { - $row = $res[0]; - $this->language = \Objects\Language::newInstance($row['uid'], $row['code'], $row['name']); - if(!$this->language) { - return $this->createError(L("Error while loading language")); - } - } - } - - return $this->success; - } - - private function updateLanguage() { - $languageId = $this->language->getId(); - $userId = $this->user->getId(); - $sql = $this->user->getSQL(); - - $this->success = $sql->update("User") - ->set("language_id", $languageId) - ->where(new Compare("uid", $userId)) - ->execute(); - $this->lastError = $sql->getLastError(); - return $this->success; - } - - public function execute($values = array()) { - if(!parent::execute($values)) { - return false; - } - - if(!$this->checkLanguage()) - return false; - - if($this->user->isLoggedIn()) { - $this->updateLanguage(); - } - - $this->user->setLangauge($this->language); - return $this->success; - } -}; - -?> diff --git a/core/Api/SettingsAPI.class.php b/core/Api/SettingsAPI.class.php new file mode 100644 index 0000000..3f1b568 --- /dev/null +++ b/core/Api/SettingsAPI.class.php @@ -0,0 +1,169 @@ + new StringType('key', -1, true, NULL) + )); + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $key = $this->getParam("key"); + $sql = $this->user->getSQL(); + + $query = $sql->select("name", "value") ->from("Settings"); + + if (!is_null($key) && !empty($key)) { + $query->where(new CondRegex(new Column("name"), $key)); + } + + // filter sensitive values, if called from outside + if ($this->isExternalCall()) { + $query->where(new CondNot("private")); + } + + $res = $query->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $settings = array(); + foreach($res as $row) { + $settings[$row["name"]] = $row["value"]; + } + $this->result["settings"] = $settings; + } + + return $this->success; + } + } + + class Set extends SettingsAPI { + public function __construct(User $user, bool $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'settings' => new Parameter('settings', Parameter::TYPE_ARRAY) + )); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $values = $this->getParam("settings"); + if (empty($values)) { + return $this->createError("No values given."); + } + + $paramKey = new StringType('key', 32); + $paramValue = new StringType('value', 1024, true, NULL); + + $sql = $this->user->getSQL(); + $query = $sql->insert("Settings", array("name", "value")); + $keys = array(); + $deleteKeys = array(); + + foreach($values as $key => $value) { + if (!$paramKey->parseParam($key)) { + $key = print_r($key, true); + return $this->createError("Invalid Type for key in parameter settings: '$key' (Required: " . $paramKey->getTypeName() . ")"); + } else if(!is_null($value) && !$paramValue->parseParam($value)) { + $value = print_r($value, true); + return $this->createError("Invalid Type for value in parameter settings: '$value' (Required: " . $paramValue->getTypeName() . ")"); + } else if(preg_match("/^[a-zA-Z_][a-zA-Z_0-9-]*$/", $paramKey->value) !== 1) { + return $this->createError("The property key should only contain alphanumeric characters, underscores and dashes"); + } else { + if (!is_null($paramValue->value)) { + $query->addRow($paramKey->value, $paramValue->value); + } else { + $deleteKeys[] = $paramKey->value; + } + $keys[] = $paramKey->value; + } + } + + if ($this->isExternalCall()) { + $column = $this->checkReadonly($keys); + if(!$this->success) { + return false; + } else if($column !== null) { + return $this->createError("Column '$column' is readonly."); + } + } + + if (!empty($deleteKeys) && !$this->deleteKeys($keys)) { + return false; + } + + if (count($deleteKeys) !== count($keys)) { + $query->onDuplicateKeyStrategy(new UpdateStrategy( + array("name"), + array("value" => new Column("value"))) + ); + + + $this->success = ($query->execute() !== FALSE); + $this->lastError = $sql->getLastError(); + } + + return $this->success; + } + + private function checkReadonly(array $keys) { + $sql = $this->user->getSQL(); + $res = $sql->select("name") + ->from("Settings") + ->where(new CondBool("readonly")) + ->where(new CondIn("name", $keys)) + ->limit(1) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success && !empty($res)) { + return $res[0]["name"]; + } + + return null; + } + + private function deleteKeys(array $keys) { + $sql = $this->user->getSQL(); + $res = $sql->delete("Settings") + ->where(new CondIn("name", $keys)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + } +} \ No newline at end of file diff --git a/core/Api/Stats.class.php b/core/Api/Stats.class.php new file mode 100644 index 0000000..052dcc1 --- /dev/null +++ b/core/Api/Stats.class.php @@ -0,0 +1,90 @@ +user->getSQL(); + $res = $sql->select($sql->count())->from("User")->execute(); + $this->success = $this->success && ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + return ($this->success ? $res[0]["count"] : 0); + } + + private function getPageCount() { + $sql = $this->user->getSQL(); + $res = $sql->select($sql->count())->from("Route") + ->where(new CondBool("active")) + ->execute(); + $this->success = $this->success && ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + return ($this->success ? $res[0]["count"] : 0); + } + + private function checkSettings() { + $req = new \Api\Settings\Get($this->user); + $this->success = $req->execute(array("key" => "^(mail_enabled|recaptcha_enabled)$")); + $this->lastError = $req->getLastError(); + + if ($this->success) { + $settings = $req->getResult()["settings"]; + $this->mailConfigured = ($settings["mail_enabled"] ?? "0") === "1"; + $this->recaptchaConfigured = ($settings["recaptcha_enabled"] ?? "0") === "1"; + } + + return $this->success; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $userCount = $this->getUserCount(); + $pageCount = $this->getPageCount(); + $req = new \Api\Visitors\Stats($this->user); + $this->success = $req->execute(array("type"=>"monthly")); + $this->lastError = $req->getLastError(); + if (!$this->success) { + return false; + } + + $visitorStatistics = $req->getResult()["visitors"]; + $loadAvg = "Unknown"; + if (function_exists("sys_getloadavg")) { + $loadAvg = sys_getloadavg(); + } + + if (!$this->checkSettings()) { + return false; + } + + $this->result["userCount"] = $userCount; + $this->result["pageCount"] = $pageCount; + $this->result["visitors"] = $visitorStatistics; + $this->result["server"] = array( + "version" => WEBBASE_VERSION, + "server" => $_SERVER["SERVER_SOFTWARE"] ?? "Unknown", + "memory_usage" => memory_get_usage(), + "load_avg" => $loadAvg, + "database" => $this->user->getSQL()->getStatus(), + "mail" => $this->mailConfigured, + "reCaptcha" => $this->recaptchaConfigured + ); + + return $this->success; + } + +} \ No newline at end of file diff --git a/core/Api/User/Login.class.php b/core/Api/User/Login.class.php deleted file mode 100644 index 64cf253..0000000 --- a/core/Api/User/Login.class.php +++ /dev/null @@ -1,82 +0,0 @@ - new StringType('username', 32), - 'password' => new StringType('password'), - 'stayLoggedIn' => new Parameter('stayLoggedIn', Parameter::TYPE_BOOLEAN, true, true) - )); - $this->forbidMethod("GET"); - } - - private function wrongCredentials() { - $runtime = microtime(true) - $this->startedAt; - $sleepTime = round(3e6 - $runtime); - if($sleepTime > 0) usleep($sleepTime); - return $this->createError(L('Wrong username or password')); - } - - public function execute($values = array()) { - if(!parent::execute($values)) { - return false; - } - - if($this->user->isLoggedIn()) { - $this->lastError = L('You are already logged in'); - $this->success = true; - return true; - } - - $this->startedAt = microtime(true); - $this->success = false; - $username = $this->getParam('username'); - $password = $this->getParam('password'); - $stayLoggedIn = $this->getParam('stayLoggedIn'); - - $sql = $this->user->getSQL(); - $res = $sql->select("User.uid", "User.password", "User.salt") - ->from("User") - ->where(new Compare("User.name", $username)) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - - if($this->success) { - if(count($res) === 0) { - return $this->wrongCredentials(); - } else { - $row = $res[0]; - $salt = $row['salt']; - $uid = $row['uid']; - $hash = hash('sha256', $password . $salt); - if($hash === $row['password']) { - if(!($this->success = $this->user->createSession($uid, $stayLoggedIn))) { - return $this->createError("Error creating Session: " . $sql->getLastError()); - } else { - $this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds(); - $this->success = true; - } - } - else { - return $this->wrongCredentials(); - } - } - } - - return $this->success; - } -}; - -?> diff --git a/core/Api/User/Logout.class.php b/core/Api/User/Logout.class.php deleted file mode 100644 index 46403c3..0000000 --- a/core/Api/User/Logout.class.php +++ /dev/null @@ -1,26 +0,0 @@ -loginRequired = true; - $this->apiKeyAllowed = false; - } - - public function execute($values = array()) { - if(!parent::execute($values)) { - return false; - } - - $this->success = $this->user->logout(); - $this->lastError = $this->user->getSQL()->getLastError(); - return $this->success; - } -}; - -?> diff --git a/core/Api/UserAPI.class.php b/core/Api/UserAPI.class.php new file mode 100644 index 0000000..1e5fc28 --- /dev/null +++ b/core/Api/UserAPI.class.php @@ -0,0 +1,1140 @@ +user->getSQL(); + $res = $sql->select("User.name", "User.email") + ->from("User") + ->where(...$conditions) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success && !empty($res)) { + $row = $res[0]; + if (strcasecmp($username, $row['name']) === 0) { + return $this->createError("This username is already taken."); + } else if (strcasecmp($email, $row['email']) === 0) { + return $this->createError("This email address is already in use."); + } + } + + return $this->success; + } + + protected function checkPasswordRequirements($password, $confirmPassword) { + if(strcmp($password, $confirmPassword) !== 0) { + return $this->createError("The given passwords do not match"); + } else if(strlen($password) < 6) { + return $this->createError("The password should be at least 6 characters long"); + } + + return true; + } + + protected function checkRequirements($username, $password, $confirmPassword) { + if(strlen($username) < 5 || strlen($username) > 32) { + return $this->createError("The username should be between 5 and 32 characters long"); + } + + return $this->checkPasswordRequirements($password, $confirmPassword); + } + + protected function insertUser($username, $email, $password, $confirmed) { + $sql = $this->user->getSQL(); + $hash = $this->hashPassword($password); + $res = $sql->insert("User", array("name", "password", "email", "confirmed")) + ->addRow($username, $hash, $email, $confirmed) + ->returning("uid") + ->execute(); + + $this->lastError = $sql->getLastError(); + $this->success = ($res !== FALSE); + + if ($this->success) { + return $sql->getLastInsertId(); + } + + return $this->success; + } + + protected function hashPassword($password) { + return password_hash($password, PASSWORD_BCRYPT); + } + + protected function getUser($id) { + $sql = $this->user->getSQL(); + $res = $sql->select("User.uid as userId", "User.name", "User.email", "User.registered_at", "User.confirmed", + "Group.uid as groupId", "Group.name as groupName", "Group.color as groupColor") + ->from("User") + ->leftJoin("UserGroup", "User.uid", "UserGroup.user_id") + ->leftJoin("Group", "Group.uid", "UserGroup.group_id") + ->where(new Compare("User.uid", $id)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + return ($this->success && !empty($res) ? $res : array()); + } + + protected function getMessageTemplate($key) { + $req = new \Api\Settings\Get($this->user); + $this->success = $req->execute(array("key" => "^($key|mail_enabled)$")); + $this->lastError = $req->getLastError(); + + if ($this->success) { + $settings = $req->getResult()["settings"]; + $isEnabled = ($settings["mail_enabled"] ?? "0") === "1"; + if (!$isEnabled) { + return $this->createError("Mail is not enabled."); + } + + return $settings[$key] ?? "{{link}}"; + } + + return $this->success; + } + + protected function invalidateToken($token) { + $this->user->getSQL() + ->update("UserToken") + ->set("used", true) + ->where(new Compare("token", $token)) + ->execute(); + } + } + +} + +namespace Api\User { + + use Api\Parameter\Parameter; + use Api\Parameter\StringType; + use Api\UserAPI; + use Api\VerifyCaptcha; + use DateTime; + use Driver\SQL\Condition\Compare; + use Driver\SQL\Condition\CondBool; + use Driver\SQL\Condition\CondIn; + use Objects\User; + + class Create extends UserAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'username' => new StringType('username', 32), + 'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL), + 'password' => new StringType('password'), + 'confirmPassword' => new StringType('confirmPassword'), + )); + + $this->loginRequired = true; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $username = $this->getParam('username'); + $email = $this->getParam('email'); + $password = $this->getParam('password'); + $confirmPassword = $this->getParam('confirmPassword'); + + if (!$this->checkRequirements($username, $password, $confirmPassword)) { + return false; + } + + if (!$this->userExists($username, $email)) { + return false; + } + + // prevent duplicate keys + $email = (!is_null($email) && empty($email)) ? null : $email; + + $id = $this->insertUser($username, $email, $password, true); + if ($this->success) { + $this->result["userId"] = $id; + } + + return $this->success; + } + } + + class Fetch extends UserAPI { + + private int $userCount; + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'page' => new Parameter('page', Parameter::TYPE_INT, true, 1), + 'count' => new Parameter('count', Parameter::TYPE_INT, true, 20) + )); + } + + private function getUserCount() { + + $sql = $this->user->getSQL(); + $res = $sql->select($sql->count())->from("User")->execute(); + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->userCount = $res[0]["count"]; + } + + return $this->success; + } + + private function selectIds($page, $count) { + $sql = $this->user->getSQL(); + $res = $sql->select("User.uid") + ->from("User") + ->limit($count) + ->offset(($page - 1) * $count) + ->orderBy("User.uid") + ->ascending() + ->execute(); + + $this->success = ($res !== NULL); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $ids = array(); + foreach($res as $row) $ids[] = $row["uid"]; + return $ids; + } + + return false; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $page = $this->getParam("page"); + if ($page < 1) { + return $this->createError("Invalid page count"); + } + + $count = $this->getParam("count"); + if ($count < 1 || $count > 50) { + return $this->createError("Invalid fetch count"); + } + + if (!$this->getUserCount()) { + return false; + } + + $userIds = $this->selectIds($page, $count); + if ($userIds === false) { + return false; + } + + $sql = $this->user->getSQL(); + $res = $sql->select("User.uid as userId", "User.name", "User.email", "User.registered_at", "User.confirmed", + "Group.uid as groupId", "Group.name as groupName", "Group.color as groupColor") + ->from("User") + ->leftJoin("UserGroup", "User.uid", "UserGroup.user_id") + ->leftJoin("Group", "Group.uid", "UserGroup.group_id") + ->where(new CondIn("User.uid", $userIds)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->result["users"] = array(); + foreach ($res as $row) { + $userId = intval($row["userId"]); + $groupId = intval($row["groupId"]); + $groupName = $row["groupName"]; + $groupColor = $row["groupColor"]; + if (!isset($this->result["users"][$userId])) { + $this->result["users"][$userId] = array( + "uid" => $userId, + "name" => $row["name"], + "email" => $row["email"], + "registered_at" => $row["registered_at"], + "confirmed" => $sql->parseBool($row["confirmed"]), + "groups" => array(), + ); + } + + if (!is_null($groupId)) { + $this->result["users"][$userId]["groups"][$groupId] = array( + "name" => $groupName, + "color" => $groupColor + ); + } + } + $this->result["pageCount"] = intval(ceil($this->userCount / $count)); + $this->result["totalCount"] = $this->userCount; + } + + return $this->success; + } + } + + class Get extends UserAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'id' => new Parameter('id', Parameter::TYPE_INT) + )); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $sql = $this->user->getSQL(); + $id = $this->getParam("id"); + $user = $this->getUser($id); + + if ($this->success) { + if (empty($user)) { + return $this->createError("User not found"); + } else { + $this->result["user"] = array( + "uid" => $user[0]["userId"], + "name" => $user[0]["name"], + "email" => $user[0]["email"], + "registered_at" => $user[0]["registered_at"], + "confirmed" => $sql->parseBool($user["0"]["confirmed"]), + "groups" => array() + ); + + foreach($user as $row) { + if (!is_null($row["groupId"])) { + $this->result["user"]["groups"][$row["groupId"]] = array( + "name" => $row["groupName"], + "color" => $row["groupColor"], + ); + } + } + } + } + + return $this->success; + } + } + + class Info extends UserAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array()); + $this->csrfTokenRequired = false; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if (!$this->user->isLoggedIn()) { + $this->result["loggedIn"] = false; + } else { + $this->result["loggedIn"] = true; + } + + $this->result["user"] = $this->user->jsonSerialize(); + return $this->success; + } + } + + class Invite extends UserAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'username' => new StringType('username', 32), + 'email' => new StringType('email', 64), + )); + + $this->loginRequired = true; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $username = $this->getParam('username'); + $email = $this->getParam('email'); + if (!$this->userExists($username, $email)) { + return false; + } + + $messageBody = $this->getMessageTemplate("message_accept_invite"); + if ($messageBody === false) { + return false; + } + + // Create user + $id = $this->insertUser($username, $email, "", false); + if (!$this->success) { + return false; + } + + // Create Token + $token = generateRandomString(36); + $valid_until = (new DateTime())->modify("+7 day"); + $sql = $this->user->getSQL(); + $res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until")) + ->addRow($id, $token, "invite", $valid_until) + ->execute(); + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + //send validation mail + if ($this->success) { + + $settings = $this->user->getConfiguration()->getSettings(); + $baseUrl = htmlspecialchars($settings->getBaseUrl()); + $siteName = htmlspecialchars($settings->getSiteName()); + + $replacements = array( + "link" => "$baseUrl/acceptInvite?token=$token", + "site_name" => $siteName, + "base_url" => $baseUrl, + "username" => htmlspecialchars($username) + ); + + foreach($replacements as $key => $value) { + $messageBody = str_replace("{{{$key}}}", $value, $messageBody); + } + + $request = new \Api\Mail\Send($this->user); + $this->success = $request->execute(array( + "to" => $email, + "subject" => "[$siteName] Account Invitation", + "body" => $messageBody + )); + + $this->lastError = $request->getLastError(); + + if (!$this->success) { + $this->lastError = "The invitation was created but the confirmation email could not be sent. " . + "Please contact the server administration. Reason: " . $this->lastError; + } + } + + return $this->success; + } + } + + class AcceptInvite extends UserAPI { + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'token' => new StringType('token', 36), + 'password' => new StringType('password'), + 'confirmPassword' => new StringType('confirmPassword'), + )); + $this->csrfTokenRequired = false; + } + + private function updateUser($uid, $password) { + $sql = $this->user->getSQL(); + $res = $sql->update("User") + ->set("password", $this->hashPassword($password)) + ->set("confirmed", true) + ->where(new Compare("uid", $uid)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if ($this->user->isLoggedIn()) { + return $this->createError("You are already logged in."); + } + + $token = $this->getParam("token"); + $password = $this->getParam("password"); + $confirmPassword = $this->getParam("confirmPassword"); + + $req = new CheckToken($this->user); + $this->success = $req->execute(array("token" => $token)); + $this->lastError = $req->getLastError(); + + if (!$this->success) { + return false; + } + + $result = $req->getResult(); + if (strcasecmp($result["token"]["type"], "invite") !== 0) { + return $this->createError("Invalid token type"); + } else if($result["user"]["confirmed"]) { + return $this->createError("Your email address is already confirmed."); + } else if (!$this->checkPasswordRequirements($password, $confirmPassword)) { + return false; + } else if (!$this->updateUser($result["user"]["uid"], $password)) { + return false; + } else { + + // Invalidate token + $this->user->getSQL() + ->update("UserToken") + ->set("used", true) + ->where(new Compare("token", $token)) + ->execute(); + + return true; + } + } + } + + class ConfirmEmail extends UserAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'token' => new StringType('token', 36) + )); + } + + private function updateUser($uid) { + $sql = $this->user->getSQL(); + $res = $sql->update("User") + ->set("confirmed", true) + ->where(new Compare("uid", $uid)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if ($this->user->isLoggedIn()) { + return $this->createError("You are already logged in."); + } + + $token = $this->getParam("token"); + + $req = new CheckToken($this->user); + $this->success = $req->execute(array("token" => $token)); + $this->lastError = $req->getLastError(); + + if ($this->success) { + $result = $req->getResult(); + if (strcasecmp($result["token"]["type"], "email_confirm") !== 0) { + return $this->createError("Invalid token type"); + } else if($result["user"]["confirmed"]) { + return $this->createError("Your email address is already confirmed."); + } else if (!$this->updateUser($result["user"]["uid"])) { + return false; + } else { + $this->invalidateToken($token); + return true; + } + } + + return $this->success; + } + } + + class Login extends UserAPI { + + private int $startedAt; + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'username' => new StringType('username', 32), + 'password' => new StringType('password'), + 'stayLoggedIn' => new Parameter('stayLoggedIn', Parameter::TYPE_BOOLEAN, true, true) + )); + $this->forbidMethod("GET"); + } + + private function wrongCredentials() { + $runtime = microtime(true) - $this->startedAt; + $sleepTime = round(3e6 - $runtime); + if ($sleepTime > 0) usleep($sleepTime); + return $this->createError(L('Wrong username or password')); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if ($this->user->isLoggedIn()) { + $this->lastError = L('You are already logged in'); + $this->success = true; + return true; + } + + $this->startedAt = microtime(true); + $this->success = false; + $username = $this->getParam('username'); + $password = $this->getParam('password'); + $stayLoggedIn = $this->getParam('stayLoggedIn'); + + $sql = $this->user->getSQL(); + $res = $sql->select("User.uid", "User.password", "User.confirmed") + ->from("User") + ->where(new Compare("User.name", $username)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + if (count($res) === 0) { + return $this->wrongCredentials(); + } else { + $row = $res[0]; + $uid = $row['uid']; + $confirmed = $sql->parseBool($row["confirmed"]); + if (password_verify($password, $row['password'])) { + if (!$confirmed) { + return $this->createError("Your email address has not been confirmed yet."); + } else if (!($this->success = $this->user->createSession($uid, $stayLoggedIn))) { + return $this->createError("Error creating Session: " . $sql->getLastError()); + } else { + $this->result["loggedIn"] = true; + $this->result["logoutIn"] = $this->user->getSession()->getExpiresSeconds(); + $this->result["csrf_token"] = $this->user->getSession()->getCsrfToken(); + $this->success = true; + } + } else { + return $this->wrongCredentials(); + } + } + } + + return $this->success; + } + } + + class Logout extends UserAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall); + $this->loginRequired = true; + $this->apiKeyAllowed = false; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $this->success = $this->user->logout(); + $this->lastError = $this->user->getSQL()->getLastError(); + return $this->success; + } + } + + class Register extends UserAPI { + + private ?int $userId; + private string $token; + + public function __construct(User $user, bool $externalCall = false) { + $parameters = array( + "username" => new StringType("username", 32), + 'email' => new Parameter('email', Parameter::TYPE_EMAIL), + "password" => new StringType("password"), + "confirmPassword" => new StringType("confirmPassword"), + ); + + $settings = $user->getConfiguration()->getSettings(); + if ($settings->isRecaptchaEnabled()) { + $parameters["captcha"] = new StringType("captcha"); + } + + parent::__construct($user, $externalCall, $parameters); + $this->csrfTokenRequired = false; + } + + private function insertToken() { + $validUntil = (new DateTime())->modify("+48 hour"); + $sql = $this->user->getSQL(); + $res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until")) + ->addRow($this->userId, $this->token, "email_confirm", $validUntil) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if ($this->user->isLoggedIn()) { + return $this->createError(L('You are already logged in')); + } + + $registrationAllowed = $this->user->getConfiguration()->getSettings()->isRegistrationAllowed(); + if(!$registrationAllowed) { + return $this->createError("User Registration is not enabled."); + } + + $settings = $this->user->getConfiguration()->getSettings(); + if ($settings->isRecaptchaEnabled()) { + $captcha = $this->getParam("captcha"); + $req = new VerifyCaptcha($this->user); + if (!$req->execute(array("captcha" => $captcha, "action" => "register"))) { + return $this->createError($req->getLastError()); + } + } + + $username = $this->getParam("username"); + $email = $this->getParam('email'); + $password = $this->getParam("password"); + $confirmPassword = $this->getParam("confirmPassword"); + if (!$this->userExists($username, $email)) { + return false; + } + + if(!$this->checkRequirements($username, $password, $confirmPassword)) { + return false; + } + + $messageBody = $this->getMessageTemplate("message_confirm_email"); + if ($messageBody === false) { + return false; + } + + $id = $this->insertUser($username, $email, $password, false); + if ($id === FALSE) { + return false; + } + + $this->userId = $id; + $this->token = generateRandomString(36); + if ($this->insertToken()) { + $settings = $this->user->getConfiguration()->getSettings(); + $baseUrl = htmlspecialchars($settings->getBaseUrl()); + $siteName = htmlspecialchars($settings->getSiteName()); + + if ($this->success) { + + $replacements = array( + "link" => "$baseUrl/confirmEmail?token=$this->token", + "site_name" => $siteName, + "base_url" => $baseUrl, + "username" => htmlspecialchars($username) + ); + + foreach($replacements as $key => $value) { + $messageBody = str_replace("{{{$key}}}", $value, $messageBody); + } + + $request = new \Api\Mail\Send($this->user); + $this->success = $request->execute(array( + "to" => $email, + "subject" => "[$siteName] E-Mail Confirmation", + "body" => $messageBody + )); + $this->lastError = $request->getLastError(); + } + } + + if (!$this->success) { + $this->lastError = "Your account was registered but the confirmation email could not be sent. " . + "Please contact the server administration. Reason: " . $this->lastError; + } + + return $this->success; + } + } + + class CheckToken extends UserAPI { + + public function __construct($user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'token' => new StringType('token', 36), + )); + } + + private function checkToken($token) { + $sql = $this->user->getSQL(); + $res = $sql->select("UserToken.token_type", "User.uid", "User.name", "User.email") + ->from("UserToken") + ->innerJoin("User", "UserToken.user_id", "User.uid") + ->where(new Compare("UserToken.token", $token)) + ->where(new Compare("UserToken.valid_until", $sql->now(), ">")) + ->where(new Compare("UserToken.used", 0)) + ->execute(); + $this->lastError = $sql->getLastError(); + $this->success = ($res !== FALSE); + + if ($this->success && !empty($res)) { + return $res[0]; + } + + return array(); + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $token = $this->getParam('token'); + $tokenEntry = $this->checkToken($token); + + if ($this->success) { + if (!empty($tokenEntry)) { + $this->result["token"] = array( + "type" => $tokenEntry["token_type"] + ); + + $this->result["user"] = array( + "name" => $tokenEntry["name"], + "email" => $tokenEntry["email"], + "uid" => $tokenEntry["uid"] + ); + } else { + return $this->createError("This token does not exist or is no longer valid"); + } + } + return $this->success; + } + } + + class Edit extends UserAPI { + + public function __construct(User $user, bool $externalCall) { + parent::__construct($user, $externalCall, array( + 'id' => new Parameter('id', Parameter::TYPE_INT), + 'username' => new StringType('username', 32, true, NULL), + 'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL), + 'password' => new StringType('password', -1, true, NULL), + 'groups' => new Parameter('groups', Parameter::TYPE_ARRAY, true, NULL), + 'confirmed' => new Parameter('confirmed', Parameter::TYPE_BOOLEAN, true, NULL) + )); + + $this->loginRequired = true; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $id = $this->getParam("id"); + $user = $this->getUser($id); + + if ($this->success) { + if (empty($user)) { + return $this->createError("User not found"); + } + + $username = $this->getParam("username"); + $email = $this->getParam("email"); + $password = $this->getParam("password"); + $groups = $this->getParam("groups"); + $confirmed = $this->getParam("confirmed"); + + $email = (!is_null($email) && empty($email)) ? null : $email; + + $groupIds = array(); + if (!is_null($groups)) { + $param = new Parameter('groupId', Parameter::TYPE_INT); + + foreach($groups as $groupId) { + if (!$param->parseParam($groupId)) { + $value = print_r($groupId, true); + return $this->createError("Invalid Type for groupId in parameter groups: '$value' (Required: " . $param->getTypeName() . ")"); + } + + $groupIds[] = $param->value; + } + + if ($id === $this->user->getId() && !in_array(USER_GROUP_ADMIN, $groupIds)) { + return $this->createError("Cannot remove Administrator group from own user."); + } + } + + // Check for duplicate username, email + $usernameChanged = !is_null($username) ? strcasecmp($username, $user[0]["name"]) !== 0 : false; + $emailChanged = !is_null($email) ? strcasecmp($email, $user[0]["email"]) !== 0 : false; + if($usernameChanged || $emailChanged) { + if (!$this->userExists($usernameChanged ? $username : NULL, $emailChanged ? $email : NULL)) { + return false; + } + } + + $sql = $this->user->getSQL(); + $query = $sql->update("User"); + + if ($usernameChanged) $query->set("name", $username); + if ($emailChanged) $query->set("email", $email); + if (!is_null($password)) $query->set("password", $this->hashPassword($password)); + + if (!is_null($confirmed)) { + if ($id === $this->user->getId() && $confirmed === false) { + return $this->createError("Cannot make own account unconfirmed."); + } else { + $query->set("confirmed", $confirmed); + } + } + + if (!empty($query->getValues())) { + $query->where(new Compare("User.uid", $id)); + $res = $query->execute(); + $this->lastError = $sql->getLastError(); + $this->success = ($res !== FALSE); + } + + if ($this->success && !empty($groupIds)) { + + $deleteQuery = $sql->delete("UserGroup")->where(new Compare("user_id", $id)); + $insertQuery = $sql->insert("UserGroup", array("user_id", "group_id")); + + foreach($groupIds as $groupId) { + $insertQuery->addRow($id, $groupId); + } + + $this->success = ($deleteQuery->execute() !== FALSE) && ($insertQuery->execute() !== FALSE); + $this->lastError = $sql->getLastError(); + } + } + + return $this->success; + } + } + + class Delete extends UserAPI { + + public function __construct(User $user, bool $externalCall) { + parent::__construct($user, $externalCall, array( + 'id' => new Parameter('id', Parameter::TYPE_INT) + )); + + $this->loginRequired = true; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $id = $this->getParam("id"); + if ($id === $this->user->getId()) { + return $this->createError("You cannot delete your own user."); + } + + $user = $this->getUser($id); + if ($this->success) { + if (empty($user)) { + return $this->createError("User not found"); + } else { + $sql = $this->user->getSQL(); + $res = $sql->delete("User")->where(new Compare("uid", $id))->execute(); + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + } + } + + return $this->success; + } + } + + class RequestPasswordReset extends UserAPI { + public function __construct(User $user, $externalCall = false) { + $parameters = array( + 'email' => new Parameter('email', Parameter::TYPE_EMAIL), + ); + + $settings = $user->getConfiguration()->getSettings(); + if ($settings->isRecaptchaEnabled()) { + $parameters["captcha"] = new StringType("captcha"); + } + + parent::__construct($user, $externalCall, $parameters); + $this->csrfTokenRequired = false; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if ($this->user->isLoggedIn()) { + return $this->createError("You already logged in."); + } + + $settings = $this->user->getConfiguration()->getSettings(); + if ($settings->isRecaptchaEnabled()) { + $captcha = $this->getParam("captcha"); + $req = new VerifyCaptcha($this->user); + if (!$req->execute(array("captcha" => $captcha, "action" => "resetPassword"))) { + return $this->createError($req->getLastError()); + } + } + + $messageBody = $this->getMessageTemplate("message_reset_password"); + if ($messageBody === false) { + return false; + } + + $email = $this->getParam("email"); + $user = $this->findUser($email); + if ($user === false) { + return false; + } + + if ($user !== null) { + $token = generateRandomString(36); + if (!$this->insertToken($user["uid"], $token)) { + return false; + } + + $baseUrl = htmlspecialchars($settings->getBaseUrl()); + $siteName = htmlspecialchars($settings->getSiteName()); + + $replacements = array( + "link" => "$baseUrl/resetPassword?token=$token", + "site_name" => $siteName, + "base_url" => $baseUrl, + "username" => htmlspecialchars($user["name"]) + ); + + foreach($replacements as $key => $value) { + $messageBody = str_replace("{{{$key}}}", $value, $messageBody); + } + + $request = new \Api\Mail\Send($this->user); + $this->success = $request->execute(array( + "to" => $email, + "subject" => "[$siteName] Password Reset", + "body" => $messageBody + )); + $this->lastError = $request->getLastError(); + } + + return $this->success; + } + + private function findUser($email) { + $sql = $this->user->getSQL(); + $res = $sql->select("User.uid", "User.name") + ->from("User") + ->where(new Compare("User.email", $email)) + ->where(new CondBool("User.confirmed")) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + if ($this->success) { + if (empty($res)) { + return null; + } else { + return $res[0]; + } + } + + return $this->success; + } + + private function insertToken(int $id, string $token) { + $validUntil = (new DateTime())->modify("+1 hour"); + $sql = $this->user->getSQL(); + $res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until")) + ->addRow($id, $token, "password_reset", $validUntil) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + } + + class ResetPassword extends UserAPI { + + public function __construct(User $user, $externalCall = false) { + parent::__construct($user, $externalCall, array( + 'token' => new StringType('token', 36), + 'password' => new StringType('password'), + 'confirmPassword' => new StringType('confirmPassword'), + )); + + $this->csrfTokenRequired = false; + } + + private function updateUser($uid, $password) { + $sql = $this->user->getSQL(); + $res = $sql->update("User") + ->set("password", $this->hashPassword($password)) + ->where(new Compare("uid", $uid)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + return $this->success; + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + if ($this->user->isLoggedIn()) { + return $this->createError("You are already logged in."); + } + + $token = $this->getParam("token"); + $password = $this->getParam("password"); + $confirmPassword = $this->getParam("confirmPassword"); + + $req = new CheckToken($this->user); + $this->success = $req->execute(array("token" => $token)); + $this->lastError = $req->getLastError(); + if (!$this->success) { + return false; + } + + $result = $req->getResult(); + if (strcasecmp($result["token"]["type"], "password_reset") !== 0) { + return $this->createError("Invalid token type"); + } else if (!$this->checkPasswordRequirements($password, $confirmPassword)) { + return false; + } else if (!$this->updateUser($result["user"]["uid"], $password)) { + return false; + } else { + $this->invalidateToken($token); + return true; + } + } + } +} \ No newline at end of file diff --git a/core/Api/VerifyCaptcha.class.php b/core/Api/VerifyCaptcha.class.php new file mode 100644 index 0000000..2e41c22 --- /dev/null +++ b/core/Api/VerifyCaptcha.class.php @@ -0,0 +1,67 @@ + new StringType("captcha"), + "action" => new StringType("action"), + )); + + $this->isPublic = false; + } + + public function execute($values = array()) { + if(!parent::execute($values)) { + return false; + } + + $settings = $this->user->getConfiguration()->getSettings(); + if (!$settings->isRecaptchaEnabled()) { + return $this->createError("Google reCaptcha is not enabled."); + } + + $url = "https://www.google.com/recaptcha/api/siteverify"; + $secret = $settings->getRecaptchaSecretKey(); + $captcha = $this->getParam("captcha"); + $action = $this->getParam("action"); + + $params = array( + "secret" => $secret, + "response" => $captcha + ); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL,$url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = @json_decode(curl_exec($ch), true); + curl_close ($ch); + + $this->success = false; + $this->lastError = "Could not verify captcha: No response from google received."; + + if($response) { + $this->success = $response["success"]; + if(!$this->success) { + $this->lastError = "Could not verify captcha: " . implode(";", $response["error-codes"]); + } else { + $score = $response["score"]; + if($action !== $response["action"]) { + $this->createError("Could not verify captcha: Action does not match"); + } + else if($score < 0.7) { + $this->createError("Could not verify captcha: Google ReCaptcha Score < 0.7 (Your score: $score), you are likely a bot"); + } + } + } + + return $this->success; + } +} \ No newline at end of file diff --git a/core/Api/VisitorsAPI.class.php b/core/Api/VisitorsAPI.class.php new file mode 100644 index 0000000..2489b8f --- /dev/null +++ b/core/Api/VisitorsAPI.class.php @@ -0,0 +1,88 @@ + new StringType('type', 32), + 'date' => new Parameter('date', Parameter::TYPE_DATE, true, new DateTime()) + )); + } + + private function setConditions(string $type, DateTime $date, Select $query) { + if ($type === "yearly") { + $yearStart = $date->format("Y0000"); + $yearEnd = $date->modify("+1 year")->format("Y0000"); + $query->where(new Compare("day", $yearStart, ">=")); + $query->where(new Compare("day", $yearEnd, "<")); + } else if($type === "monthly") { + $monthStart = $date->format("Ym00"); + $monthEnd = $date->modify("+1 month")->format("Ym00"); + $query->where(new Compare("day", $monthStart, ">=")); + $query->where(new Compare("day", $monthEnd, "<")); + } else if($type === "weekly") { + $weekStart = ($date->modify("monday this week"))->format("Ymd"); + $weekEnd = ($date->modify("sunday this week"))->format("Ymd"); + $query->where(new Compare("day", $weekStart, ">=")); + $query->where(new Compare("day", $weekEnd, "<=")); + } else { + $this->createError("Invalid scope: $type"); + } + } + + public function execute($values = array()) { + if (!parent::execute($values)) { + return false; + } + + $date = $this->getParam("date"); + $type = $this->getParam("type"); + + $sql = $this->user->getSQL(); + $query = $sql->select($sql->count(), "day") + ->from("Visitor") + ->where(new Compare("count", 1, ">")) + ->groupBy("day") + ->orderBy("day") + ->ascending(); + + $this->setConditions($type, $date, $query); + if (!$this->success) { + return false; + } + + $res = $query->execute(); + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->result["type"] = $type; + $this->result["visitors"] = array(); + + foreach($res as $row) { + $day = DateTime::createFromFormat("Ymd", $row["day"])->format("Y/m/d"); + $count = $row["count"]; + $this->result["visitors"][$day] = $count; + } + } + + return $this->success; + } + } +} \ No newline at end of file diff --git a/core/Configuration/.gitignore b/core/Configuration/.gitignore index 6b0cf63..bd1b2f7 100755 --- a/core/Configuration/.gitignore +++ b/core/Configuration/.gitignore @@ -1,3 +1 @@ -Mail\.class\.php -JWT\.class\.php Database\.class\.php diff --git a/core/Configuration/Configuration.class.php b/core/Configuration/Configuration.class.php index 6bdc816..6230d75 100755 --- a/core/Configuration/Configuration.class.php +++ b/core/Configuration/Configuration.class.php @@ -2,53 +2,36 @@ namespace Configuration; +use Objects\ConnectionData; + class Configuration { - private $database; - private $mail; - private $jwt; + private ?ConnectionData $database; + private Settings $settings; function __construct() { - } + $this->database = null; + $this->settings = Settings::loadDefaults(); - public function load() { - try { - - $classes = array( - \Configuration\Database::class => &$this->database, - \Configuration\Mail::class => &$this->mail, - \Configuration\JWT::class => &$this->jwt - ); - - $success = true; - foreach($classes as $class => &$ref) { - $path = getClassPath($class); - if(!file_exists($path)) { - $success = false; - } else { - include_once $path; - if(class_exists($class)) { - $ref = new $class(); - } - } + $class = \Configuration\Database::class; + $path = getClassPath($class, true); + if(file_exists($path) && is_readable($path)) { + include_once $path; + if(class_exists($class)) { + $this->database = new \Configuration\Database(); } - - return $success; - } catch(\Error $e) { - die($e); } } - public function getDatabase() { return $this->database; } - public function getJWT() { return $this->jwt; } - public function getMail() { return $this->mail; } - - public function isFilePresent($className) { - $path = getClassPath("\\Configuration\\$className"); - return file_exists($path); + public function getDatabase() : ?ConnectionData { + return $this->database; } - public function create($className, $data) { + public function getSettings() : Settings { + return $this->settings; + } + + public function create(string $className, $data) { $path = getClassPath("\\Configuration\\$className"); if($data) { @@ -59,22 +42,15 @@ class Configuration { namespace Configuration; - class $className { - - private \$key; - + class $className extends KeyData { + public function __construct() { - \$this->key = '$key'; + parent::__construct('$key'); } - - public function getKey() { - return \$this->key; - } - } - - ?>", false + + }", false ); - } else { + } else if($data instanceof ConnectionData) { $superClass = get_class($data); $host = addslashes($data->getHost()); $port = intval($data->getPort()); @@ -98,22 +74,19 @@ class Configuration { public function __construct() { parent::__construct('$host', $port, '$login', '$password');$properties } - } - - ?>", false + }", false ); + } else { + return false; } } else { - $code = intendCode( - "", false); + $code = " +} \ No newline at end of file diff --git a/core/Configuration/CreateDatabase.class.php b/core/Configuration/CreateDatabase.class.php index 3db4408..35be6fb 100755 --- a/core/Configuration/CreateDatabase.class.php +++ b/core/Configuration/CreateDatabase.class.php @@ -2,16 +2,16 @@ namespace Configuration; -use \Driver\SQL\Query\CreateTable; -use \Driver\SQL\Query\Insert; -use \Driver\SQL\Column\Column; -use \Driver\SQL\Strategy\UpdateStrategy; +use Driver\SQL\SQL; use \Driver\SQL\Strategy\SetNullStrategy; use \Driver\SQL\Strategy\CascadeStrategy; class CreateDatabase { - public static function createQueries($sql) { + // NOTE: + // explicit serial ids removed due to postgres' serial implementation + + public static function createQueries(SQL $sql) { $queries = array(); // Language @@ -23,17 +23,18 @@ class CreateDatabase { ->unique("code") ->unique("name"); - $queries[] = $sql->insert("Language", array("uid", "code", "name")) - ->addRow(1, "en_US", 'American English') - ->addRow(2, "de_DE", 'Deutsch Standard'); + $queries[] = $sql->insert("Language", array("code", "name")) + ->addRow( "en_US", 'American English') + ->addRow( "de_DE", 'Deutsch Standard'); $queries[] = $sql->createTable("User") ->addSerial("uid") ->addString("email", 64, true) ->addString("name", 32) - ->addString("salt", 16) - ->addString("password", 64) + ->addString("password", 128) + ->addBool("confirmed", false) ->addInt("language_id", true, 1) + ->addDateTime("registered_at", false, $sql->currentTimestamp()) ->primaryKey("uid") ->unique("email") ->unique("name") @@ -49,35 +50,39 @@ class CreateDatabase { ->addString("browser", 64) ->addJson("data", false, '{}') ->addBool("stay_logged_in", true) + ->addString("csrf_token", 16 ) ->primaryKey("uid", "user_id") ->foreignKey("user_id", "User", "uid", new CascadeStrategy()); $queries[] = $sql->createTable("UserToken") ->addInt("user_id") ->addString("token", 36) - ->addEnum("token_type", array("password_reset", "confirmation")) + ->addEnum("token_type", array("password_reset", "email_confirm", "invite")) ->addDateTime("valid_until") + ->addBool("used", false) ->foreignKey("user_id", "User", "uid", new CascadeStrategy()); - $queries[] = $sql->createTable("Group") ->addSerial("uid") ->addString("name", 32) + ->addString("color", 10) ->primaryKey("uid") ->unique("name"); - $queries[] = $sql->insert("Group", array("uid", "name")) - ->addRow(USER_GROUP_DEFAULT, "Default") - ->addRow(USER_GROUP_ADMIN, "Administrator"); + $queries[] = $sql->insert("Group", array("name", "color")) + ->addRow(USER_GROUP_MODERATOR_NAME, "#007bff") + ->addRow(USER_GROUP_SUPPORT_NAME, "#28a745") + ->addRow(USER_GROUP_ADMIN_NAME, "#dc3545"); $queries[] = $sql->createTable("UserGroup") ->addInt("user_id") ->addInt("group_id") ->unique("user_id", "group_id") - ->foreignKey("user_id", "User", "uid") - ->foreignKey("group_id", "Group", "uid"); + ->foreignKey("user_id", "User", "uid", new CascadeStrategy()) + ->foreignKey("group_id", "Group", "uid", new CascadeStrategy()); $queries[] = $sql->createTable("Notification") ->addSerial("uid") + ->addEnum("type", array("default","message","warning"), false, "default") ->addDateTime("created_at", false, $sql->currentTimestamp()) ->addString("title", 32) ->addString("message", 256) @@ -86,7 +91,7 @@ class CreateDatabase { $queries[] = $sql->createTable("UserNotification") ->addInt("user_id") ->addInt("notification_id") - ->addBool("seen") + ->addBool("seen", false) ->foreignKey("user_id", "User", "uid") ->foreignKey("notification_id", "Notification", "uid") ->unique("user_id", "notification_id"); @@ -94,7 +99,7 @@ class CreateDatabase { $queries[] = $sql->createTable("GroupNotification") ->addInt("group_id") ->addInt("notification_id") - ->addBool("seen") + ->addBool("seen", false) ->foreignKey("group_id", "Group", "uid") ->foreignKey("notification_id", "Notification", "uid") ->unique("group_id", "notification_id"); @@ -108,8 +113,116 @@ class CreateDatabase { ->primaryKey("uid") ->foreignKey("user_id", "User", "uid"); + $queries[] = $sql->createTable("Visitor") + ->addInt("day") + ->addInt("count", false, 1) + ->addString("cookie", 26) + ->unique("day", "cookie"); + + $queries[] = $sql->createTable("Route") + ->addSerial("uid") + ->addString("request", 128) + ->addEnum("action", array("redirect_temporary", "redirect_permanently", "static", "dynamic")) + ->addString("target", 128) + ->addString("extra", 64, true) + ->addBool("active", true) + ->primaryKey("uid"); + + $queries[] = $sql->insert("Route", array("request", "action", "target", "extra")) + ->addRow("^/admin(/.*)?$", "dynamic", "\\Documents\\Admin", NULL) + ->addRow("^/register(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\Register") + ->addRow("^/confirmEmail(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ConfirmEmail") + ->addRow("^/acceptInvite(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\AcceptInvite") + ->addRow("^/resetPassword(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ResetPassword") + ->addRow("^/$", "static", "/static/welcome.html", NULL); + + $queries[] = $sql->createTable("Settings") + ->addString("name", 32) + ->addString("value", 1024, true) + ->addBool("private", false) // these values are not returned from '/api/settings/get', but can be changed + ->addBool("readonly", false) // these values are neither returned, nor can be changed from outside + ->primaryKey("name"); + + $settingsQuery = $sql->insert("Settings", array("name", "value", "private", "readonly")) + // ->addRow("mail_enabled", "0") # this key will be set during installation + ->addRow("mail_host", "", false, false) + ->addRow("mail_port", "", false, false) + ->addRow("mail_username", "", false, false) + ->addRow("mail_password", "", true, false) + ->addRow("mail_from", "", false, false) + ->addRow("message_confirm_email", self::MessageConfirmEmail(), false, false) + ->addRow("message_accept_invite", self::MessageAcceptInvite(), false, false) + ->addRow("message_reset_password", self::MessageResetPassword(), false, false); + + (Settings::loadDefaults())->addRows($settingsQuery); + $queries[] = $settingsQuery; + + $queries[] = $sql->createTable("ContactRequest") + ->addSerial("uid") + ->addString("from_name", 32) + ->addString("from_email", 64) + ->addString("message", 512) + ->addDateTime("created_at", false, $sql->currentTimestamp()) + ->primaryKey("uid"); + + $queries[] = $sql->createTable("ApiPermission") + ->addString("method", 32) + ->addJson("groups", true, '[]') + ->addString("description", 128, false, "") + ->primaryKey("method"); + + $queries[] = $sql->insert("ApiPermission", array("method", "groups", "description")) + ->addRow("ApiKey/create", array(), "Allows users to create API-Keys for themselves") + ->addRow("ApiKey/fetch", array(), "Allows users to list their API-Keys") + ->addRow("ApiKey/refresh", array(), "Allows users to refresh their API-Keys") + ->addRow("ApiKey/revoke", array(), "Allows users to revoke their API-Keys") + ->addRow("Groups/fetch", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to list all available groups") + ->addRow("Groups/create", array(USER_GROUP_ADMIN), "Allows users to create a new groups") + ->addRow("Groups/delete", array(USER_GROUP_ADMIN), "Allows users to delete a group") + ->addRow("Routes/fetch", array(USER_GROUP_ADMIN), "Allows users to list all configured routes") + ->addRow("Routes/save", array(USER_GROUP_ADMIN), "Allows users to create, delete and modify routes") + ->addRow("Mail/test", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to send a test email to a given address") + ->addRow("Settings/get", array(USER_GROUP_ADMIN), "Allows users to fetch server settings") + ->addRow("Settings/set", array(USER_GROUP_ADMIN), "Allows users create, delete or modify server settings") + ->addRow("Stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to fetch server stats") + ->addRow("User/create", array(USER_GROUP_ADMIN), "Allows users to create a new user, email address does not need to be confirmed") + ->addRow("User/fetch", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to list all registered users") + ->addRow("User/get", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to get information about a single user") + ->addRow("User/invite", array(USER_GROUP_ADMIN), "Allows users to create a new user and send them an invitation link") + ->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user") + ->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user") + ->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions") + ->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics"); + return $queries; } -} -?> + private static function MessageConfirmEmail() : string { + return "Hello {{username}},
" . + "You recently created an account on {{site_name}}. Please click on the following link to " . + "confirm your email address and complete your registration. If you haven't registered an " . + "account, you can simply ignore this email. The link is valid for the next 48 hours:

" . + "{{link}}

" . + "Best Regards
" . + "{{site_name}} Administration"; + } + + private static function MessageAcceptInvite() : string { + return "Hello {{username}},
" . + "You were invited to create an account on {{site_name}}. Please click on the following link to " . + "confirm your email address and complete your registration by choosing a new password. " . + "If you want to decline the invitation, you can simply ignore this email. The link is valid for the next 7 days:

" . + "{{link}}

" . + "Best Regards
" . + "{{site_name}} Administration"; + } + + private static function MessageResetPassword() : string { + return "Hello {{username}},
" . + "you requested a password reset on {{site_name}}. Please click on the following link to " . + "choose a new password. If this request was not intended, you can simply ignore the email. The Link is valid for one hour:

" . + "{{link}}

" . + "Best Regards
" . + "{{site_name}} Administration"; + } +} diff --git a/core/Configuration/Settings.class.php b/core/Configuration/Settings.class.php new file mode 100644 index 0000000..27904a5 --- /dev/null +++ b/core/Configuration/Settings.class.php @@ -0,0 +1,107 @@ +jwtSecret; + } + + public function isInstalled() { + return $this->installationComplete; + } + + public static function loadDefaults() : Settings { + $hostname = $_SERVER["SERVER_NAME"]; + $protocol = getProtocol(); + $jwt = generateRandomString(32); + + $settings = new Settings(); + $settings->siteName = "WebBase"; + $settings->baseUrl = "$protocol://$hostname"; + $settings->jwtSecret = $jwt; + $settings->installationComplete = false; + $settings->registrationAllowed = false; + $settings->recaptchaPublicKey = ""; + $settings->recaptchaPrivateKey = ""; + $settings->recaptchaEnabled = false; + return $settings; + } + + public function loadFromDatabase(User $user) { + $req = new \Api\Settings\Get($user); + $success = $req->execute(); + + if ($success) { + $result = $req->getResult()["settings"]; + $this->siteName = $result["site_name"] ?? $this->siteName; + $this->registrationAllowed = $result["user_registration_enabled"] ?? $this->registrationAllowed; + $this->installationComplete = $result["installation_completed"] ?? $this->installationComplete; + $this->jwtSecret = $result["jwt_secret"] ?? $this->jwtSecret; + $this->recaptchaEnabled = $result["recaptcha_enabled"] ?? $this->recaptchaEnabled; + $this->recaptchaPublicKey = $result["recaptcha_public_key"] ?? $this->recaptchaPublicKey; + $this->recaptchaPrivateKey = $result["recaptcha_private_key"] ?? $this->recaptchaPrivateKey; + + if (!isset($result["jwt_secret"])) { + $req = new \Api\Settings\Set($user); + $req->execute(array("settings" => array( + "jwt_secret" => $this->jwtSecret + ))); + } + } + + return false; + } + + public function addRows(Insert $query) { + $query->addRow("site_name", $this->siteName, false, false) + ->addRow("base_url", $this->baseUrl, false, false) + ->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0", false, false) + ->addRow("installation_completed", $this->installationComplete ? "1" : "0", true, true) + ->addRow("jwt_secret", $this->jwtSecret, true, true) + ->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false) + ->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false) + ->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false); + } + + public function getSiteName() : string { + return $this->siteName; + } + + public function getBaseUrl() : string { + return $this->baseUrl; + } + + public function isRecaptchaEnabled() : bool { + return $this->recaptchaEnabled; + } + + public function getRecaptchaSiteKey() : string { + return $this->recaptchaPublicKey; + } + + public function getRecaptchaSecretKey() : string { + return $this->recaptchaPrivateKey; + } + + public function isRegistrationAllowed() : bool { + return $this->registrationAllowed; + } +} \ No newline at end of file diff --git a/core/Documents/Account.class.php b/core/Documents/Account.class.php new file mode 100644 index 0000000..8b811a6 --- /dev/null +++ b/core/Documents/Account.class.php @@ -0,0 +1,72 @@ +loadJQuery(); + $this->addJS(Script::CORE); + $this->addJS(Script::ACCOUNT); + $this->loadBootstrap(); + $this->loadFontawesome(); + } + + protected function initMetas() { + return array( + array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'), + array('name' => 'format-detection', 'content' => 'telephone=yes'), + array('charset' => 'utf-8'), + array("http-equiv" => 'expires', 'content' => '0'), + array("name" => 'robots', 'content' => 'noarchive'), + ); + } + + protected function initRawFields() { + return array(); + } + + protected function initTitle() { + return "Account"; + } + } + + class AccountBody extends SimpleBody { + + public function __construct($document) { + parent::__construct($document); + } + + protected function getContent() { + + $view = $this->getDocument()->getView(); + if ($view === null) { + return "The page you does not exist or is no longer valid. Return to start page"; + } + + return $view->getCode(); + } + } +} \ No newline at end of file diff --git a/core/Documents/Admin.class.php b/core/Documents/Admin.class.php index 79ce7bc..662e0da 100644 --- a/core/Documents/Admin.class.php +++ b/core/Documents/Admin.class.php @@ -1,29 +1,35 @@ isLoggedIn() ? AdminDashboardBody::class : LoginBody::class; + parent::__construct($user, AdminHead::class, $body, $view); } } } namespace Documents\Admin { - class Head extends \Elements\Head { + use Elements\Head; + use Elements\Link; + use Elements\Script; + + class AdminHead extends Head { public function __construct($document) { parent::__construct($document); } protected function initSources() { - $this->loadJQuery(); - $this->loadBootstrap(); $this->loadFontawesome(); - $this->addJS(\Elements\Script::CORE); - $this->addCSS(\Elements\Link::CORE); - $this->addJS(\Elements\Script::ADMIN); - $this->addCSS(\Elements\Link::ADMIN); } protected function initMetas() { @@ -44,26 +50,4 @@ namespace Documents\Admin { return "WebBase - Administration"; } } - - class Body extends \Elements\Body { - - public function __construct($document) { - parent::__construct($document); - } - - public function getCode() { - $html = parent::getCode(); - - $document = $this->getDocument(); - if(!$document->getUser()->isLoggedIn()) { - $html .= new \Views\Login($document); - } else { - $html .= new \Views\Admin($document); - } - - return $html; - } - } -} - -?> +} \ No newline at end of file diff --git a/core/Documents/Document404.class.php b/core/Documents/Document404.class.php index 581fcf5..fa97af3 100644 --- a/core/Documents/Document404.class.php +++ b/core/Documents/Document404.class.php @@ -1,27 +1,32 @@ loadJQuery(); - // $this->loadBootstrap(); - // $this->loadFontawesome(); - // $this->addJS(\Elements\Script::CORE); - // $this->addCSS(\Elements\Link::CORE); } protected function initMetas() { @@ -43,18 +48,18 @@ namespace Documents\Document404 { } } - class Body404 extends \Elements\Body { + class Body404 extends SimpleBody { public function __construct($document) { parent::__construct($document); } - public function getCode() { - $html = parent::getCode(); - $html .= "404 Not Found"; - return $html; + public function loadView() { + http_response_code(404); + } + + protected function getContent() { + return $this->load(View404::class); } } } - -?> diff --git a/core/Documents/Install.class.php b/core/Documents/Install.class.php index 94aa1a5..a7d419f 100644 --- a/core/Documents/Install.class.php +++ b/core/Documents/Install.class.php @@ -1,9 +1,14 @@ databaseRequired = false; } } @@ -11,7 +16,17 @@ namespace Documents { namespace Documents\Install { - class Head extends \Elements\Head { + use Configuration\CreateDatabase; + use Driver\SQL\SQL; + use Elements\Body; + use Elements\Head; + use Elements\Link; + use Elements\Script; + use External\PHPMailer\Exception; + use External\PHPMailer\PHPMailer; + use Objects\ConnectionData; + + class InstallHead extends Head { public function __construct($document) { parent::__construct($document); @@ -21,9 +36,9 @@ namespace Documents\Install { $this->loadJQuery(); $this->loadBootstrap(); $this->loadFontawesome(); - $this->addJS(\Elements\Script::CORE); - $this->addCSS(\Elements\Link::CORE); - $this->addJS(\Elements\Script::INSTALL); + $this->addJS(Script::CORE); + $this->addCSS(Link::CORE); + $this->addJS(Script::INSTALL); } protected function initMetas() { @@ -46,27 +61,31 @@ namespace Documents\Install { } - class Body extends \Elements\Body { + class InstallBody extends Body { // Status enum const NOT_STARTED = 0; const PENDING = 1; - const SUCCESFULL = 2; + const SUCCESSFUL = 2; const ERROR = 3; // Step enum - const CHECKING_REQUIRMENTS = 1; + const CHECKING_REQUIREMENTS = 1; const DATABASE_CONFIGURATION = 2; const CREATE_USER = 3; const ADD_MAIL_SERVICE = 4; const FINISH_INSTALLATION = 5; // - private $errorString; + private string $errorString; + private int $currentStep; + private array $steps; function __construct($document) { parent::__construct($document); $this->errorString = ""; + $this->currentStep = InstallBody::CHECKING_REQUIREMENTS; + $this->steps = array(); } private function getParameter($name) { @@ -80,7 +99,7 @@ namespace Documents\Install { private function getCurrentStep() { if(!$this->checkRequirements()["success"]) { - return self::CHECKING_REQUIRMENTS; + return self::CHECKING_REQUIREMENTS; } $user = $this->getDocument()->getUser(); @@ -92,6 +111,10 @@ namespace Documents\Install { } $sql = $user->getSQL(); + if(!$sql || !$sql->isConnected()) { + return self::DATABASE_CONFIGURATION; + } + $countKeyword = $sql->count(); $res = $sql->select($countKeyword)->from("User")->execute(); if ($res === FALSE) { @@ -104,19 +127,28 @@ namespace Documents\Install { } } - if($step === self::ADD_MAIL_SERVICE && $config->isFilePresent("Mail")) { - $step = self::FINISH_INSTALLATION; - if(!$config->isFilePresent("JWT") && !$config->create("JWT", generateRandomString(32))) { - $this->errorString = "Unable to create jwt file"; - } else { - $req = new \Api\Notifications\Create($user); - $success = $req->execute(array( - "title" => "Welcome", - "message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!", - "groupId" => USER_GROUP_ADMIN) - ); + if ($step === self::ADD_MAIL_SERVICE) { + $req = new \Api\Settings\Get($user); + $success = $req->execute(array("key" => "^mail_enabled$")); + if (!$success) { + $this->errorString = $req->getLastError(); + return self::DATABASE_CONFIGURATION; + } else if (isset($req->getResult()["settings"]["mail_enabled"])) { + $step = self::FINISH_INSTALLATION; + + $req = new \Api\Settings\Set($user); + $success = $req->execute(array("settings" => array("installation_completed" => "1"))); if (!$success) { $this->errorString = $req->getLastError(); + } else { + $req = new \Api\Notifications\Create($user); + $req->execute(array( + "title" => "Welcome", + "message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!", + "groupId" => USER_GROUP_ADMIN + ) + ); + $this->errorString = $req->getLastError(); } } } @@ -145,8 +177,8 @@ namespace Documents\Install { } } - if(version_compare(PHP_VERSION, '7.1', '<')) { - $failedRequirements[] = "PHP Version >= 7.1 is required. Got: " . PHP_VERSION . ""; + if(version_compare(PHP_VERSION, '7.4', '<')) { + $failedRequirements[] = "PHP Version >= 7.4 is required. Got: " . PHP_VERSION . ""; $success = false; } @@ -213,16 +245,16 @@ namespace Documents\Install { $msg = "Unsupported database type. Must be one of: " . implode(", ", $supportedTypes); $success = false; } else { - $connectionData = new \Objects\ConnectionData($host, $port, $username, $password); + $connectionData = new ConnectionData($host, $port, $username, $password); $connectionData->setProperty('database', $database); $connectionData->setProperty('encoding', $encoding); $connectionData->setProperty('type', $type); - $sql = \Driver\SQL\SQL::createConnection($connectionData); + $sql = SQL::createConnection($connectionData); $success = false; - if(!($sql instanceof \Driver\SQL\SQL)) { - $msg = "Error connecting to database: " . str($sql); + if(is_string($sql)) { + $msg = "Error connecting to database: $sql"; } else if(!$sql->isConnected()) { - if (!$sql->checkRequirements()["success"]) { + if (!$sql->checkRequirements()) { $driverName = $sql->getDriverName(); $installLink = "https://www.php.net/manual/en/$driverName.setup.php"; $link = $this->createExternalLink($installLink); @@ -234,7 +266,7 @@ namespace Documents\Install { $msg = ""; $success = true; - $queries = \Configuration\CreateDatabase::createQueries($sql); + $queries = CreateDatabase::createQueries($sql); foreach($queries as $query) { if (!($res = $query->execute())) { $msg = "Error creating tables: " . $sql->getLastError(); @@ -243,7 +275,8 @@ namespace Documents\Install { } } - if($success && !$this->getDocument()->getUser()->getConfiguration()->create("Database", $connectionData)) { + $config = $this->getDocument()->getUser()->getConfiguration(); + if(!$config->create("Database", $connectionData)) { $success = false; $msg = "Unable to write file"; } @@ -269,8 +302,8 @@ namespace Documents\Install { $username = $this->getParameter("username"); $password = $this->getParameter("password"); $confirmPassword = $this->getParameter("confirmPassword"); + $email = $this->getParameter("email") ?? ""; - $msg = $this->errorString; $success = true; $missingInputs = array(); @@ -292,29 +325,23 @@ namespace Documents\Install { if(!$success) { $msg = "Please fill out the following inputs:
" . $this->createUnorderedList($missingInputs); - } else if(strlen($username) < 5 || strlen($username) > 32) { - $msg = "The username should be between 5 and 32 characters long"; - $success = false; - } else if(strcmp($password, $confirmPassword) !== 0) { - $msg = "The given passwords do not match"; - $success = false; - } else if(strlen($password) < 6) { - $msg = "The password should be at least 6 characters long"; - $success = false; } else { - $salt = generateRandomString(16); - $hash = hash('sha256', $password . $salt); $sql = $user->getSQL(); + $req = new \Api\User\Create($user); + $success = $req->execute(array( + 'username' => $username, + 'email' => $email, + 'password' => $password, + 'confirmPassword' => $confirmPassword, + )); - $success = $sql->insert("User", array("name", "salt", "password")) - ->addRow($username, $salt, $hash) - ->returning("uid") - ->execute() - && $sql->insert("UserGroup", array("group_id", "user_id")) - ->addRow(USER_GROUP_ADMIN, $sql->getLastInsertId()) - ->execute(); - - $msg = $sql->getLastError(); + $msg = $req->getLastError(); + if ($success) { + $success = $sql->insert("UserGroup", array("group_id", "user_id")) + ->addRow(USER_GROUP_ADMIN, $req->getResult()["userId"]) + ->execute(); + $msg = $sql->getLastError(); + } } return array("msg" => $msg, "success" => $success); @@ -333,10 +360,9 @@ namespace Documents\Install { $success = true; $msg = $this->errorString; if($this->getParameter("skip") === "true") { - if(!$user->getConfiguration()->create("Mail", null)) { - $success = false; - $msg = "Unable to create file"; - } + $req = new \Api\Settings\Set($user); + $success = $req->execute(array("settings" => array( "mail_enabled" => "0" ))); + $msg = $req->getLastError(); } else { $address = $this->getParameter("address"); @@ -375,7 +401,7 @@ namespace Documents\Install { } else { $success = false; - $mail = new \External\PHPMailer\PHPMailer(true); + $mail = new PHPMailer(true); $mail->IsSMTP(); $mail->SMTPAuth = true; $mail->Username = $username; @@ -395,16 +421,20 @@ namespace Documents\Install { $msg = ""; $mail->smtpClose(); } - } catch(\External\PHPMailer\Exception $error) { + } catch(Exception $error) { $msg = "Could not connect to SMTP Server: " . $error->errorMessage(); } if($success) { - $connectionData = new \Objects\ConnectionData($address, $port, $username, $password); - if(!$user->getConfiguration()->create("Mail", $connectionData)) { - $success = false; - $msg = "Unable to create file"; - } + $req = new \Api\Settings\Set($user); + $success = $req->execute(array("settings" => array( + "mail_enabled" => "1", + "mail_host" => "$address", + "mail_port" => "$port", + "mail_username" => "$username", + "mail_password" => "$password", + ))); + $msg = $req->getLastError(); } } } @@ -416,7 +446,7 @@ namespace Documents\Install { switch($this->currentStep) { - case self::CHECKING_REQUIRMENTS: + case self::CHECKING_REQUIREMENTS: return $this->checkRequirements(); case self::DATABASE_CONFIGURATION: @@ -446,26 +476,26 @@ namespace Documents\Install { switch($status) { case self::PENDING: - $statusIcon = ''; + $statusIcon = $this->createIcon("spinner"); $statusText = "Loading…"; $statusColor = "muted"; break; - case self::SUCCESFULL: - $statusIcon = ''; - $statusText = "Successfull"; + case self::SUCCESSFUL: + $statusIcon = $this->createIcon("check-circle"); + $statusText = "Successful"; $statusColor = "success"; break; case self::ERROR: - $statusIcon = ''; + $statusIcon = $this->createIcon("times-circle"); $statusText = "Failed"; $statusColor = "danger"; break; case self::NOT_STARTED: default: - $statusIcon = ''; + $statusIcon = $this->createIcon("circle", "far"); $statusText = "Pending"; $statusColor = "muted"; break; @@ -552,7 +582,7 @@ namespace Documents\Install { private function createProgessMainview() { $views = array( - self::CHECKING_REQUIRMENTS => array( + self::CHECKING_REQUIREMENTS => array( "title" => "Application Requirements", "progressText" => "Checking requirements, please wait a moment…" ), @@ -585,6 +615,7 @@ namespace Documents\Install { "title" => "Create a User", "form" => array( array("title" => "Username", "name" => "username", "type" => "text", "required" => true), + array("title" => "Email", "name" => "email", "type" => "text"), array("title" => "Password", "name" => "password", "type" => "password", "required" => true), array("title" => "Confirm Password", "name" => "confirmPassword", "type" => "password", "required" => true), ), @@ -661,7 +692,7 @@ namespace Documents\Install { ); if($this->currentStep != self::FINISH_INSTALLATION) { - if ($this->currentStep == self::CHECKING_REQUIRMENTS) { + if ($this->currentStep == self::CHECKING_REQUIREMENTS) { $buttons[] = array("title" => "Retry", "type" => "success", "id" => "btnRetry", "float" => "right"); } else { $buttons[] = array("title" => "Submit", "type" => "success", "id" => "btnSubmit", "float" => "right"); @@ -683,7 +714,7 @@ namespace Documents\Install { $id = $button["id"]; $float = $button["float"]; $disabled = (isset($button["disabled"]) && $button["disabled"]) ? " disabled" : ""; - $button = ""; + $button = ""; if($float === "left") { $buttonsLeft .= $button; @@ -701,12 +732,11 @@ namespace Documents\Install { return $html; } - function getCode() { $html = parent::getCode(); $this->steps = array( - self::CHECKING_REQUIRMENTS => array( + self::CHECKING_REQUIREMENTS => array( "title" => "Checking requirements", "status" => self::ERROR ), @@ -731,12 +761,12 @@ namespace Documents\Install { $this->currentStep = $this->getCurrentStep(); // set status - for($step = self::CHECKING_REQUIRMENTS; $step < $this->currentStep; $step++) { - $this->steps[$step]["status"] = self::SUCCESFULL; + for($step = self::CHECKING_REQUIREMENTS; $step < $this->currentStep; $step++) { + $this->steps[$step]["status"] = self::SUCCESSFUL; } if($this->currentStep == self::FINISH_INSTALLATION) { - $this->steps[$this->currentStep]["status"] = self::SUCCESFULL; + $this->steps[$this->currentStep]["status"] = self::SUCCESSFUL; } // POST @@ -748,6 +778,7 @@ namespace Documents\Install { $progressSidebar = $this->createProgressSidebar(); $progressMainview = $this->createProgessMainview(); + $errorStyle = ($this->errorString ? '' : ' style="display:none"'); $errorClass = ($this->errorString ? ' alert-danger' : ''); @@ -773,7 +804,7 @@ namespace Documents\Install {
$progressMainview -
$this->errorString
+
$this->errorString
@@ -781,9 +812,5 @@ namespace Documents\Install { return $html; } - } - } - -?> diff --git a/core/Driver/SQL/Column/BoolColumn.class.php b/core/Driver/SQL/Column/BoolColumn.class.php index 8c91230..759abdb 100644 --- a/core/Driver/SQL/Column/BoolColumn.class.php +++ b/core/Driver/SQL/Column/BoolColumn.class.php @@ -9,5 +9,3 @@ class BoolColumn extends Column { } } - -?> diff --git a/core/Driver/SQL/Column/Column.class.php b/core/Driver/SQL/Column/Column.class.php index 658a011..1693928 100644 --- a/core/Driver/SQL/Column/Column.class.php +++ b/core/Driver/SQL/Column/Column.class.php @@ -4,8 +4,8 @@ namespace Driver\SQL\Column; class Column { - private $name; - private $nullable; + private string $name; + private bool $nullable; private $defaultValue; public function __construct($name, $nullable = false, $defaultValue = NULL) { @@ -18,6 +18,4 @@ class Column { public function notNull() { return !$this->nullable; } public function getDefaultValue() { return $this->defaultValue; } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Column/DateTimeColumn.class.php b/core/Driver/SQL/Column/DateTimeColumn.class.php index 7c41ab9..c7310c9 100644 --- a/core/Driver/SQL/Column/DateTimeColumn.class.php +++ b/core/Driver/SQL/Column/DateTimeColumn.class.php @@ -7,6 +7,4 @@ class DateTimeColumn extends Column { public function __construct($name, $nullable=false, $defaultValue=NULL) { parent::__construct($name, $nullable, $defaultValue); } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Column/EnumColumn.class.php b/core/Driver/SQL/Column/EnumColumn.class.php index e5744a4..833d536 100644 --- a/core/Driver/SQL/Column/EnumColumn.class.php +++ b/core/Driver/SQL/Column/EnumColumn.class.php @@ -4,7 +4,7 @@ namespace Driver\SQL\Column; class EnumColumn extends Column { - private $values; + private array $values; public function __construct($name, $values, $nullable=false, $defaultValue=NULL) { parent::__construct($name, $nullable, $defaultValue); @@ -13,5 +13,3 @@ class EnumColumn extends Column { public function getValues() { return $this->values; } } - -?> diff --git a/core/Driver/SQL/Column/IntColumn.class.php b/core/Driver/SQL/Column/IntColumn.class.php index 8c904a2..2cea131 100644 --- a/core/Driver/SQL/Column/IntColumn.class.php +++ b/core/Driver/SQL/Column/IntColumn.class.php @@ -9,5 +9,3 @@ class IntColumn extends Column { } } - -?> diff --git a/core/Driver/SQL/Column/JsonColumn.class.php b/core/Driver/SQL/Column/JsonColumn.class.php index 748237c..bc7ad92 100644 --- a/core/Driver/SQL/Column/JsonColumn.class.php +++ b/core/Driver/SQL/Column/JsonColumn.class.php @@ -8,6 +8,4 @@ class JsonColumn extends Column { parent::__construct($name, $nullable, $defaultValue); } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Column/SerialColumn.class.php b/core/Driver/SQL/Column/SerialColumn.class.php index 2e25825..5e1a6d3 100644 --- a/core/Driver/SQL/Column/SerialColumn.class.php +++ b/core/Driver/SQL/Column/SerialColumn.class.php @@ -8,6 +8,4 @@ class SerialColumn extends Column { parent::__construct($name, false, $defaultValue); # not nullable } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Column/StringColumn.class.php b/core/Driver/SQL/Column/StringColumn.class.php index 381580f..7ac78f7 100644 --- a/core/Driver/SQL/Column/StringColumn.class.php +++ b/core/Driver/SQL/Column/StringColumn.class.php @@ -4,7 +4,7 @@ namespace Driver\SQL\Column; class StringColumn extends Column { - private $maxSize; + private ?int $maxSize; public function __construct($name, $maxSize=null, $nullable=false, $defaultValue=null) { parent::__construct($name, $nullable, $defaultValue); @@ -12,6 +12,4 @@ class StringColumn extends Column { } public function getMaxSize() { return $this->maxSize; } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Condition/Compare.class.php b/core/Driver/SQL/Condition/Compare.class.php index 93a32eb..6ed475b 100644 --- a/core/Driver/SQL/Condition/Compare.class.php +++ b/core/Driver/SQL/Condition/Compare.class.php @@ -4,6 +4,10 @@ namespace Driver\SQL\Condition; class Compare extends Condition { + private string $operator; + private string $column; + private $value; + public function __construct($col, $val, $operator='=') { $this->operator = $operator; $this->column = $col; @@ -14,6 +18,4 @@ class Compare extends Condition { public function getValue() { return $this->value; } public function getOperator() { return $this->operator; } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Condition/CondAnd.class.php b/core/Driver/SQL/Condition/CondAnd.class.php index 7616c90..4c5f2c7 100644 --- a/core/Driver/SQL/Condition/CondAnd.class.php +++ b/core/Driver/SQL/Condition/CondAnd.class.php @@ -4,13 +4,11 @@ namespace Driver\SQL\Condition; class CondAnd extends Condition { - private $conditions; + private array $conditions; public function __construct(...$conditions) { $this->conditions = $conditions; } public function getConditions() { return $this->conditions; } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Condition/CondBool.class.php b/core/Driver/SQL/Condition/CondBool.class.php index d374ea8..8d68941 100644 --- a/core/Driver/SQL/Condition/CondBool.class.php +++ b/core/Driver/SQL/Condition/CondBool.class.php @@ -4,12 +4,12 @@ namespace Driver\SQL\Condition; class CondBool extends Condition { + private $value; + public function __construct($val) { $this->value = $val; } public function getValue() { return $this->value; } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Condition/CondIn.class.php b/core/Driver/SQL/Condition/CondIn.class.php new file mode 100644 index 0000000..0968413 --- /dev/null +++ b/core/Driver/SQL/Condition/CondIn.class.php @@ -0,0 +1,17 @@ +column = $column; + $this->expression = $expression; + } + + public function getColumn() { return $this->column; } + public function getExpression() { return $this->expression; } +} \ No newline at end of file diff --git a/core/Driver/SQL/Condition/CondKeyword.class.php b/core/Driver/SQL/Condition/CondKeyword.class.php new file mode 100644 index 0000000..a153da6 --- /dev/null +++ b/core/Driver/SQL/Condition/CondKeyword.class.php @@ -0,0 +1,20 @@ +leftExpression = $leftExpression; + $this->rightExpression = $rightExpression; + $this->keyword = $keyword; + } + + public function getLeftExp() { return $this->leftExpression; } + public function getRightExp() { return $this->rightExpression; } + public function getKeyword() { return $this->keyword; } +} \ No newline at end of file diff --git a/core/Driver/SQL/Condition/CondLike.class.php b/core/Driver/SQL/Condition/CondLike.class.php new file mode 100644 index 0000000..1e7b949 --- /dev/null +++ b/core/Driver/SQL/Condition/CondLike.class.php @@ -0,0 +1,10 @@ +expression = $expression; + } + + public function getExpression() { + return $this->expression; + } +} \ No newline at end of file diff --git a/core/Driver/SQL/Condition/CondOr.class.php b/core/Driver/SQL/Condition/CondOr.class.php index c145632..b92ab6b 100644 --- a/core/Driver/SQL/Condition/CondOr.class.php +++ b/core/Driver/SQL/Condition/CondOr.class.php @@ -4,13 +4,11 @@ namespace Driver\SQL\Condition; class CondOr extends Condition { - private $conditions; + private array $conditions; public function __construct(...$conditions) { - $this->conditions = $conditions; + $this->conditions = (!empty($conditions) && is_array($conditions[0])) ? $conditions[0] : $conditions; } public function getConditions() { return $this->conditions; } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Condition/CondRegex.class.php b/core/Driver/SQL/Condition/CondRegex.class.php new file mode 100644 index 0000000..1147bce --- /dev/null +++ b/core/Driver/SQL/Condition/CondRegex.class.php @@ -0,0 +1,11 @@ + +} \ No newline at end of file diff --git a/core/Driver/SQL/Constraint/Constraint.class.php b/core/Driver/SQL/Constraint/Constraint.class.php index 83afe69..09e90fd 100644 --- a/core/Driver/SQL/Constraint/Constraint.class.php +++ b/core/Driver/SQL/Constraint/Constraint.class.php @@ -4,13 +4,11 @@ namespace Driver\SQL\Constraint; abstract class Constraint { - private $columnName; + private array $columnNames; - public function __construct($columnName) { - $this->columnName = $columnName; + public function __construct($columnNames) { + $this->columnNames = (!is_array($columnNames) ? array($columnNames) : $columnNames); } - public function getColumnName() { return $this->columnName; } -}; - -?> + public function getColumnNames() { return $this->columnNames; } +} \ No newline at end of file diff --git a/core/Driver/SQL/Constraint/ForeignKey.class.php b/core/Driver/SQL/Constraint/ForeignKey.class.php index 9b88782..5f46366 100644 --- a/core/Driver/SQL/Constraint/ForeignKey.class.php +++ b/core/Driver/SQL/Constraint/ForeignKey.class.php @@ -2,11 +2,13 @@ namespace Driver\SQL\Constraint; +use Driver\SQL\Strategy\Strategy; + class ForeignKey extends Constraint { - private $referencedTable; - private $referencedColumn; - private $strategy; + private string $referencedTable; + private string $referencedColumn; + private ?Strategy $strategy; public function __construct($name, $refTable, $refColumn, $strategy = NULL) { parent::__construct($name); @@ -18,6 +20,4 @@ class ForeignKey extends Constraint { public function getReferencedTable() { return $this->referencedTable; } public function getReferencedColumn() { return $this->referencedColumn; } public function onDelete() { return $this->strategy; } -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Constraint/PrimaryKey.class.php b/core/Driver/SQL/Constraint/PrimaryKey.class.php index 1bb7683..b7398df 100644 --- a/core/Driver/SQL/Constraint/PrimaryKey.class.php +++ b/core/Driver/SQL/Constraint/PrimaryKey.class.php @@ -8,6 +8,4 @@ class PrimaryKey extends Constraint { parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names); } -}; - -?> +} diff --git a/core/Driver/SQL/Constraint/Unique.class.php b/core/Driver/SQL/Constraint/Unique.class.php index 022062d..c45d1fe 100644 --- a/core/Driver/SQL/Constraint/Unique.class.php +++ b/core/Driver/SQL/Constraint/Unique.class.php @@ -8,6 +8,4 @@ class Unique extends Constraint { parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names); } -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Expression/Add.class.php b/core/Driver/SQL/Expression/Add.class.php new file mode 100644 index 0000000..61faffb --- /dev/null +++ b/core/Driver/SQL/Expression/Add.class.php @@ -0,0 +1,13 @@ +tpye = $type; + $this->type = $type; $this->table = $table; $this->columnA = $columnA; $this->columnB = $columnB; @@ -21,6 +21,4 @@ class Join { public function getColumnA() { return $this->columnA; } public function getColumnB() { return $this->columnB; } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Keyword.class.php b/core/Driver/SQL/Keyword.class.php index 16bb6c1..d613885 100644 --- a/core/Driver/SQL/Keyword.class.php +++ b/core/Driver/SQL/Keyword.class.php @@ -4,7 +4,7 @@ namespace Driver\SQL; class Keyword { - private $value; + private string $value; public function __construct($value) { $this->value = $value; @@ -12,6 +12,4 @@ class Keyword { public function getValue() { return $this->value; } -} - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/MySQL.class.php b/core/Driver/SQL/MySQL.class.php index 91505e7..7686ca1 100644 --- a/core/Driver/SQL/MySQL.class.php +++ b/core/Driver/SQL/MySQL.class.php @@ -13,9 +13,9 @@ use \Driver\SQL\Column\DateTimeColumn; use Driver\SQL\Column\BoolColumn; use Driver\SQL\Column\JsonColumn; -use \Driver\SQL\Strategy\CascadeStrategy; -use \Driver\SQL\Strategy\SetDefaultStrategy; -use \Driver\SQL\Strategy\SetNullStrategy; +use Driver\SQL\Condition\CondRegex; +use Driver\SQL\Expression\Add; +use Driver\SQL\Strategy\Strategy; use \Driver\SQL\Strategy\UpdateStrategy; class MySQL extends SQL { @@ -32,7 +32,7 @@ class MySQL extends SQL { return 'mysqli'; } - // Connection Managment + // Connection Management public function connect() { if(!is_null($this->connection)) { @@ -47,7 +47,7 @@ class MySQL extends SQL { $this->connectionData->getPort() ); - if (mysqli_connect_errno($this->connection)) { + if (mysqli_connect_errno()) { $this->lastError = "Failed to connect to MySQL: " . mysqli_connect_error(); $this->connection = NULL; return false; @@ -63,6 +63,7 @@ class MySQL extends SQL { } mysqli_close($this->connection); + return true; } public function getLastError() { @@ -81,6 +82,8 @@ class MySQL extends SQL { switch($paramType) { case Parameter::TYPE_BOOLEAN: $value = $value ? 1 : 0; + $sqlParams[0] .= 'i'; + break; case Parameter::TYPE_INT: $sqlParams[0] .= 'i'; break; @@ -99,6 +102,10 @@ class MySQL extends SQL { $value = $value->format('Y-m-d H:i:s'); $sqlParams[0] .= 's'; break; + case Parameter::TYPE_ARRAY: + $value = json_encode($value); + $sqlParams[0] .= 's'; + break; case Parameter::TYPE_EMAIL: default: $sqlParams[0] .= 's'; @@ -161,64 +168,39 @@ class MySQL extends SQL { return ($success && $returnValues) ? $resultRows : $success; } - public function executeInsert($insert) { - $tableName = $this->tableName($insert->getTableName()); - $columns = $insert->getColumns(); - $rows = $insert->getRows(); - $onDuplicateKey = $insert->onDuplicateKey() ?? ""; + protected function getOnDuplicateStrategy(?Strategy $strategy, &$params) { + if (is_null($strategy)) { + return ""; + } else if ($strategy instanceof UpdateStrategy) { + $updateValues = array(); + foreach($strategy->getValues() as $key => $value) { + $leftColumn = $this->columnName($key); + if ($value instanceof Column) { + $columnName = $this->columnName($value->getName()); + $updateValues[] = "$leftColumn=VALUES($columnName)"; + } else if($value instanceof Add) { + $columnName = $this->columnName($value->getColumn()); + $operator = $value->getOperator(); + $value = $value->getValue(); + $updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params); + } else { + $updateValues[] = "$leftColumn=" . $this->addValue($value, $params); + } + } - if (empty($rows)) { - $this->lastError = "No rows to insert given."; + return " ON DUPLICATE KEY UPDATE " . implode(",", $updateValues); + } else { + $strategyClass = get_class($strategy); + $this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet."; return false; } - - if (is_null($columns) || empty($columns)) { - $columns = ""; - $numColumns = count($rows[0]); - } else { - $numColumns = count($columns); - $columns = " (" . $this->columnName($columns) . ")"; - } - - $numRows = count($rows); - $parameters = array(); - $values = implode(",", array_fill(0, $numRows, "(" . implode(",", array_fill(0, $numColumns, "?")) . ")")); - - foreach($rows as $row) { - $parameters = array_merge($parameters, $row); - } - - if ($onDuplicateKey) { - if ($onDuplicateKey instanceof UpdateStrategy) { - $updateValues = array(); - foreach($onDuplicateKey->getValues() as $key => $value) { - if ($value instanceof Column) { - $columnName = $value->getName(); - $updateValues[] = "`$key`=`$columnName`"; - } else { - $updateValues[] = "`$key`=" . $this->addValue($value, $parameters); - } - } - - $onDuplicateKey = " ON DUPLICATE KEY UPDATE " . implode(",", $updateValues); - } else { - $strategy = get_class($onDuplicateKey); - $this->lastError = "ON DUPLICATE Strategy $strategy is not supported yet."; - return false; - } - } - - $query = "INSERT INTO $tableName$columns VALUES$values$onDuplicateKey"; - $success = $this->execute($query, $parameters); - - if($success) { - $this->lastInsertId = mysqli_insert_id($this->connection); - } - - return $success; } - public function getColumnDefinition($column) { + protected function fetchReturning($res, string $returningCol) { + $this->lastInsertId = mysqli_insert_id($this->connection); + } + + public function getColumnDefinition(Column $column) { $columnName = $this->columnName($column->getName()); $defaultValue = $column->getDefaultValue(); @@ -255,7 +237,7 @@ class MySQL extends SQL { $notNull = $column->notNull() ? " NOT NULL" : ""; if (!is_null($defaultValue) || !$column->notNull()) { - $defaultValue = " DEFAULT " . $this->getValueDefinition($column->getDefaultValue()); + $defaultValue = " DEFAULT " . $this->getValueDefinition($defaultValue); } else { $defaultValue = ""; } @@ -323,4 +305,7 @@ class MySQL extends SQL { return new Keyword("NOW()"); } -}; + public function getStatus() { + return mysqli_stat($this->connection); + } +} diff --git a/core/Driver/SQL/PostgreSQL.class.php b/core/Driver/SQL/PostgreSQL.class.php index c7d01ea..e3e8794 100644 --- a/core/Driver/SQL/PostgreSQL.class.php +++ b/core/Driver/SQL/PostgreSQL.class.php @@ -4,7 +4,7 @@ namespace Driver\SQL; use \Api\Parameter\Parameter; -use \Driver\SQL\Column\Column; +use Driver\SQL\Column\Column; use \Driver\SQL\Column\IntColumn; use \Driver\SQL\Column\SerialColumn; use \Driver\SQL\Column\StringColumn; @@ -13,10 +13,10 @@ use \Driver\SQL\Column\DateTimeColumn; use Driver\SQL\Column\BoolColumn; use Driver\SQL\Column\JsonColumn; -use \Driver\SQL\Strategy\CascadeStrategy; -use \Driver\SQL\Strategy\SetDefaultStrategy; -use \Driver\SQL\Strategy\SetNullStrategy; -use \Driver\SQL\Strategy\UpdateStrategy; +use Driver\SQL\Condition\CondRegex; +use Driver\SQL\Expression\Add; +use Driver\SQL\Strategy\Strategy; +use Driver\SQL\Strategy\UpdateStrategy; class PostgreSQL extends SQL { @@ -32,7 +32,7 @@ class PostgreSQL extends SQL { return 'pgsql'; } - // Connection Managment + // Connection Management public function connect() { if(!is_null($this->connection)) { return true; @@ -68,13 +68,13 @@ class PostgreSQL extends SQL { if(is_null($this->connection)) return; - pg_close($this->connection); + @pg_close($this->connection); } public function getLastError() { $lastError = parent::getLastError(); if (empty($lastError)) { - $lastError = pg_last_error($this->connection) . " " . pg_last_error($this->connection); + $lastError = trim(pg_last_error($this->connection) . " " . pg_last_error($this->connection)); } return $lastError; @@ -99,6 +99,9 @@ class PostgreSQL extends SQL { case Parameter::TYPE_DATE_TIME: $value = $value->format("Y-m-d H:i:s"); break; + case Parameter::TYPE_ARRAY: + $value = json_encode($value); + break; default: break; } @@ -131,76 +134,48 @@ class PostgreSQL extends SQL { } } - // Querybuilder - public function executeInsert($insert) { + protected function getOnDuplicateStrategy(?Strategy $strategy, &$params) { + if (!is_null($strategy)) { + if ($strategy instanceof UpdateStrategy) { + $updateValues = array(); + foreach($strategy->getValues() as $key => $value) { + $leftColumn = $this->columnName($key); + if ($value instanceof Column) { + $columnName = $this->columnName($value->getName()); + $updateValues[] = "$leftColumn=EXCLUDED.$columnName"; + } else if ($value instanceof Add) { + $columnName = $this->columnName($value->getColumn()); + $operator = $value->getOperator(); + $value = $value->getValue(); + $updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params); + } else { + $updateValues[] = "$leftColumn=" . $this->addValue($value, $parameters); + } + } - $tableName = $this->tableName($insert->getTableName()); - $columns = $insert->getColumns(); - $rows = $insert->getRows(); - $onDuplicateKey = $insert->onDuplicateKey() ?? ""; - - if (empty($rows)) { - $this->lastError = "No rows to insert given."; - return false; - } - - if (is_null($columns) || empty($columns)) { - $columnStr = ""; - } else { - $columnStr = " (" . $this->columnName($columns) . ")"; - } - - $numRows = count($rows); - $parameters = array(); - - $values = array(); - foreach($rows as $row) { - $rowPlaceHolder = array(); - foreach($row as $val) { - $rowPlaceHolder[] = $this->addValue($val, $parameters); - } - - $values[] = "(" . implode(",", $rowPlaceHolder) . ")"; - } - - $values = implode(",", $values); - - if ($onDuplicateKey) { - /*if ($onDuplicateKey instanceof UpdateStrategy) { - $updateValues = array(); - foreach($onDuplicateKey->getValues() as $key => $value) { - if ($value instanceof Column) { - $columnName = $value->getName(); - $updateValues[] = "\"$key\"=\"$columnName\""; + $conflictingColumns = $this->columnName($strategy->getConflictingColumns()); + $updateValues = implode(",", $updateValues); + return " ON CONFLICT ($conflictingColumns) DO UPDATE SET $updateValues"; } else { - $updateValues[] = "\"$key\"=" . $this->addValue($value, $parameters); + $strategyClass = get_class($strategy); + $this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet."; + return false; } - } - - $onDuplicateKey = " ON CONFLICT DO UPDATE SET " . implode(",", $updateValues); - } else*/ { - $strategy = get_class($onDuplicateKey); - $this->lastError = "ON DUPLICATE Strategy $strategy is not supported yet."; - return false; + } else { + return ""; } - } + } - $returningCol = $insert->getReturning(); - $returning = $returningCol ? (" RETURNING " . $this->columnName($returningCol)) : ""; + protected function getReturning(?string $columns) { + return $columns ? (" RETURNING " . $this->columnName($columns)) : ""; + } - $query = "INSERT INTO $tableName$columnStr VALUES$values$onDuplicateKey$returning"; - $res = $this->execute($query, $parameters, !empty($returning)); - $success = ($res !== FALSE); - - if($success && !empty($returning)) { - $this->lastInsertId = $res[0][$returningCol]; - } - - return $success; + protected function fetchReturning($res, string $returningCol) { + $this->lastInsertId = $res[0][$returningCol]; } // UGLY but.. what should i do? - private function createEnum($enumColumn) { + private function createEnum(EnumColumn $enumColumn) { $typeName = $enumColumn->getName(); if(!endsWith($typeName, "_type")) { $typeName = "${typeName}_type"; @@ -319,5 +294,27 @@ class PostgreSQL extends SQL { public function currentTimestamp() { return new Keyword("CURRENT_TIMESTAMP"); } -} -?> + + public function getStatus() { + $version = pg_version($this->connection)["client"] ?? "??"; + $status = pg_connection_status($this->connection); + static $statusTexts = array( + PGSQL_CONNECTION_OK => "PGSQL_CONNECTION_OK", + PGSQL_CONNECTION_BAD => "PGSQL_CONNECTION_BAD", + ); + + return ($statusTexts[$status] ?? "Unknown") . " (v$version)"; + } + + protected function buildCondition($condition, &$params) { + if($condition instanceof CondRegex) { + $left = $condition->getLeftExp(); + $right = $condition->getRightExp(); + $left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params); + $right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params); + return $left . " ~ " . $right; + } else { + return parent::buildCondition($condition, $params); + } + } +} \ No newline at end of file diff --git a/core/Driver/SQL/Query/CreateTable.class.php b/core/Driver/SQL/Query/CreateTable.class.php index 9de7708..5743747 100644 --- a/core/Driver/SQL/Query/CreateTable.class.php +++ b/core/Driver/SQL/Query/CreateTable.class.php @@ -16,10 +16,10 @@ use Driver\SQL\Constraint\ForeignKey; class CreateTable extends Query { - private $tableName; - private $columns; - private $constraints; - private $ifNotExists; + private string $tableName; + private array $columns; + private array $constraints; + private bool $ifNotExists; public function __construct($sql, $name) { parent::__construct($sql); @@ -92,6 +92,4 @@ class CreateTable extends Query { public function getTableName() { return $this->tableName; } public function getColumns() { return $this->columns; } public function getConstraints() { return $this->constraints; } -}; - -?> +} diff --git a/core/Driver/SQL/Query/Delete.class.php b/core/Driver/SQL/Query/Delete.class.php index 1b4f507..705fbfe 100644 --- a/core/Driver/SQL/Query/Delete.class.php +++ b/core/Driver/SQL/Query/Delete.class.php @@ -2,10 +2,12 @@ namespace Driver\SQL\Query; +use Driver\SQL\Condition\CondOr; + class Delete extends Query { - private $table; - private $conditions; + private string $table; + private array $conditions; public function __construct($sql, $table) { parent::__construct($sql); @@ -14,7 +16,7 @@ class Delete extends Query { } public function where(...$conditions) { - $this->conditions = array_merge($this->conditions, $conditions); + $this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions)); return $this; } @@ -24,6 +26,4 @@ class Delete extends Query { public function getTable() { return $this->table; } public function getConditions() { return $this->conditions; } -}; - -?> +} diff --git a/core/Driver/SQL/Query/Insert.class.php b/core/Driver/SQL/Query/Insert.class.php index 34350a7..308aa87 100644 --- a/core/Driver/SQL/Query/Insert.class.php +++ b/core/Driver/SQL/Query/Insert.class.php @@ -2,13 +2,15 @@ namespace Driver\SQL\Query; +use Driver\SQL\Strategy\Strategy; + class Insert extends Query { - private $tableName; - private $columns; - private $rows; - private $onDuplicateKey; - private $returning; + private string $tableName; + private array $columns; + private array $rows; + private ?Strategy $onDuplicateKey; + private ?string $returning; public function __construct($sql, $name, $columns=array()) { parent::__construct($sql); @@ -43,6 +45,4 @@ class Insert extends Query { public function getRows() { return $this->rows; } public function onDuplicateKey() { return $this->onDuplicateKey; } public function getReturning() { return $this->returning; } -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Query/Query.class.php b/core/Driver/SQL/Query/Query.class.php index dce4510..b58a3b7 100644 --- a/core/Driver/SQL/Query/Query.class.php +++ b/core/Driver/SQL/Query/Query.class.php @@ -2,16 +2,23 @@ namespace Driver\SQL\Query; +use Driver\SQL\SQL; + abstract class Query { - protected $sql; + protected SQL $sql; + public bool $dump; public function __construct($sql) { $this->sql = $sql; + $this->dump = false; + } + + public function dump() { + $this->dump = true; + return $this; } public abstract function execute(); -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Query/Select.class.php b/core/Driver/SQL/Query/Select.class.php index 5f2b4c0..52aee6d 100644 --- a/core/Driver/SQL/Query/Select.class.php +++ b/core/Driver/SQL/Query/Select.class.php @@ -2,16 +2,20 @@ namespace Driver\SQL\Query; +use Driver\SQL\Condition\CondOr; +use Driver\SQL\Join; + class Select extends Query { - private $columns; - private $tables; - private $conditions; - private $joins; - private $orderColumns; - private $sortAscending; - private $limit; - private $offset; + private array $columns; + private array $tables; + private array $conditions; + private array $joins; + private array $orderColumns; + private array $groupColumns; + private bool $sortAscending; + private int $limit; + private int $offset; public function __construct($sql, ...$columns) { parent::__construct($sql); @@ -20,6 +24,7 @@ class Select extends Query { $this->conditions = array(); $this->joins = array(); $this->orderColumns = array(); + $this->groupColumns = array(); $this->limit = 0; $this->offset = 0; $this->sortAscending = true; @@ -31,17 +36,22 @@ class Select extends Query { } public function where(...$conditions) { - $this->conditions = array_merge($this->conditions, $conditions); + $this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions)); return $this; } public function innerJoin($table, $columnA, $columnB) { - $this->joins[] = new \Driver\SQL\Join("INNER", $table, $columnA, $columnB); + $this->joins[] = new Join("INNER", $table, $columnA, $columnB); return $this; } public function leftJoin($table, $columnA, $columnB) { - $this->joins[] = new \Driver\SQL\Join("LEFT", $table, $columnA, $columnB); + $this->joins[] = new Join("LEFT", $table, $columnA, $columnB); + return $this; + } + + public function groupBy(...$columns) { + $this->groupColumns = $columns; return $this; } @@ -51,12 +61,12 @@ class Select extends Query { } public function ascending() { - $this->ascending = true; + $this->sortAscending = true; return $this; } public function descending() { - $this->ascending = false; + $this->sortAscending = false; return $this; } @@ -78,11 +88,10 @@ class Select extends Query { public function getTables() { return $this->tables; } public function getConditions() { return $this->conditions; } public function getJoins() { return $this->joins; } - public function isOrderedAscending() { return $this->ascending; } + public function isOrderedAscending() { return $this->sortAscending; } public function getOrderBy() { return $this->orderColumns; } public function getLimit() { return $this->limit; } public function getOffset() { return $this->offset; } + public function getGroupBy() { return $this->groupColumns; } -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Query/Truncate.class.php b/core/Driver/SQL/Query/Truncate.class.php index d5b39af..d2c0478 100644 --- a/core/Driver/SQL/Query/Truncate.class.php +++ b/core/Driver/SQL/Query/Truncate.class.php @@ -4,7 +4,7 @@ namespace Driver\SQL\Query; class Truncate extends Query { - private $tableName; + private string $tableName; public function __construct($sql, $name) { parent::__construct($sql); @@ -15,7 +15,5 @@ class Truncate extends Query { return $this->sql->executeTruncate($this); } - public function getTableName() { return $this->tableName; } -}; - -?> + public function getTable() { return $this->tableName; } +} \ No newline at end of file diff --git a/core/Driver/SQL/Query/Update.class.php b/core/Driver/SQL/Query/Update.class.php index 9c29edf..e69a4f3 100644 --- a/core/Driver/SQL/Query/Update.class.php +++ b/core/Driver/SQL/Query/Update.class.php @@ -2,11 +2,13 @@ namespace Driver\SQL\Query; +use Driver\SQL\Condition\CondOr; + class Update extends Query { - private $values; - private $table; - private $conditions; + private array $values; + private string $table; + private array $conditions; public function __construct($sql, $table) { parent::__construct($sql); @@ -16,7 +18,7 @@ class Update extends Query { } public function where(...$conditions) { - $this->conditions = array_merge($this->conditions, $conditions); + $this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions)); return $this; } @@ -32,6 +34,4 @@ class Update extends Query { public function getTable() { return $this->table; } public function getConditions() { return $this->conditions; } public function getValues() { return $this->values; } -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/SQL.class.php b/core/Driver/SQL/SQL.class.php index 8a46120..738e343 100644 --- a/core/Driver/SQL/SQL.class.php +++ b/core/Driver/SQL/SQL.class.php @@ -2,16 +2,37 @@ namespace Driver\SQL; +use Driver\SQL\Column\Column; +use Driver\SQL\Condition\Compare; +use Driver\SQL\Condition\CondBool; +use Driver\SQL\Condition\CondIn; +use Driver\SQL\Condition\Condition; +use Driver\SQL\Condition\CondKeyword; +use Driver\SQL\Condition\CondNot; +use Driver\SQL\Condition\CondOr; +use Driver\SQL\Constraint\Constraint; use \Driver\SQL\Constraint\Unique; use \Driver\SQL\Constraint\PrimaryKey; use \Driver\SQL\Constraint\ForeignKey; +use Driver\SQL\Query\CreateTable; +use Driver\SQL\Query\Delete; +use Driver\SQL\Query\Insert; +use Driver\SQL\Query\Query; +use Driver\SQL\Query\Select; +use Driver\SQL\Query\Truncate; +use Driver\SQL\Query\Update; +use Driver\SQL\Strategy\CascadeStrategy; +use Driver\SQL\Strategy\SetDefaultStrategy; +use Driver\SQL\Strategy\SetNullStrategy; +use Driver\SQL\Strategy\Strategy; +use Objects\ConnectionData; abstract class SQL { - protected $lastError; + protected string $lastError; protected $connection; - protected $connectionData; - protected $lastInsertId; + protected ConnectionData $connectionData; + protected int $lastInsertId; public function __construct($connectionData) { $this->connection = NULL; @@ -29,27 +50,27 @@ abstract class SQL { } public function createTable($tableName) { - return new Query\CreateTable($this, $tableName); + return new CreateTable($this, $tableName); } public function insert($tableName, $columns=array()) { - return new Query\Insert($this, $tableName, $columns); + return new Insert($this, $tableName, $columns); } public function select(...$columNames) { - return new Query\Select($this, $columNames); + return new Select($this, $columNames); } public function truncate($table) { - return new Query\Truncate($this, $table); + return new Truncate($this, $table); } public function delete($table) { - return new Query\Delete($this, $table); + return new Delete($this, $table); } public function update($table) { - return new Query\Update($this, $table); + return new Update($this, $table); } // #################### @@ -65,7 +86,54 @@ abstract class SQL { public abstract function disconnect(); // Querybuilder - public function executeCreateTable($createTable) { + protected function buildQuery(Query $query, array &$params) { + if ($query instanceof Select) { + $select = $query; + $columns = $this->columnName($select->getColumns()); + $tables = $select->getTables(); + + if (!$tables) { + return $this->execute("SELECT $columns", $params, true); + } + + $tables = $this->tableName($tables); + $where = $this->getWhereClause($select->getConditions(), $params); + + $joinStr = ""; + $joins = $select->getJoins(); + if (!empty($joins)) { + foreach($joins as $join) { + $type = $join->getType(); + $joinTable = $this->tableName($join->getTable()); + $columnA = $this->columnName($join->getColumnA()); + $columnB = $this->columnName($join->getColumnB()); + $joinStr .= " $type JOIN $joinTable ON $columnA=$columnB"; + } + } + + $groupBy = ""; + $groupColumns = $select->getGroupBy(); + if (!empty($groupColumns)) { + $groupBy = " GROUP BY " . $this->columnName($groupColumns); + } + + $orderBy = ""; + $orderColumns = $select->getOrderBy(); + if (!empty($orderColumns)) { + $orderBy = " ORDER BY " . $this->columnName($orderColumns); + $orderBy .= ($select->isOrderedAscending() ? " ASC" : " DESC"); + } + + $limit = ($select->getLimit() > 0 ? (" LIMIT " . $select->getLimit()) : ""); + $offset = ($select->getOffset() > 0 ? (" OFFSET " . $select->getOffset()) : ""); + return "SELECT $columns FROM $tables$joinStr$where$groupBy$orderBy$limit$offset"; + } else { + $this->lastError = "buildQuery() not implemented for type: " . get_class($query); + return FALSE; + } + } + + public function executeCreateTable(CreateTable $createTable) { $tableName = $this->tableName($createTable->getTableName()); $ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": ""; @@ -89,73 +157,94 @@ abstract class SQL { return $this->execute($query); } - // TODO pull this function up - public abstract function executeInsert($query); + public function executeInsert(Insert $insert) { - public function executeSelect($select) { + $tableName = $this->tableName($insert->getTableName()); + $columns = $insert->getColumns(); + $rows = $insert->getRows(); - $columns = $this->columnName($select->getColumns()); - $tables = $select->getTables(); - $params = array(); - - if (!$tables) { - return "SELECT $columns"; + if (empty($rows)) { + $this->lastError = "No rows to insert given."; + return false; } - $tables = $this->tableName($tables); - $where = $this->getWhereClause($select->getConditions(), $params); + if (is_null($columns) || empty($columns)) { + $columnStr = ""; + } else { + $columnStr = " (" . $this->columnName($columns) . ")"; + } - $joinStr = ""; - $joins = $select->getJoins(); - if (!empty($joins)) { - foreach($joins as $join) { - $type = $join->getType(); - $joinTable = $this->tableName($join->getTable()); - $columnA = $this->columnName($join->getColumnA()); - $columnB = $this->columnName($join->getColumnB()); - $joinStr .= " $type JOIN $joinTable ON $columnA=$columnB"; + $parameters = array(); + $values = array(); + foreach($rows as $row) { + $rowPlaceHolder = array(); + foreach($row as $val) { + $rowPlaceHolder[] = $this->addValue($val, $parameters); } + + $values[] = "(" . implode(",", $rowPlaceHolder) . ")"; } - $orderBy = ""; - $orderColumns = $select->getOrderBy(); - if (!empty($orderColumns)) { - $orderBy = " ORDER BY " . $this->columnName($orderColumns); - $orderBy .= ($select->isOrderedAscending() ? " ASC" : " DESC"); + $values = implode(",", $values); + + $onDuplicateKey = $this->getOnDuplicateStrategy($insert->onDuplicateKey(), $parameters); + if ($onDuplicateKey === FALSE) { + return false; } - $limit = ($select->getLimit() > 0 ? (" LIMIT " . $select->getLimit()) : ""); - $offset = ($select->getOffset() > 0 ? (" OFFSET " . $select->getOffset()) : ""); - $query = "SELECT $columns FROM $tables$joinStr$where$orderBy$limit$offset"; + $returningCol = $insert->getReturning(); + $returning = $this->getReturning($returningCol); + + $query = "INSERT INTO $tableName$columnStr VALUES $values$onDuplicateKey$returning"; + if($insert->dump) { var_dump($query); var_dump($parameters); } + $res = $this->execute($query, $parameters, !empty($returning)); + $success = ($res !== FALSE); + + if($success && $returningCol) { + $this->fetchReturning($res, $returningCol); + } + + return $success; + } + + public function executeSelect(Select $select) { + $params = array(); + $query = $this->buildQuery($select, $params); + if($select->dump) { var_dump($query); var_dump($params); } return $this->execute($query, $params, true); } - public function executeDelete($delete) { + public function executeDelete(Delete $delete) { + $params = array(); $table = $this->tableName($delete->getTable()); $where = $this->getWhereClause($delete->getConditions(), $params); $query = "DELETE FROM $table$where"; + if($delete->dump) { var_dump($query); } + return $this->execute($query, $params); + } + + public function executeTruncate(Truncate $truncate) { + $query = "TRUNCATE " . $this->tableName($truncate->getTable()); + if ($truncate->dump) { var_dump($query); } return $this->execute($query); } - public function executeTruncate($truncate) { - return $this->execute("TRUNCATE " . $truncate->getTable()); - } - - public function executeUpdate($update) { + public function executeUpdate(Update $update) { $params = array(); $table = $this->tableName($update->getTable()); $valueStr = array(); foreach($update->getValues() as $key => $val) { - $valueStr[] = "$key=" . $this->addValue($val, $params); + $valueStr[] = $this->columnName($key) . "=" . $this->addValue($val, $params); } $valueStr = implode(",", $valueStr); $where = $this->getWhereClause($update->getConditions(), $params); $query = "UPDATE $table SET $valueStr$where"; + if($update->dump) { var_dump($query); var_dump($params); } return $this->execute($query, $params); } @@ -167,10 +256,8 @@ abstract class SQL { } } - protected abstract function getColumnDefinition($column); - - public function getConstraintDefinition($constraint) { - $columnName = $this->columnName($constraint->getColumnName()); + public function getConstraintDefinition(Constraint $constraint) { + $columnName = $this->columnName($constraint->getColumnNames()); if ($constraint instanceof PrimaryKey) { return "PRIMARY KEY ($columnName)"; } else if ($constraint instanceof Unique) { @@ -190,10 +277,19 @@ abstract class SQL { return $code; } else { - $this->lastError = "Unsupported constraint type: " . get_class($strategy); + $this->lastError = "Unsupported constraint type: " . get_class($constraint); + return false; } } + protected function getReturning(?string $columns) { + return ""; + } + + protected abstract function getColumnDefinition(Column $column); + protected abstract function fetchReturning($res, string $returningCol); + protected abstract function getOnDuplicateStrategy(?Strategy $strategy, &$params); + protected abstract function getValueDefinition($val); protected abstract function addValue($val, &$params); @@ -201,17 +297,25 @@ abstract class SQL { protected abstract function columnName($col); // Special Keywords and functions + public function now() { return $this->currentTimestamp(); } public abstract function currentTimestamp(); public function count($col = NULL) { if (is_null($col)) { return new Keyword("COUNT(*) AS count"); } else { + $countCol = strtolower(str_replace(".","_", $col)) . "_count"; $col = $this->columnName($col); - return new Keyword("COUNT($col) AS count"); + return new Keyword("COUNT($col) AS $countCol"); } } + public function sum($col) { + $sumCol = strtolower(str_replace(".","_", $col)) . "_sum"; + $col = $this->columnName($col); + return new Keyword("SUM($col) AS $sumCol"); + } + public function distinct($col) { $col = $this->columnName($col); return new Keyword("DISTINCT($col)"); @@ -221,29 +325,67 @@ abstract class SQL { protected abstract function execute($query, $values=NULL, $returnValues=false); protected function buildCondition($condition, &$params) { - if ($condition instanceof \Driver\SQL\Condition\CondOr) { + + if ($condition instanceof CondOr) { $conditions = array(); foreach($condition->getConditions() as $cond) { $conditions[] = $this->buildCondition($cond, $params); } return "(" . implode(" OR ", $conditions) . ")"; - } else if ($condition instanceof \Driver\SQL\Condition\Compare) { + } else if ($condition instanceof Compare) { $column = $this->columnName($condition->getColumn()); $value = $condition->getValue(); $operator = $condition->getOperator(); return $column . $operator . $this->addValue($value, $params); - } else if ($condition instanceof \Driver\SQL\Condition\CondBool) { + } else if ($condition instanceof CondBool) { return $this->columnName($condition->getValue()); } else if (is_array($condition)) { - if (count($condition) == 1) { + if (count($condition) === 1) { return $this->buildCondition($condition[0], $params); } else { $conditions = array(); - foreach($condition as $cond) { + foreach ($condition as $cond) { $conditions[] = $this->buildCondition($cond, $params); } return implode(" AND ", $conditions); } + } else if($condition instanceof CondIn) { + + $expression = $condition->getExpression(); + if (is_array($expression)) { + $values = array(); + foreach ($expression as $value) { + $values[] = $this->addValue($value, $params); + } + + $values = implode(",", $values); + } else if($expression instanceof Select) { + $values = $this->buildQuery($expression, $params); + } else { + $this->lastError = "Unsupported in-expression value: " . get_class($condition); + return false; + } + + return $this->columnName($condition->getColumn()) . " IN ($values)"; + } else if($condition instanceof CondKeyword) { + $left = $condition->getLeftExp(); + $right = $condition->getRightExp(); + $keyword = $condition->getKeyword(); + $left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params); + $right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params); + return "$left $keyword $right "; + } else if($condition instanceof CondNot) { + $expression = $condition->getExpression(); + if ($expression instanceof Condition) { + $expression = $this->buildCondition($expression, $params); + } else { + $expression = $this->columnName($expression); + } + + return "NOT $expression"; + } else { + $this->lastError = "Unsupported condition type: " . get_class($condition); + return false; } } @@ -260,7 +402,7 @@ abstract class SQL { $this->connection = NULL; } - public static function createConnection($connectionData) { + public static function createConnection(ConnectionData $connectionData) { $type = $connectionData->getProperty("type"); if ($type === "mysql") { $sql = new MySQL($connectionData); @@ -279,6 +421,10 @@ abstract class SQL { return $sql; } -} -?> + public abstract function getStatus(); + + public function parseBool($val) : bool { + return in_array($val, array(true, 1, '1', 't', 'true', 'TRUE'), true); + } +} \ No newline at end of file diff --git a/core/Driver/SQL/Strategy/CascadeStrategy.class.php b/core/Driver/SQL/Strategy/CascadeStrategy.class.php index 103f8bd..b72f5db 100644 --- a/core/Driver/SQL/Strategy/CascadeStrategy.class.php +++ b/core/Driver/SQL/Strategy/CascadeStrategy.class.php @@ -7,6 +7,4 @@ class CascadeStrategy extends Strategy { public function __construct() { } -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Strategy/SetDefaultStrategy.class.php b/core/Driver/SQL/Strategy/SetDefaultStrategy.class.php index b896080..8b5c77a 100644 --- a/core/Driver/SQL/Strategy/SetDefaultStrategy.class.php +++ b/core/Driver/SQL/Strategy/SetDefaultStrategy.class.php @@ -7,6 +7,4 @@ class SetDefaultStrategy extends Strategy { public function __construct() { } -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Strategy/SetNullStrategy.class.php b/core/Driver/SQL/Strategy/SetNullStrategy.class.php index 7b6fd05..5d4f35a 100644 --- a/core/Driver/SQL/Strategy/SetNullStrategy.class.php +++ b/core/Driver/SQL/Strategy/SetNullStrategy.class.php @@ -7,6 +7,4 @@ class SetNullStrategy extends Strategy { public function __construct() { } -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Strategy/Strategy.class.php b/core/Driver/SQL/Strategy/Strategy.class.php index 83b662d..96efdab 100644 --- a/core/Driver/SQL/Strategy/Strategy.class.php +++ b/core/Driver/SQL/Strategy/Strategy.class.php @@ -4,6 +4,4 @@ namespace Driver\SQL\Strategy; abstract class Strategy { -}; - -?> +} \ No newline at end of file diff --git a/core/Driver/SQL/Strategy/UpdateStrategy.class.php b/core/Driver/SQL/Strategy/UpdateStrategy.class.php index 3fce860..9084979 100644 --- a/core/Driver/SQL/Strategy/UpdateStrategy.class.php +++ b/core/Driver/SQL/Strategy/UpdateStrategy.class.php @@ -4,13 +4,17 @@ namespace Driver\SQL\Strategy; class UpdateStrategy extends Strategy { - private $values; + private array $values; + private array $conflictingColumns; - public function __construct($values) { + public function __construct(array $conflictingColumns, array $values) { + $this->conflictingColumns = $conflictingColumns; $this->values = $values; } - public function getValues() { return $this->values; } -}; + public function getConflictingColumns() { + return $this->conflictingColumns; + } -?> + public function getValues() { return $this->values; } +} \ No newline at end of file diff --git a/core/Elements/Body.class.php b/core/Elements/Body.class.php index b016dbf..196961a 100644 --- a/core/Elements/Body.class.php +++ b/core/Elements/Body.class.php @@ -2,9 +2,8 @@ namespace Elements; -abstract class Body extends \View { +abstract class Body extends View { public function __construct($document) { parent::__construct($document); } -}; -?> +} \ No newline at end of file diff --git a/core/Elements/Document.class.php b/core/Elements/Document.class.php index b662324..d0cc9a9 100644 --- a/core/Elements/Document.class.php +++ b/core/Elements/Document.class.php @@ -2,18 +2,22 @@ namespace Elements; +use Objects\User; + abstract class Document { - protected $head; - protected $body; - protected $user; - protected $databaseRequired; + protected Head $head; + protected Body $body; + protected User $user; + protected bool $databaseRequired; + private ?string $activeView; - public function __construct($user, $headClass, $bodyClass) { + public function __construct(User $user, $headClass, $bodyClass, ?string $view = NULL) { $this->head = new $headClass($this); $this->body = new $bodyClass($this); $this->user = $user; $this->databaseRequired = true; + $this->activeView = $view; } public function getHead() { return $this->head; } @@ -21,36 +25,14 @@ abstract class Document { public function getSQL() { return $this->user->getSQL(); } public function getUser() { return $this->user; } - protected function sendHeaders() { - header("X-Frame-Options: DENY"); - } + public function getView() : ?View { - public static function createSearchableDocument($documentClass, $user) { - return new $documentClass($user); - } - - public static function createDocument($class) { - // TODO: check instance, configuration, .. - - require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/sql.php'; - // require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/conf/config.php'; - // require_once realpath($_SERVER['DOCUMENT_ROOT']) . "/php/pages/$file.php"; - require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/api/objects/User.php'; - - $connectionData = getSqlData($database); - $sql = connectSQL($connectionData); - if(!$sql->isConnected()) { - http_response_code(500); - die('Internal Database error'); + $file = getClassPath($this->activeView); + if(!file_exists($file) || !is_subclass_of($this->activeView, View::class)) { + return null; } - $user = new CUser($sql); - $document = new $class($user); - $code = $document->getCode(); - - $document->sendHeaders(); - $user->sendCookies(); - die($code); + return new $this->activeView($this); } function getCode() { @@ -66,15 +48,14 @@ abstract class Document { $body = $this->body->getCode(); $head = $this->head->getCode(); + $lang = $this->user->getLanguage()->getShortCode(); $html = ""; - $html .= ""; + $html .= ""; $html .= $head; $html .= $body; $html .= ""; return $html; } -}; - -?> +} \ No newline at end of file diff --git a/core/Elements/Head.class.php b/core/Elements/Head.class.php index 3eab8d8..383c2e2 100644 --- a/core/Elements/Head.class.php +++ b/core/Elements/Head.class.php @@ -2,19 +2,20 @@ namespace Elements; -abstract class Head extends \View { +abstract class Head extends View { - protected $sources; - protected $title; - protected $metas; - protected $rawFields; - protected $keywords; - protected $description; - protected $baseUrl; + protected array $sources; + protected string $title; + protected array $metas; + protected array $rawFields; + protected array $keywords; + protected string $description; + protected string $baseUrl; function __construct($document) { parent::__construct($document); $this->sources = array(); + $this->searchable = false; $this->metas = $this->initMetas(); $this->rawFields = $this->initRawFields(); $this->title = $this->initTitle(); @@ -45,7 +46,7 @@ abstract class Head extends \View { public function addKeywords($keywords) { array_merge($this->keywords, $keywords); } public function getTitle() { return $this->title; } - public function addCSS($href, $type = Link::MIME_TEXT_CSS) { $this->sources[] = new Link("stylesheet", $href, $type); } + public function addCSS($href, $type = Link::MIME_TEXT_CSS) { $this->sources[] = new Link(Link::STYLESHEET, $href, $type); } public function addStyle($style) { $this->sources[] = new Style($style); } public function addJS($url) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, $url, ""); } public function addJSCode($code) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, "", $code); } @@ -54,19 +55,6 @@ abstract class Head extends \View { $this->addCSS(Link::FONTAWESOME); } - public function loadSyntaxHighlighting() { - $this->addJS(Script::HIGHLIGHT); - $this->addJSCode(Script::HIGHLIGHT_JS_LOADER); - $this->addCSS(Link::HIGHLIGHT); - $this->addCSS(Link::HIGHLIGHT_THEME); - } - - public function loadJQueryTerminal($unixFormatting = true) { - $this->addJS(Script::JQUERY_TERMINAL); - if($unixFormatting) $this->addJS(Script::JQUERY_TERMINAL_UNIX); - $this->addCSS(Link::JQUERY_TERMINAL); - } - public function loadGoogleRecaptcha($siteKey) { $this->addJS("https://www.google.com/recaptcha/api.js?render=$siteKey"); } @@ -80,11 +68,6 @@ abstract class Head extends \View { $this->addJS(Script::BOOTSTRAP); } - public function loadChartJS() { - $this->addJS(Script::MOMENT); - $this->addJS(Script::CHART); - } - public function getCode() { $header = ""; @@ -123,4 +106,3 @@ abstract class Head extends \View { return $header; } } -?> diff --git a/core/Elements/Link.class.php b/core/Elements/Link.class.php index 5424d05..57b4243 100644 --- a/core/Elements/Link.class.php +++ b/core/Elements/Link.class.php @@ -2,41 +2,28 @@ namespace Elements; -class Link extends Source { +class Link extends StaticView { const STYLESHEET = "stylesheet"; const MIME_TEXT_CSS = "text/css"; - const FONTAWESOME = '/css/fontawesome.min.css'; - // const JQUERY_UI = '/css/jquery-ui.css'; - // const JQUERY_TERMINAL = '/css/jquery.terminal.min.css'; - const BOOTSTRAP = '/css/bootstrap.min.css'; - // const BOOTSTRAP_THEME = '/css/bootstrap-theme.min.css'; - // const BOOTSTRAP_DATEPICKER_CSS = '/css/bootstrap-datepicker.standalone.min.css'; - // const BOOTSTRAP_DATEPICKER3_CSS = '/css/bootstrap-datepicker.standalone.min.css'; - // const HIGHLIGHT = '/css/highlight.css'; - // const HIGHLIGHT_THEME = '/css/theme.css'; - const CORE = "/css/style.css"; - const ADMIN = "/css/admin.css"; - // const HOME = "/css/home.css"; - // const REVEALJS = "/css/reveal.css"; - // const REVEALJS_THEME_MOON = "/css/reveal_moon.css"; - // const REVEALJS_THEME_BLACK = "/css/reveal_black.css"; + const FONTAWESOME = "/css/fontawesome.min.css"; + const BOOTSTRAP = "/css/bootstrap.min.css"; + const CORE = "/css/style.css"; + const ACCOUNT = "/css/account.css"; - private $type; - private $rel; + private string $type; + private string $rel; + private string $href; function __construct($rel, $href, $type = "") { - parent::__construct('link', $href); + $this->href = $href; $this->type = $type; $this->rel = $rel; } function getCode() { $type = (empty($this->type) ? "" : " type=\"$this->type\""); - $link = "rel\" href=\"$this->url\" $type/>"; - return $link; + return "rel\" href=\"$this->href\"$type/>"; } } - -?> diff --git a/core/Elements/Script.class.php b/core/Elements/Script.class.php index f8a2592..23cfcec 100644 --- a/core/Elements/Script.class.php +++ b/core/Elements/Script.class.php @@ -2,52 +2,28 @@ namespace Elements; -class Script extends Source { +class Script extends StaticView { const MIME_TEXT_JAVASCRIPT = "text/javascript"; - const CORE = "/js/script.js"; - // const HOME = "/js/home.js"; - const ADMIN = "/js/admin.js"; - // const SORTTABLE = "/js/sorttable.js"; - const JQUERY = "/js/jquery.min.js"; - // const JQUERY_UI = "/js/jquery-ui.js"; - // const JQUERY_MASKED_INPUT = "/js/jquery.maskedinput.min.js"; - // const JQUERY_CONTEXT_MENU = "/js/jquery.contextmenu.min.js"; - // const JQUERY_TERMINAL = "/js/jquery.terminal.min.js"; - // const JQUERY_TERMINAL_UNIX = "/js/unix_formatting.js"; - // const JSCOLOR = "/js/jscolor.min.js"; - // const SYNTAX_HIGHLIGHTER = "/js/syntaxhighlighter.js"; - // const HIGHLIGHT = "/js/highlight.pack.js"; - // const GOOGLE_CHARTS = "/js/loader.js"; - const BOOTSTRAP = "/js/bootstrap.min.js"; - // const BOOTSTRAP_DATEPICKER_JS = "/js/bootstrap-datepicker.min.js"; - // const POPPER = "/js/popper.min.js"; - // const JSMPEG = "/js/jsmpeg.min.js"; - // const MOMENT = "/js/moment.min.js"; - // const CHART = "/js/chart.js"; - // const REVEALJS = "/js/reveal.js"; - // const REVEALJS_PLUGIN_NOTES = "/js/reveal_notes.js"; - const INSTALL = "/js/install.js"; + const CORE = "/js/script.js"; + const JQUERY = "/js/jquery.min.js"; + const INSTALL = "/js/install.js"; + const BOOTSTRAP = "/js/bootstrap.bundle.min.js"; + const ACCOUNT = "/js/account.js"; - const HIGHLIGHT_JS_LOADER = "\$(document).ready(function(){\$('code').each(function(i, block) { hljs.highlightBlock(block); }); })"; - - private $type; - private $content; + private string $type; + private string $content; + private string $src; function __construct($type, $src, $content = "") { - parent::__construct('script', $src); + $this->src = $src; $this->type = $type; $this->content = $content; } function getCode() { - $src = (empty($this->url) ? "" : " src=\"$this->url\""); - $script = "'; - return $script; + $src = (empty($this->src) ? "" : " src=\"$this->src\""); + return ""; } -} - -?> +} \ No newline at end of file diff --git a/core/Elements/SimpleBody.class.php b/core/Elements/SimpleBody.class.php new file mode 100644 index 0000000..87f8712 --- /dev/null +++ b/core/Elements/SimpleBody.class.php @@ -0,0 +1,16 @@ +getContent(); + return parent::getCode() . "$content"; + } + + protected abstract function getContent(); +} \ No newline at end of file diff --git a/core/Elements/Source.class.php b/core/Elements/Source.class.php deleted file mode 100644 index fb2d59b..0000000 --- a/core/Elements/Source.class.php +++ /dev/null @@ -1,22 +0,0 @@ -sourceType = $sourceType; - $this->url = $url; - } - - public function getCode() { - return "<$sourceType />"; - } - - public function getUrl() { return $this->url; } -} - -?> diff --git a/core/Elements/StaticView.class.php b/core/Elements/StaticView.class.php new file mode 100644 index 0000000..8fc791c --- /dev/null +++ b/core/Elements/StaticView.class.php @@ -0,0 +1,13 @@ +getCode(); + } + +} \ No newline at end of file diff --git a/core/Elements/Style.class.php b/core/Elements/Style.class.php index 35c8968..5fbe4e8 100644 --- a/core/Elements/Style.class.php +++ b/core/Elements/Style.class.php @@ -2,12 +2,11 @@ namespace Elements; -class Style extends Source { +class Style extends StaticView { - private $style; + private string $style; function __construct($style) { - parent::__construct('style', ''); $this->style = $style; } @@ -15,5 +14,3 @@ class Style extends Source { return ""; } } - -?> diff --git a/core/Elements/View.class.php b/core/Elements/View.class.php new file mode 100644 index 0000000..71954c8 --- /dev/null +++ b/core/Elements/View.class.php @@ -0,0 +1,136 @@ +document = $document; + $this->searchable = false; + $this->reference = ""; + $this->title = "Untitled View"; + $this->langModules = array(); + $this->loadView = $loadView; + } + + public function getTitle() { return $this->title; } + public function getDocument() { return $this->document; } + public function isSearchable() { return $this->searchable; } + public function getReference() { return $this->reference; } + + protected function load(string $viewClass) : string { + try { + $reflectionClass = new \ReflectionClass($viewClass); + if ($reflectionClass->isSubclassOf(View::class) && $reflectionClass->isInstantiable()) { + $view = $reflectionClass->newInstanceArgs(array($this->getDocument())); + $view->loadView(); + return $view; + } + } catch(\ReflectionException $e) { + error_log($e->getMessage()); + } + + return ""; + } + + private function loadLanguageModules() { + $lang = $this->document->getUser()->getLanguage(); + foreach($this->langModules as $langModule) { + $lang->loadModule($langModule); + } + } + + // Virtual Methods + public function loadView() { } + + public function getCode() { + + // Load translations + $this->loadLanguageModules(); + + // Load Meta Data + Head (title, scripts, includes, ...) + if($this->loadView) { + $this->loadView(); + } + + return ''; + } + + // UI Functions + private function createList($items, $tag) { + if(count($items) === 0) + return "<$tag>"; + else + return "<$tag>
  • " . implode("
  • ", $items) . "
  • "; + } + + public function createOrderedList($items=array()) { + return $this->createList($items, "ol"); + } + + public function createUnorderedList($items=array()) { + return $this->createList($items, "ul"); + } + + protected function createLink($link, $title=null) { + if(is_null($title)) $title=$link; + return "$title"; + } + + protected function createExternalLink($link, $title=null) { + if(is_null($title)) $title=$link; + return "$title"; + } + + protected function createIcon($icon, $type = "fas", $classes = "") { + $iconClass = "$type fa-$icon"; + + if($icon === "spinner" || $icon === "circle-notch") + $iconClass .= " fa-spin"; + + if($classes) + $iconClass .= " $classes"; + + return ""; + } + + protected function createErrorText($text, $id="", $hidden=false) { + return $this->createStatusText("danger", $text, $id, $hidden); + } + + protected function createWarningText($text, $id="", $hidden=false) { + return $this->createStatusText("warning", $text, $id, $hidden); + } + + protected function createSuccessText($text, $id="", $hidden=false) { + return $this->createStatusText("success", $text, $id, $hidden); + } + + protected function createSecondaryText($text, $id="", $hidden=false) { + return $this->createStatusText("secondary", $text, $id, $hidden); + } + + protected function createInfoText($text, $id="", $hidden=false) { + return $this->createStatusText("info", $text, $id, $hidden); + } + + protected function createStatusText($type, $text, $id="", $hidden=false) { + if(strlen($id) > 0) $id = " id=\"$id\""; + $hidden = ($hidden?" hidden" : ""); + return "
    $text
    "; + } + + protected function createBadge($type, $text) { + $text = htmlspecialchars($text); + return "$text"; + } +} \ No newline at end of file diff --git a/core/External/phpQuery.php b/core/External/phpQuery.php deleted file mode 100644 index 703cb81..0000000 --- a/core/External/phpQuery.php +++ /dev/null @@ -1,5702 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT License - * @package phpQuery - */ - -// class names for instanceof -// TODO move them as class constants into phpQuery -define('DOMDOCUMENT', 'DOMDocument'); -define('DOMELEMENT', 'DOMElement'); -define('DOMNODELIST', 'DOMNodeList'); -define('DOMNODE', 'DOMNode'); - -/** - * DOMEvent class. - * - * Based on - * @link http://developer.mozilla.org/En/DOM:event - * @author Tobiasz Cudnik - * @package phpQuery - * @todo implement ArrayAccess ? - */ -class DOMEvent { - /** - * Returns a boolean indicating whether the event bubbles up through the DOM or not. - * - * @var unknown_type - */ - public $bubbles = true; - /** - * Returns a boolean indicating whether the event is cancelable. - * - * @var unknown_type - */ - public $cancelable = true; - /** - * Returns a reference to the currently registered target for the event. - * - * @var unknown_type - */ - public $currentTarget; - /** - * Returns detail about the event, depending on the type of event. - * - * @var unknown_type - * @link http://developer.mozilla.org/en/DOM/event.detail - */ - public $detail; // ??? - /** - * Used to indicate which phase of the event flow is currently being evaluated. - * - * NOT IMPLEMENTED - * - * @var unknown_type - * @link http://developer.mozilla.org/en/DOM/event.eventPhase - */ - public $eventPhase; // ??? - /** - * The explicit original target of the event (Mozilla-specific). - * - * NOT IMPLEMENTED - * - * @var unknown_type - */ - public $explicitOriginalTarget; // moz only - /** - * The original target of the event, before any retargetings (Mozilla-specific). - * - * NOT IMPLEMENTED - * - * @var unknown_type - */ - public $originalTarget; // moz only - /** - * Identifies a secondary target for the event. - * - * @var unknown_type - */ - public $relatedTarget; - /** - * Returns a reference to the target to which the event was originally dispatched. - * - * @var unknown_type - */ - public $target; - /** - * Returns the time that the event was created. - * - * @var unknown_type - */ - public $timeStamp; - /** - * Returns the name of the event (case-insensitive). - */ - public $type; - public $runDefault = true; - public $data = null; - public function __construct($data) { - foreach($data as $k => $v) { - $this->$k = $v; - } - if (! $this->timeStamp) - $this->timeStamp = time(); - } - /** - * Cancels the event (if it is cancelable). - * - */ - public function preventDefault() { - $this->runDefault = false; - } - /** - * Stops the propagation of events further along in the DOM. - * - */ - public function stopPropagation() { - $this->bubbles = false; - } -} - - -/** - * DOMDocumentWrapper class simplifies work with DOMDocument. - * - * Know bug: - * - in XHTML fragments,
    changes to
    - * - * @todo check XML catalogs compatibility - * @author Tobiasz Cudnik - * @package phpQuery - */ -class DOMDocumentWrapper { - /** - * @var DOMDocument - */ - public $document; - public $id; - /** - * @todo Rewrite as method and quess if null. - * @var unknown_type - */ - public $contentType = ''; - public $xpath; - public $uuid = 0; - public $data = array(); - public $dataNodes = array(); - public $events = array(); - public $eventsNodes = array(); - public $eventsGlobal = array(); - /** - * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28 - * @var unknown_type - */ - public $frames = array(); - /** - * Document root, by default equals to document itself. - * Used by documentFragments. - * - * @var DOMNode - */ - public $root; - public $isDocumentFragment; - public $isXML = false; - public $isXHTML = false; - public $isHTML = false; - public $charset; - public function __construct($markup = null, $contentType = null, $newDocumentID = null) { - if (isset($markup)) - $this->load($markup, $contentType, $newDocumentID); - $this->id = $newDocumentID - ? $newDocumentID - : md5(microtime()); - } - public function load($markup, $contentType = null, $newDocumentID = null) { -// phpQuery::$documents[$id] = $this; - $this->contentType = strtolower($contentType); - if ($markup instanceof DOMDOCUMENT) { - $this->document = $markup; - $this->root = $this->document; - $this->charset = $this->document->encoding; - // TODO isDocumentFragment - } else { - $loaded = $this->loadMarkup($markup); - } - if ($loaded) { -// $this->document->formatOutput = true; - $this->document->preserveWhiteSpace = true; - $this->xpath = new DOMXPath($this->document); - $this->afterMarkupLoad(); - return true; - // remember last loaded document -// return phpQuery::selectDocument($id); - } - return false; - } - protected function afterMarkupLoad() { - if ($this->isXHTML) { - $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml"); - } - } - protected function loadMarkup($markup) { - $loaded = false; - if ($this->contentType) { - self::debug("Load markup for content type {$this->contentType}"); - // content determined by contentType - list($contentType, $charset) = $this->contentTypeToArray($this->contentType); - switch($contentType) { - case 'text/html': - phpQuery::debug("Loading HTML, content type '{$this->contentType}'"); - $loaded = $this->loadMarkupHTML($markup, $charset); - break; - case 'text/xml': - case 'application/xhtml+xml': - phpQuery::debug("Loading XML, content type '{$this->contentType}'"); - $loaded = $this->loadMarkupXML($markup, $charset); - break; - default: - // for feeds or anything that sometimes doesn't use text/xml - if (strpos('xml', $this->contentType) !== false) { - phpQuery::debug("Loading XML, content type '{$this->contentType}'"); - $loaded = $this->loadMarkupXML($markup, $charset); - } else - phpQuery::debug("Could not determine document type from content type '{$this->contentType}'"); - } - } else { - // content type autodetection - if ($this->isXML($markup)) { - phpQuery::debug("Loading XML, isXML() == true"); - $loaded = $this->loadMarkupXML($markup); - if (! $loaded && $this->isXHTML) { - phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true'); - $loaded = $this->loadMarkupHTML($markup); - } - } else { - phpQuery::debug("Loading HTML, isXML() == false"); - $loaded = $this->loadMarkupHTML($markup); - } - } - return $loaded; - } - protected function loadMarkupReset() { - $this->isXML = $this->isXHTML = $this->isHTML = false; - } - protected function documentCreate($charset, $version = '1.0') { - if (! $version) - $version = '1.0'; - $this->document = new DOMDocument($version, $charset); - $this->charset = $this->document->encoding; -// $this->document->encoding = $charset; - $this->document->formatOutput = true; - $this->document->preserveWhiteSpace = true; - } - protected function loadMarkupHTML($markup, $requestedCharset = null) { - if (phpQuery::$debug) - phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250)); - $this->loadMarkupReset(); - $this->isHTML = true; - if (!isset($this->isDocumentFragment)) - $this->isDocumentFragment = self::isDocumentFragmentHTML($markup); - $charset = null; - $documentCharset = $this->charsetFromHTML($markup); - $addDocumentCharset = false; - if ($documentCharset) { - $charset = $documentCharset; - $markup = $this->charsetFixHTML($markup); - } else if ($requestedCharset) { - $charset = $requestedCharset; - } - if (! $charset) - $charset = phpQuery::$defaultCharset; - // HTTP 1.1 says that the default charset is ISO-8859-1 - // @see http://www.w3.org/International/O-HTTP-charset - if (! $documentCharset) { - $documentCharset = 'ISO-8859-1'; - $addDocumentCharset = true; - } - // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' - // Worse, some pages can have mixed encodings... we'll try not to worry about that - $requestedCharset = strtoupper($requestedCharset); - $documentCharset = strtoupper($documentCharset); - phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset"); - if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) { - phpQuery::debug("CHARSET CONVERT"); - // Document Encoding Conversion - // http://code.google.com/p/phpquery/issues/detail?id=86 - if (function_exists('mb_detect_encoding')) { - $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO'); - $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets)); - if (! $docEncoding) - $docEncoding = $documentCharset; // ok trust the document - phpQuery::debug("DETECTED '$docEncoding'"); - // Detected does not match what document says... - if ($docEncoding !== $documentCharset) { - // Tricky.. - } - if ($docEncoding !== $requestedCharset) { - phpQuery::debug("CONVERT $docEncoding => $requestedCharset"); - $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding); - $markup = $this->charsetAppendToHTML($markup, $requestedCharset); - $charset = $requestedCharset; - } - } else { - phpQuery::debug("TODO: charset conversion without mbstring..."); - } - } - $return = false; - if ($this->isDocumentFragment) { - phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'"); - $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); - } else { - if ($addDocumentCharset) { - phpQuery::debug("Full markup load (HTML), appending charset: '$charset'"); - $markup = $this->charsetAppendToHTML($markup, $charset); - } - phpQuery::debug("Full markup load (HTML), documentCreate('$charset')"); - $this->documentCreate($charset); - $return = phpQuery::$debug === 2 - ? $this->document->loadHTML($markup) - : @$this->document->loadHTML($markup); - if ($return) - $this->root = $this->document; - } - if ($return && ! $this->contentType) - $this->contentType = 'text/html'; - return $return; - } - protected function loadMarkupXML($markup, $requestedCharset = null) { - if (phpQuery::$debug) - phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250)); - $this->loadMarkupReset(); - $this->isXML = true; - // check agains XHTML in contentType or markup - $isContentTypeXHTML = $this->isXHTML(); - $isMarkupXHTML = $this->isXHTML($markup); - if ($isContentTypeXHTML || $isMarkupXHTML) { - self::debug('Full markup load (XML), XHTML detected'); - $this->isXHTML = true; - } - // determine document fragment - if (! isset($this->isDocumentFragment)) - $this->isDocumentFragment = $this->isXHTML - ? self::isDocumentFragmentXHTML($markup) - : self::isDocumentFragmentXML($markup); - // this charset will be used - $charset = null; - // charset from XML declaration @var string - $documentCharset = $this->charsetFromXML($markup); - if (! $documentCharset) { - if ($this->isXHTML) { - // this is XHTML, try to get charset from content-type meta header - $documentCharset = $this->charsetFromHTML($markup); - if ($documentCharset) { - phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'"); - $this->charsetAppendToXML($markup, $documentCharset); - $charset = $documentCharset; - } - } - if (! $documentCharset) { - // if still no document charset... - $charset = $requestedCharset; - } - } else if ($requestedCharset) { - $charset = $requestedCharset; - } - if (! $charset) { - $charset = phpQuery::$defaultCharset; - } - if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) { - // TODO place for charset conversion -// $charset = $requestedCharset; - } - $return = false; - if ($this->isDocumentFragment) { - phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'"); - $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); - } else { - // FIXME ??? - if ($isContentTypeXHTML && ! $isMarkupXHTML) - if (! $documentCharset) { - phpQuery::debug("Full markup load (XML), appending charset '$charset'"); - $markup = $this->charsetAppendToXML($markup, $charset); - } - // see http://pl2.php.net/manual/en/book.dom.php#78929 - // LIBXML_DTDLOAD (>= PHP 5.1) - // does XML ctalogues works with LIBXML_NONET - // $this->document->resolveExternals = true; - // TODO test LIBXML_COMPACT for performance improvement - // create document - $this->documentCreate($charset); - if (phpversion() < 5.1) { - $this->document->resolveExternals = true; - $return = phpQuery::$debug === 2 - ? $this->document->loadXML($markup) - : @$this->document->loadXML($markup); - } else { - /** @link http://pl2.php.net/manual/en/libxml.constants.php */ - $libxmlStatic = phpQuery::$debug === 2 - ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET - : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR; - $return = $this->document->loadXML($markup, $libxmlStatic); -// if (! $return) -// $return = $this->document->loadHTML($markup); - } - if ($return) - $this->root = $this->document; - } - if ($return) { - if (! $this->contentType) { - if ($this->isXHTML) - $this->contentType = 'application/xhtml+xml'; - else - $this->contentType = 'text/xml'; - } - return $return; - } else { - throw new Exception("Error loading XML markup"); - } - } - protected function isXHTML($markup = null) { - if (! isset($markup)) { - return strpos($this->contentType, 'xhtml') !== false; - } - // XXX ok ? - return strpos($markup, "doctype) && is_object($dom->doctype) -// ? $dom->doctype->publicId -// : self::$defaultDoctype; - } - protected function isXML($markup) { -// return strpos($markup, ']+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', - $markup, $matches - ); - if (! isset($matches[0])) - return array(null, null); - // get attr 'content' - preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches); - if (! isset($matches[0])) - return array(null, null); - return $this->contentTypeToArray($matches[2]); - } - protected function charsetFromHTML($markup) { - $contentType = $this->contentTypeFromHTML($markup); - return $contentType[1]; - } - protected function charsetFromXML($markup) { - $matches; - // find declaration - preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i', - $markup, $matches - ); - return isset($matches[2]) - ? strtolower($matches[2]) - : null; - } - /** - * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug. - * - * @link http://code.google.com/p/phpquery/issues/detail?id=80 - * @param $html - */ - protected function charsetFixHTML($markup) { - $matches = array(); - // find meta tag - preg_match('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', - $markup, $matches, PREG_OFFSET_CAPTURE - ); - if (! isset($matches[0])) - return; - $metaContentType = $matches[0][0]; - $markup = substr($markup, 0, $matches[0][1]) - .substr($markup, $matches[0][1]+strlen($metaContentType)); - $headStart = stripos($markup, ''); - $markup = substr($markup, 0, $headStart+6).$metaContentType - .substr($markup, $headStart+6); - return $markup; - } - protected function charsetAppendToHTML($html, $charset, $xhtml = false) { - // remove existing meta[type=content-type] - $html = preg_replace('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html); - $meta = ''; - if (strpos($html, ')@s', - "{$meta}", - $html - ); - } - } else { - return preg_replace( - '@)@s', - ''.$meta, - $html - ); - } - } - protected function charsetAppendToXML($markup, $charset) { - $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>'; - return $declaration.$markup; - } - public static function isDocumentFragmentHTML($markup) { - return stripos($markup, 'documentFragmentCreate($node, $sourceCharset); -// if ($fake === false) -// throw new Exception("Error loading documentFragment markup"); -// else -// $return = array_merge($return, -// $this->import($fake->root->childNodes) -// ); -// } else { -// $return[] = $this->document->importNode($node, true); -// } -// } -// return $return; -// } else { -// // string markup -// $fake = $this->documentFragmentCreate($source, $sourceCharset); -// if ($fake === false) -// throw new Exception("Error loading documentFragment markup"); -// else -// return $this->import($fake->root->childNodes); -// } - if (is_array($source) || $source instanceof DOMNODELIST) { - // dom nodes - self::debug('Importing nodes to document'); - foreach($source as $node) - $return[] = $this->document->importNode($node, true); - } else { - // string markup - $fake = $this->documentFragmentCreate($source, $sourceCharset); - if ($fake === false) - throw new Exception("Error loading documentFragment markup"); - else - return $this->import($fake->root->childNodes); - } - return $return; - } - /** - * Creates new document fragment. - * - * @param $source - * @return DOMDocumentWrapper - */ - protected function documentFragmentCreate($source, $charset = null) { - $fake = new DOMDocumentWrapper(); - $fake->contentType = $this->contentType; - $fake->isXML = $this->isXML; - $fake->isHTML = $this->isHTML; - $fake->isXHTML = $this->isXHTML; - $fake->root = $fake->document; - if (! $charset) - $charset = $this->charset; -// $fake->documentCreate($this->charset); - if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) - $source = array($source); - if (is_array($source) || $source instanceof DOMNODELIST) { - // dom nodes - // load fake document - if (! $this->documentFragmentLoadMarkup($fake, $charset)) - return false; - $nodes = $fake->import($source); - foreach($nodes as $node) - $fake->root->appendChild($node); - } else { - // string markup - $this->documentFragmentLoadMarkup($fake, $charset, $source); - } - return $fake; - } - /** - * - * @param $document DOMDocumentWrapper - * @param $markup - * @return $document - */ - private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) { - // TODO error handling - // TODO copy doctype - // tempolary turn off - $fragment->isDocumentFragment = false; - if ($fragment->isXML) { - if ($fragment->isXHTML) { - // add FAKE element to set default namespace - $fragment->loadMarkupXML('' - .'' - .''.$markup.''); - $fragment->root = $fragment->document->firstChild->nextSibling; - } else { - $fragment->loadMarkupXML(''.$markup.''); - $fragment->root = $fragment->document->firstChild; - } - } else { - $markup2 = phpQuery::$defaultDoctype.''; - $noBody = strpos($markup, 'loadMarkupHTML($markup2); - // TODO resolv body tag merging issue - $fragment->root = $noBody - ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling - : $fragment->document->firstChild->nextSibling->firstChild->nextSibling; - } - if (! $fragment->root) - return false; - $fragment->isDocumentFragment = true; - return true; - } - protected function documentFragmentToMarkup($fragment) { - phpQuery::debug('documentFragmentToMarkup'); - $tmp = $fragment->isDocumentFragment; - $fragment->isDocumentFragment = false; - $markup = $fragment->markup(); - if ($fragment->isXML) { - $markup = substr($markup, 0, strrpos($markup, '')); - if ($fragment->isXHTML) { - $markup = substr($markup, strpos($markup, '')+6); - } - } else { - $markup = substr($markup, strpos($markup, '')+6); - $markup = substr($markup, 0, strrpos($markup, '')); - } - $fragment->isDocumentFragment = $tmp; - if (phpQuery::$debug) - phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150)); - return $markup; - } - /** - * Return document markup, starting with optional $nodes as root. - * - * @param $nodes DOMNode|DOMNodeList - * @return string - */ - public function markup($nodes = null, $innerMarkup = false) { - if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT) - $nodes = null; - if (isset($nodes)) { - $markup = ''; - if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) ) - $nodes = array($nodes); - if ($this->isDocumentFragment && ! $innerMarkup) - foreach($nodes as $i => $node) - if ($node->isSameNode($this->root)) { - // var_dump($node); - $nodes = array_slice($nodes, 0, $i) - + phpQuery::DOMNodeListToArray($node->childNodes) - + array_slice($nodes, $i+1); - } - if ($this->isXML && ! $innerMarkup) { - self::debug("Getting outerXML with charset '{$this->charset}'"); - // we need outerXML, so we can benefit from - // $node param support in saveXML() - foreach($nodes as $node) - $markup .= $this->document->saveXML($node); - } else { - $loop = array(); - if ($innerMarkup) - foreach($nodes as $node) { - if ($node->childNodes) - foreach($node->childNodes as $child) - $loop[] = $child; - else - $loop[] = $node; - } - else - $loop = $nodes; - self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment"); - $fake = $this->documentFragmentCreate($loop); - $markup = $this->documentFragmentToMarkup($fake); - } - if ($this->isXHTML) { - self::debug("Fixing XHTML"); - $markup = self::markupFixXHTML($markup); - } - self::debug("Markup: ".substr($markup, 0, 250)); - return $markup; - } else { - if ($this->isDocumentFragment) { - // documentFragment, html only... - self::debug("Getting markup, DocumentFragment detected"); -// return $this->markup( -//// $this->document->getElementsByTagName('body')->item(0) -// $this->document->root, true -// ); - $markup = $this->documentFragmentToMarkup($this); - // no need for markupFixXHTML, as it's done thought markup($nodes) method - return $markup; - } else { - self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'"); - $markup = $this->isXML - ? $this->document->saveXML() - : $this->document->saveHTML(); - if ($this->isXHTML) { - self::debug("Fixing XHTML"); - $markup = self::markupFixXHTML($markup); - } - self::debug("Markup: ".substr($markup, 0, 250)); - return $markup; - } - } - } - protected static function markupFixXHTML($markup) { - $markup = self::expandEmptyTag('script', $markup); - $markup = self::expandEmptyTag('select', $markup); - $markup = self::expandEmptyTag('textarea', $markup); - return $markup; - } - public static function debug($text) { - phpQuery::debug($text); - } - /** - * expandEmptyTag - * - * @param $tag - * @param $xml - * @return unknown_type - * @author mjaque at ilkebenson dot com - * @link http://php.net/manual/en/domdocument.savehtml.php#81256 - */ - public static function expandEmptyTag($tag, $xml){ - $indice = 0; - while ($indice< strlen($xml)){ - $pos = strpos($xml, "<$tag ", $indice); - if ($pos){ - $posCierre = strpos($xml, ">", $pos); - if ($xml[$posCierre-1] == "/"){ - $xml = substr_replace($xml, ">", $posCierre-1, 2); - } - $indice = $posCierre; - } - else break; - } - return $xml; - } -} - -/** - * Event handling class. - * - * @author Tobiasz Cudnik - * @package phpQuery - * @static - */ -abstract class phpQueryEvents { - /** - * Trigger a type of event on every matched element. - * - * @param DOMNode|phpQueryObject|string $document - * @param unknown_type $type - * @param unknown_type $data - * - * @TODO exclusive events (with !) - * @TODO global events (test) - * @TODO support more than event in $type (space-separated) - */ - public static function trigger($document, $type, $data = array(), $node = null) { - // trigger: function(type, data, elem, donative, extra) { - $documentID = phpQuery::getDocumentID($document); - $namespace = null; - if (strpos($type, '.') !== false) - list($name, $namespace) = explode('.', $type); - else - $name = $type; - if (! $node) { - if (self::issetGlobal($documentID, $type)) { - $pq = phpQuery::getDocument($documentID); - // TODO check add($pq->document) - $pq->find('*')->add($pq->document) - ->trigger($type, $data); - } - } else { - if (isset($data[0]) && $data[0] instanceof DOMEvent) { - $event = $data[0]; - $event->relatedTarget = $event->target; - $event->target = $node; - $data = array_slice($data, 1); - } else { - $event = new DOMEvent(array( - 'type' => $type, - 'target' => $node, - 'timeStamp' => time(), - )); - } - $i = 0; - while($node) { - // TODO whois - phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on " - ."node \n");//.phpQueryObject::whois($node)."\n"); - $event->currentTarget = $node; - $eventNode = self::getNode($documentID, $node); - if (isset($eventNode->eventHandlers)) { - foreach($eventNode->eventHandlers as $eventType => $handlers) { - $eventNamespace = null; - if (strpos($type, '.') !== false) - list($eventName, $eventNamespace) = explode('.', $eventType); - else - $eventName = $eventType; - if ($name != $eventName) - continue; - if ($namespace && $eventNamespace && $namespace != $eventNamespace) - continue; - foreach($handlers as $handler) { - phpQuery::debug("Calling event handler\n"); - $event->data = $handler['data'] - ? $handler['data'] - : null; - $params = array_merge(array($event), $data); - $return = phpQuery::callbackRun($handler['callback'], $params); - if ($return === false) { - $event->bubbles = false; - } - } - } - } - // to bubble or not to bubble... - if (! $event->bubbles) - break; - $node = $node->parentNode; - $i++; - } - } - } - /** - * Binds a handler to one or more events (like click) for each matched element. - * Can also bind custom events. - * - * @param DOMNode|phpQueryObject|string $document - * @param unknown_type $type - * @param unknown_type $data Optional - * @param unknown_type $callback - * - * @TODO support '!' (exclusive) events - * @TODO support more than event in $type (space-separated) - * @TODO support binding to global events - */ - public static function add($document, $node, $type, $data, $callback = null) { - phpQuery::debug("Binding '$type' event"); - $documentID = phpQuery::getDocumentID($document); -// if (is_null($callback) && is_callable($data)) { -// $callback = $data; -// $data = null; -// } - $eventNode = self::getNode($documentID, $node); - if (! $eventNode) - $eventNode = self::setNode($documentID, $node); - if (!isset($eventNode->eventHandlers[$type])) - $eventNode->eventHandlers[$type] = array(); - $eventNode->eventHandlers[$type][] = array( - 'callback' => $callback, - 'data' => $data, - ); - } - /** - * Enter description here... - * - * @param DOMNode|phpQueryObject|string $document - * @param unknown_type $type - * @param unknown_type $callback - * - * @TODO namespace events - * @TODO support more than event in $type (space-separated) - */ - public static function remove($document, $node, $type = null, $callback = null) { - $documentID = phpQuery::getDocumentID($document); - $eventNode = self::getNode($documentID, $node); - if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) { - if ($callback) { - foreach($eventNode->eventHandlers[$type] as $k => $handler) - if ($handler['callback'] == $callback) - unset($eventNode->eventHandlers[$type][$k]); - } else { - unset($eventNode->eventHandlers[$type]); - } - } - } - protected static function getNode($documentID, $node) { - foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) { - if ($node->isSameNode($eventNode)) - return $eventNode; - } - } - protected static function setNode($documentID, $node) { - phpQuery::$documents[$documentID]->eventsNodes[] = $node; - return phpQuery::$documents[$documentID]->eventsNodes[ - count(phpQuery::$documents[$documentID]->eventsNodes)-1 - ]; - } - protected static function issetGlobal($documentID, $type) { - return isset(phpQuery::$documents[$documentID]) - ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal) - : false; - } -} - - -interface ICallbackNamed { - function hasName(); - function getName(); -} -/** - * Callback class introduces currying-like pattern. - * - * Example: - * function foo($param1, $param2, $param3) { - * var_dump($param1, $param2, $param3); - * } - * $fooCurried = new Callback('foo', - * 'param1 is now statically set', - * new CallbackParam, new CallbackParam - * ); - * phpQuery::callbackRun($fooCurried, - * array('param2 value', 'param3 value' - * ); - * - * Callback class is supported in all phpQuery methods which accepts callbacks. - * - * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures - * @author Tobiasz Cudnik - * - * @TODO??? return fake forwarding function created via create_function - * @TODO honor paramStructure - */ -class Callback - implements ICallbackNamed { - public $callback = null; - public $params = null; - protected $name; - public function __construct($callback, $param1 = null, $param2 = null, - $param3 = null) { - $params = func_get_args(); - $params = array_slice($params, 1); - if ($callback instanceof Callback) { - // TODO implement recurention - } else { - $this->callback = $callback; - $this->params = $params; - } - } - public function getName() { - return 'Callback: '.$this->name; - } - public function hasName() { - return isset($this->name) && $this->name; - } - public function setName($name) { - $this->name = $name; - return $this; - } - // TODO test me -// public function addParams() { -// $params = func_get_args(); -// return new Callback($this->callback, $this->params+$params); -// } -} -/** - * Shorthand for new Callback(create_function(...), ...); - * - * @author Tobiasz Cudnik - */ -class CallbackBody extends Callback { - public function __construct($paramList, $code, $param1 = null, $param2 = null, - $param3 = null) { - $params = func_get_args(); - $params = array_slice($params, 2); - $this->callback = create_function($paramList, $code); - $this->params = $params; - } -} -/** - * Callback type which on execution returns reference passed during creation. - * - * @author Tobiasz Cudnik - */ -class CallbackReturnReference extends Callback - implements ICallbackNamed { - protected $reference; - public function __construct(&$reference, $name = null){ - $this->reference =& $reference; - $this->callback = array($this, 'callback'); - } - public function callback() { - return $this->reference; - } - public function getName() { - return 'Callback: '.$this->name; - } - public function hasName() { - return isset($this->name) && $this->name; - } -} -/** - * Callback type which on execution returns value passed during creation. - * - * @author Tobiasz Cudnik - */ -class CallbackReturnValue extends Callback - implements ICallbackNamed { - protected $value; - protected $name; - public function __construct($value, $name = null){ - $this->value =& $value; - $this->name = $name; - $this->callback = array($this, 'callback'); - } - public function callback() { - return $this->value; - } - public function __toString() { - return $this->getName(); - } - public function getName() { - return 'Callback: '.$this->name; - } - public function hasName() { - return isset($this->name) && $this->name; - } -} -/** - * CallbackParameterToReference can be used when we don't really want a callback, - * only parameter passed to it. CallbackParameterToReference takes first - * parameter's value and passes it to reference. - * - * @author Tobiasz Cudnik - */ -class CallbackParameterToReference extends Callback { - /** - * @param $reference - * @TODO implement $paramIndex; - * param index choose which callback param will be passed to reference - */ - public function __construct(&$reference){ - $this->callback =& $reference; - } -} -//class CallbackReference extends Callback { -// /** -// * -// * @param $reference -// * @param $paramIndex -// * @todo implement $paramIndex; param index choose which callback param will be passed to reference -// */ -// public function __construct(&$reference, $name = null){ -// $this->callback =& $reference; -// } -//} -class CallbackParam {} - -/** - * Class representing phpQuery objects. - * - * @author Tobiasz Cudnik - * @package phpQuery - * @method phpQueryObject clone() clone() - * @method phpQueryObject empty() empty() - * @method phpQueryObject next() next($selector = null) - * @method phpQueryObject prev() prev($selector = null) - * @property Int $length - */ -class phpQueryObject - implements Iterator, Countable, ArrayAccess { - public $documentID = null; - /** - * DOMDocument class. - * - * @var DOMDocument - */ - public $document = null; - public $charset = null; - /** - * - * @var DOMDocumentWrapper - */ - public $documentWrapper = null; - /** - * XPath interface. - * - * @var DOMXPath - */ - public $xpath = null; - /** - * Stack of selected elements. - * @TODO refactor to ->nodes - * @var array - */ - public $elements = array(); - /** - * @access private - */ - protected $elementsBackup = array(); - /** - * @access private - */ - protected $previous = null; - /** - * @access private - * @TODO deprecate - */ - protected $root = array(); - /** - * Indicated if doument is just a fragment (no tag). - * - * Every document is realy a full document, so even documentFragments can - * be queried against , but getDocument(id)->htmlOuter() will return - * only contents of . - * - * @var bool - */ - public $documentFragment = true; - /** - * Iterator interface helper - * @access private - */ - protected $elementsInterator = array(); - /** - * Iterator interface helper - * @access private - */ - protected $valid = false; - /** - * Iterator interface helper - * @access private - */ - protected $current = null; - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function __construct($documentID) { -// if ($documentID instanceof self) -// var_dump($documentID->getDocumentID()); - $id = $documentID instanceof self - ? $documentID->getDocumentID() - : $documentID; -// var_dump($id); - if (! isset(phpQuery::$documents[$id] )) { -// var_dump(phpQuery::$documents); - throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first."); - } - $this->documentID = $id; - $this->documentWrapper =& phpQuery::$documents[$id]; - $this->document =& $this->documentWrapper->document; - $this->xpath =& $this->documentWrapper->xpath; - $this->charset =& $this->documentWrapper->charset; - $this->documentFragment =& $this->documentWrapper->isDocumentFragment; - // TODO check $this->DOM->documentElement; -// $this->root = $this->document->documentElement; - $this->root =& $this->documentWrapper->root; -// $this->toRoot(); - $this->elements = array($this->root); - } - /** - * - * @access private - * @param $attr - * @return unknown_type - */ - public function __get($attr) { - switch($attr) { - // FIXME doesnt work at all ? - case 'length': - return $this->size(); - break; - default: - return $this->$attr; - } - } - /** - * Saves actual object to $var by reference. - * Useful when need to break chain. - * @param phpQueryObject $var - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function toReference(&$var) { - return $var = $this; - } - public function documentFragment($state = null) { - if ($state) { - phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state; - return $this; - } - return $this->documentFragment; - } - /** - * @access private - * @TODO documentWrapper - */ - protected function isRoot( $node) { -// return $node instanceof DOMDOCUMENT || $node->tagName == 'html'; - return $node instanceof DOMDOCUMENT - || ($node instanceof DOMELEMENT && $node->tagName == 'html') - || $this->root->isSameNode($node); - } - /** - * @access private - */ - protected function stackIsRoot() { - return $this->size() == 1 && $this->isRoot($this->elements[0]); - } - /** - * Enter description here... - * NON JQUERY METHOD - * - * Watch out, it doesn't creates new instance, can be reverted with end(). - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function toRoot() { - $this->elements = array($this->root); - return $this; -// return $this->newInstance(array($this->root)); - } - /** - * Saves object's DocumentID to $var by reference. - * - * $myDocumentId; - * phpQuery::newDocument('
    ') - * ->getDocumentIDRef($myDocumentId) - * ->find('div')->... - * - * - * @param unknown_type $domId - * @see phpQuery::newDocument - * @see phpQuery::newDocumentFile - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function getDocumentIDRef(&$documentID) { - $documentID = $this->getDocumentID(); - return $this; - } - /** - * Returns object with stack set to document root. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function getDocument() { - return phpQuery::getDocument($this->getDocumentID()); - } - /** - * - * @return DOMDocument - */ - public function getDOMDocument() { - return $this->document; - } - /** - * Get object's Document ID. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function getDocumentID() { - return $this->documentID; - } - /** - * Unloads whole document from memory. - * CAUTION! None further operations will be possible on this document. - * All objects refering to it will be useless. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function unloadDocument() { - phpQuery::unloadDocuments($this->getDocumentID()); - } - public function isHTML() { - return $this->documentWrapper->isHTML; - } - public function isXHTML() { - return $this->documentWrapper->isXHTML; - } - public function isXML() { - return $this->documentWrapper->isXML; - } - /** - * Enter description here... - * - * @link http://docs.jquery.com/Ajax/serialize - * @return string - */ - public function serialize() { - return phpQuery::param($this->serializeArray()); - } - /** - * Enter description here... - * - * @link http://docs.jquery.com/Ajax/serializeArray - * @return array - */ - public function serializeArray($submit = null) { - $source = $this->filter('form, input, select, textarea') - ->find('input, select, textarea') - ->andSelf() - ->not('form'); - $return = array(); -// $source->dumpDie(); - foreach($source as $input) { - $input = phpQuery::pq($input); - if ($input->is('[disabled]')) - continue; - if (!$input->is('[name]')) - continue; - if ($input->is('[type=checkbox]') && !$input->is('[checked]')) - continue; - // jquery diff - if ($submit && $input->is('[type=submit]')) { - if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit)) - continue; - else if (is_string($submit) && $input->attr('name') != $submit) - continue; - } - $return[] = array( - 'name' => $input->attr('name'), - 'value' => $input->val(), - ); - } - return $return; - } - /** - * @access private - */ - protected function debug($in) { - if (! phpQuery::$debug ) - return; - print('
    ');
    -		print_r($in);
    -		// file debug
    -//		file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
    -		// quite handy debug trace
    -//		if ( is_array($in))
    -//			print_r(array_slice(debug_backtrace(), 3));
    -		print("
    \n"); - } - /** - * @access private - */ - protected function isRegexp($pattern) { - return in_array( - $pattern[ mb_strlen($pattern)-1 ], - array('^','*','$') - ); - } - /** - * Determines if $char is really a char. - * - * @param string $char - * @return bool - * @todo rewrite me to charcode range ! ;) - * @access private - */ - protected function isChar($char) { - return extension_loaded('mbstring') && phpQuery::$mbstringSupport - ? mb_eregi('\w', $char) - : preg_match('@\w@', $char); - } - /** - * @access private - */ - protected function parseSelector($query) { - // clean spaces - // TODO include this inside parsing ? - $query = trim( - preg_replace('@\s+@', ' ', - preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) - ) - ); - $queries = array(array()); - if (! $query) - return $queries; - $return =& $queries[0]; - $specialChars = array('>',' '); -// $specialCharsMapping = array('/' => '>'); - $specialCharsMapping = array(); - $strlen = mb_strlen($query); - $classChars = array('.', '-'); - $pseudoChars = array('-'); - $tagChars = array('*', '|', '-'); - // split multibyte string - // http://code.google.com/p/phpquery/issues/detail?id=76 - $_query = array(); - for ($i=0; $i<$strlen; $i++) - $_query[] = mb_substr($query, $i, 1); - $query = $_query; - // it works, but i dont like it... - $i = 0; - while( $i < $strlen) { - $c = $query[$i]; - $tmp = ''; - // TAG - if ($this->isChar($c) || in_array($c, $tagChars)) { - while(isset($query[$i]) - && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) { - $tmp .= $query[$i]; - $i++; - } - $return[] = $tmp; - // IDs - } else if ( $c == '#') { - $i++; - while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) { - $tmp .= $query[$i]; - $i++; - } - $return[] = '#'.$tmp; - // SPECIAL CHARS - } else if (in_array($c, $specialChars)) { - $return[] = $c; - $i++; - // MAPPED SPECIAL MULTICHARS -// } else if ( $c.$query[$i+1] == '//') { -// $return[] = ' '; -// $i = $i+2; - // MAPPED SPECIAL CHARS - } else if ( isset($specialCharsMapping[$c])) { - $return[] = $specialCharsMapping[$c]; - $i++; - // COMMA - } else if ( $c == ',') { - $queries[] = array(); - $return =& $queries[ count($queries)-1 ]; - $i++; - while( isset($query[$i]) && $query[$i] == ' ') - $i++; - // CLASSES - } else if ($c == '.') { - while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { - $tmp .= $query[$i]; - $i++; - } - $return[] = $tmp; - // ~ General Sibling Selector - } else if ($c == '~') { - $spaceAllowed = true; - $tmp .= $query[$i++]; - while( isset($query[$i]) - && ($this->isChar($query[$i]) - || in_array($query[$i], $classChars) - || $query[$i] == '*' - || ($query[$i] == ' ' && $spaceAllowed) - )) { - if ($query[$i] != ' ') - $spaceAllowed = false; - $tmp .= $query[$i]; - $i++; - } - $return[] = $tmp; - // + Adjacent sibling selectors - } else if ($c == '+') { - $spaceAllowed = true; - $tmp .= $query[$i++]; - while( isset($query[$i]) - && ($this->isChar($query[$i]) - || in_array($query[$i], $classChars) - || $query[$i] == '*' - || ($spaceAllowed && $query[$i] == ' ') - )) { - if ($query[$i] != ' ') - $spaceAllowed = false; - $tmp .= $query[$i]; - $i++; - } - $return[] = $tmp; - // ATTRS - } else if ($c == '[') { - $stack = 1; - $tmp .= $c; - while( isset($query[++$i])) { - $tmp .= $query[$i]; - if ( $query[$i] == '[') { - $stack++; - } else if ( $query[$i] == ']') { - $stack--; - if (! $stack ) - break; - } - } - $return[] = $tmp; - $i++; - // PSEUDO CLASSES - } else if ($c == ':') { - $stack = 1; - $tmp .= $query[$i++]; - while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { - $tmp .= $query[$i]; - $i++; - } - // with arguments ? - if ( isset($query[$i]) && $query[$i] == '(') { - $tmp .= $query[$i]; - $stack = 1; - while( isset($query[++$i])) { - $tmp .= $query[$i]; - if ( $query[$i] == '(') { - $stack++; - } else if ( $query[$i] == ')') { - $stack--; - if (! $stack ) - break; - } - } - $return[] = $tmp; - $i++; - } else { - $return[] = $tmp; - } - } else { - $i++; - } - } - foreach($queries as $k => $q) { - if (isset($q[0])) { - if (isset($q[0][0]) && $q[0][0] == ':') - array_unshift($queries[$k], '*'); - if ($q[0] != '>') - array_unshift($queries[$k], ' '); - } - } - return $queries; - } - /** - * Return matched DOM nodes. - * - * @param int $index - * @return array|DOMElement Single DOMElement or array of DOMElement. - */ - public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { - $return = isset($index) - ? (isset($this->elements[$index]) ? $this->elements[$index] : null) - : $this->elements; - // pass thou callbacks - $args = func_get_args(); - $args = array_slice($args, 1); - foreach($args as $callback) { - if (is_array($return)) - foreach($return as $k => $v) - $return[$k] = phpQuery::callbackRun($callback, array($v)); - else - $return = phpQuery::callbackRun($callback, array($return)); - } - return $return; - } - /** - * Return matched DOM nodes. - * jQuery difference. - * - * @param int $index - * @return array|string Returns string if $index != null - * @todo implement callbacks - * @todo return only arrays ? - * @todo maybe other name... - */ - public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { - if ($index) - $return = $this->eq($index)->text(); - else { - $return = array(); - for($i = 0; $i < $this->size(); $i++) { - $return[] = $this->eq($i)->text(); - } - } - // pass thou callbacks - $args = func_get_args(); - $args = array_slice($args, 1); - foreach($args as $callback) { - $return = phpQuery::callbackRun($callback, array($return)); - } - return $return; - } - /** - * Return matched DOM nodes. - * jQuery difference. - * - * @param int $index - * @return array|string Returns string if $index != null - * @todo implement callbacks - * @todo return only arrays ? - * @todo maybe other name... - */ - public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { - if ($index) - $return = $this->eq($index)->text(); - else { - $return = array(); - for($i = 0; $i < $this->size(); $i++) { - $return[] = $this->eq($i)->text(); - } - // pass thou callbacks - $args = func_get_args(); - $args = array_slice($args, 1); - } - foreach($args as $callback) { - if (is_array($return)) - foreach($return as $k => $v) - $return[$k] = phpQuery::callbackRun($callback, array($v)); - else - $return = phpQuery::callbackRun($callback, array($return)); - } - return $return; - } - /** - * Returns new instance of actual class. - * - * @param array $newStack Optional. Will replace old stack with new and move old one to history.c - */ - public function newInstance($newStack = null) { - $class = get_class($this); - // support inheritance by passing old object to overloaded constructor - $new = $class != 'phpQuery' - ? new $class($this, $this->getDocumentID()) - : new phpQueryObject($this->getDocumentID()); - $new->previous = $this; - if (is_null($newStack)) { - $new->elements = $this->elements; - if ($this->elementsBackup) - $this->elements = $this->elementsBackup; - } else if (is_string($newStack)) { - $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack(); - } else { - $new->elements = $newStack; - } - return $new; - } - /** - * Enter description here... - * - * In the future, when PHP will support XLS 2.0, then we would do that this way: - * contains(tokenize(@class, '\s'), "something") - * @param unknown_type $class - * @param unknown_type $node - * @return boolean - * @access private - */ - protected function matchClasses($class, $node) { - // multi-class - if ( mb_strpos($class, '.', 1)) { - $classes = explode('.', substr($class, 1)); - $classesCount = count( $classes ); - $nodeClasses = explode(' ', $node->getAttribute('class') ); - $nodeClassesCount = count( $nodeClasses ); - if ( $classesCount > $nodeClassesCount ) - return false; - $diff = count( - array_diff( - $classes, - $nodeClasses - ) - ); - if (! $diff ) - return true; - // single-class - } else { - return in_array( - // strip leading dot from class name - substr($class, 1), - // get classes for element as array - explode(' ', $node->getAttribute('class') ) - ); - } - } - /** - * @access private - */ - protected function runQuery($XQuery, $selector = null, $compare = null) { - if ($compare && ! method_exists($this, $compare)) - return false; - $stack = array(); - if (! $this->elements) - $this->debug('Stack empty, skipping...'); -// var_dump($this->elements[0]->nodeType); - // element, document - foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) { - $detachAfter = false; - // to work on detached nodes we need temporary place them somewhere - // thats because context xpath queries sucks ;] - $testNode = $stackNode; - while ($testNode) { - if (! $testNode->parentNode && ! $this->isRoot($testNode)) { - $this->root->appendChild($testNode); - $detachAfter = $testNode; - break; - } - $testNode = isset($testNode->parentNode) - ? $testNode->parentNode - : null; - } - // XXX tmp ? - $xpath = $this->documentWrapper->isXHTML - ? $this->getNodeXpath($stackNode, 'html') - : $this->getNodeXpath($stackNode); - // FIXME pseudoclasses-only query, support XML - $query = $XQuery == '//' && $xpath == '/html[1]' - ? '//*' - : $xpath.$XQuery; - $this->debug("XPATH: {$query}"); - // run query, get elements - $nodes = $this->xpath->query($query); - $this->debug("QUERY FETCHED"); - if (! $nodes->length ) - $this->debug('Nothing found'); - $debug = array(); - foreach($nodes as $node) { - $matched = false; - if ( $compare) { - phpQuery::$debug ? - $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") - : null; - $phpQueryDebug = phpQuery::$debug; - phpQuery::$debug = false; - // TODO ??? use phpQuery::callbackRun() - if (call_user_func_array(array($this, $compare), array($selector, $node))) - $matched = true; - phpQuery::$debug = $phpQueryDebug; - } else { - $matched = true; - } - if ( $matched) { - if (phpQuery::$debug) - $debug[] = $this->whois( $node ); - $stack[] = $node; - } - } - if (phpQuery::$debug) { - $this->debug("Matched ".count($debug).": ".implode(', ', $debug)); - } - if ($detachAfter) - $this->root->removeChild($detachAfter); - } - $this->elements = $stack; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function find($selectors, $context = null, $noHistory = false) { - if (!$noHistory) - // backup last stack /for end()/ - $this->elementsBackup = $this->elements; - // allow to define context - // TODO combine code below with phpQuery::pq() context guessing code - // as generic function - if ($context) { - if (! is_array($context) && $context instanceof DOMELEMENT) - $this->elements = array($context); - else if (is_array($context)) { - $this->elements = array(); - foreach ($context as $c) - if ($c instanceof DOMELEMENT) - $this->elements[] = $c; - } else if ( $context instanceof self ) - $this->elements = $context->elements; - } - $queries = $this->parseSelector($selectors); - $this->debug(array('FIND', $selectors, $queries)); - $XQuery = ''; - // remember stack state because of multi-queries - $oldStack = $this->elements; - // here we will be keeping found elements - $stack = array(); - foreach($queries as $selector) { - $this->elements = $oldStack; - $delimiterBefore = false; - foreach($selector as $s) { - // TAG - $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport - ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*' - : preg_match('@^[\w|\||-]+$@', $s) || $s == '*'; - if ($isTag) { - if ($this->isXML()) { - // namespace support - if (mb_strpos($s, '|') !== false) { - $ns = $tag = null; - list($ns, $tag) = explode('|', $s); - $XQuery .= "$ns:$tag"; - } else if ($s == '*') { - $XQuery .= "*"; - } else { - $XQuery .= "*[local-name()='$s']"; - } - } else { - $XQuery .= $s; - } - // ID - } else if ($s[0] == '#') { - if ($delimiterBefore) - $XQuery .= '*'; - $XQuery .= "[@id='".substr($s, 1)."']"; - // ATTRIBUTES - } else if ($s[0] == '[') { - if ($delimiterBefore) - $XQuery .= '*'; - // strip side brackets - $attr = trim($s, ']['); - $execute = false; - // attr with specifed value - if (mb_strpos($s, '=')) { - $value = null; - list($attr, $value) = explode('=', $attr); - $value = trim($value, "'\""); - if ($this->isRegexp($attr)) { - // cut regexp character - $attr = substr($attr, 0, -1); - $execute = true; - $XQuery .= "[@{$attr}]"; - } else { - $XQuery .= "[@{$attr}='{$value}']"; - } - // attr without specified value - } else { - $XQuery .= "[@{$attr}]"; - } - if ($execute) { - $this->runQuery($XQuery, $s, 'is'); - $XQuery = ''; - if (! $this->length()) - break; - } - // CLASSES - } else if ($s[0] == '.') { - // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]"); - // thx wizDom ;) - if ($delimiterBefore) - $XQuery .= '*'; - $XQuery .= '[@class]'; - $this->runQuery($XQuery, $s, 'matchClasses'); - $XQuery = ''; - if (! $this->length() ) - break; - // ~ General Sibling Selector - } else if ($s[0] == '~') { - $this->runQuery($XQuery); - $XQuery = ''; - $this->elements = $this - ->siblings( - substr($s, 1) - )->elements; - if (! $this->length() ) - break; - // + Adjacent sibling selectors - } else if ($s[0] == '+') { - // TODO /following-sibling:: - $this->runQuery($XQuery); - $XQuery = ''; - $subSelector = substr($s, 1); - $subElements = $this->elements; - $this->elements = array(); - foreach($subElements as $node) { - // search first DOMElement sibling - $test = $node->nextSibling; - while($test && ! ($test instanceof DOMELEMENT)) - $test = $test->nextSibling; - if ($test && $this->is($subSelector, $test)) - $this->elements[] = $test; - } - if (! $this->length() ) - break; - // PSEUDO CLASSES - } else if ($s[0] == ':') { - // TODO optimization for :first :last - if ($XQuery) { - $this->runQuery($XQuery); - $XQuery = ''; - } - if (! $this->length()) - break; - $this->pseudoClasses($s); - if (! $this->length()) - break; - // DIRECT DESCENDANDS - } else if ($s == '>') { - $XQuery .= '/'; - $delimiterBefore = 2; - // ALL DESCENDANDS - } else if ($s == ' ') { - $XQuery .= '//'; - $delimiterBefore = 2; - // ERRORS - } else { - phpQuery::debug("Unrecognized token '$s'"); - } - $delimiterBefore = $delimiterBefore === 2; - } - // run query if any - if ($XQuery && $XQuery != '//') { - $this->runQuery($XQuery); - $XQuery = ''; - } - foreach($this->elements as $node) - if (! $this->elementsContainsNode($node, $stack)) - $stack[] = $node; - } - $this->elements = $stack; - return $this->newInstance(); - } - /** - * @todo create API for classes with pseudoselectors - * @access private - */ - protected function pseudoClasses($class) { - // TODO clean args parsing ? - $class = ltrim($class, ':'); - $haveArgs = mb_strpos($class, '('); - if ($haveArgs !== false) { - $args = substr($class, $haveArgs+1, -1); - $class = substr($class, 0, $haveArgs); - } - switch($class) { - case 'even': - case 'odd': - $stack = array(); - foreach($this->elements as $i => $node) { - if ($class == 'even' && ($i%2) == 0) - $stack[] = $node; - else if ( $class == 'odd' && $i % 2 ) - $stack[] = $node; - } - $this->elements = $stack; - break; - case 'eq': - $k = intval($args); - $this->elements = isset( $this->elements[$k] ) - ? array( $this->elements[$k] ) - : array(); - break; - case 'gt': - $this->elements = array_slice($this->elements, $args+1); - break; - case 'lt': - $this->elements = array_slice($this->elements, 0, $args+1); - break; - case 'first': - if (isset($this->elements[0])) - $this->elements = array($this->elements[0]); - break; - case 'last': - if ($this->elements) - $this->elements = array($this->elements[count($this->elements)-1]); - break; - /*case 'parent': - $stack = array(); - foreach($this->elements as $node) { - if ( $node->childNodes->length ) - $stack[] = $node; - } - $this->elements = $stack; - break;*/ - case 'contains': - $text = trim($args, "\"'"); - $stack = array(); - foreach($this->elements as $node) { - if (mb_stripos($node->textContent, $text) === false) - continue; - $stack[] = $node; - } - $this->elements = $stack; - break; - case 'not': - $selector = self::unQuote($args); - $this->elements = $this->not($selector)->stack(); - break; - case 'slice': - // TODO jQuery difference ? - $args = explode(',', - str_replace(', ', ',', trim($args, "\"'")) - ); - $start = $args[0]; - $end = isset($args[1]) - ? $args[1] - : null; - if ($end > 0) - $end = $end-$start; - $this->elements = array_slice($this->elements, $start, $end); - break; - case 'has': - $selector = trim($args, "\"'"); - $stack = array(); - foreach($this->stack(1) as $el) { - if ($this->find($selector, $el, true)->length) - $stack[] = $el; - } - $this->elements = $stack; - break; - case 'submit': - case 'reset': - $this->elements = phpQuery::merge( - $this->map(array($this, 'is'), - "input[type=$class]", new CallbackParam() - ), - $this->map(array($this, 'is'), - "button[type=$class]", new CallbackParam() - ) - ); - break; -// $stack = array(); -// foreach($this->elements as $node) -// if ($node->is('input[type=submit]') || $node->is('button[type=submit]')) -// $stack[] = $el; -// $this->elements = $stack; - case 'input': - $this->elements = $this->map( - array($this, 'is'), - 'input', new CallbackParam() - )->elements; - break; - case 'password': - case 'checkbox': - case 'radio': - case 'hidden': - case 'image': - case 'file': - $this->elements = $this->map( - array($this, 'is'), - "input[type=$class]", new CallbackParam() - )->elements; - break; - case 'parent': - $this->elements = $this->map( - create_function('$node', ' - return $node instanceof DOMELEMENT && $node->childNodes->length - ? $node : null;') - )->elements; - break; - case 'empty': - $this->elements = $this->map( - create_function('$node', ' - return $node instanceof DOMELEMENT && $node->childNodes->length - ? null : $node;') - )->elements; - break; - case 'disabled': - case 'selected': - case 'checked': - $this->elements = $this->map( - array($this, 'is'), - "[$class]", new CallbackParam() - )->elements; - break; - case 'enabled': - $this->elements = $this->map( - create_function('$node', ' - return pq($node)->not(":disabled") ? $node : null;') - )->elements; - break; - case 'header': - $this->elements = $this->map( - create_function('$node', - '$isHeader = isset($node->tagName) && in_array($node->tagName, array( - "h1", "h2", "h3", "h4", "h5", "h6", "h7" - )); - return $isHeader - ? $node - : null;') - )->elements; -// $this->elements = $this->map( -// create_function('$node', '$node = pq($node); -// return $node->is("h1") -// || $node->is("h2") -// || $node->is("h3") -// || $node->is("h4") -// || $node->is("h5") -// || $node->is("h6") -// || $node->is("h7") -// ? $node -// : null;') -// )->elements; - break; - case 'only-child': - $this->elements = $this->map( - create_function('$node', - 'return pq($node)->siblings()->size() == 0 ? $node : null;') - )->elements; - break; - case 'first-child': - $this->elements = $this->map( - create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;') - )->elements; - break; - case 'last-child': - $this->elements = $this->map( - create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;') - )->elements; - break; - case 'nth-child': - $param = trim($args, "\"'"); - if (! $param) - break; - // nth-child(n+b) to nth-child(1n+b) - if ($param{0} == 'n') - $param = '1'.$param; - // :nth-child(index/even/odd/equation) - if ($param == 'even' || $param == 'odd') - $mapped = $this->map( - create_function('$node, $param', - '$index = pq($node)->prevAll()->size()+1; - if ($param == "even" && ($index%2) == 0) - return $node; - else if ($param == "odd" && $index%2 == 1) - return $node; - else - return null;'), - new CallbackParam(), $param - ); - else if (mb_strlen($param) > 1 && $param{1} == 'n') - // an+b - $mapped = $this->map( - create_function('$node, $param', - '$prevs = pq($node)->prevAll()->size(); - $index = 1+$prevs; - $b = mb_strlen($param) > 3 - ? $param{3} - : 0; - $a = $param{0}; - if ($b && $param{2} == "-") - $b = -$b; - if ($a > 0) { - return ($index-$b)%$a == 0 - ? $node - : null; - phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs"); - return $a*floor($index/$a)+$b-1 == $prevs - ? $node - : null; - } else if ($a == 0) - return $index == $b - ? $node - : null; - else - // negative value - return $index <= $b - ? $node - : null; -// if (! $b) -// return $index%$a == 0 -// ? $node -// : null; -// else -// return ($index-$b)%$a == 0 -// ? $node -// : null; - '), - new CallbackParam(), $param - ); - else - // index - $mapped = $this->map( - create_function('$node, $index', - '$prevs = pq($node)->prevAll()->size(); - if ($prevs && $prevs == $index-1) - return $node; - else if (! $prevs && $index == 1) - return $node; - else - return null;'), - new CallbackParam(), $param - ); - $this->elements = $mapped->elements; - break; - default: - $this->debug("Unknown pseudoclass '{$class}', skipping..."); - } - } - /** - * @access private - */ - protected function __pseudoClassParam($paramsString) { - // TODO; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function is($selector, $nodes = null) { - phpQuery::debug(array("Is:", $selector)); - if (! $selector) - return false; - $oldStack = $this->elements; - $returnArray = false; - if ($nodes && is_array($nodes)) { - $this->elements = $nodes; - } else if ($nodes) - $this->elements = array($nodes); - $this->filter($selector, true); - $stack = $this->elements; - $this->elements = $oldStack; - if ($nodes) - return $stack ? $stack : null; - return (bool)count($stack); - } - /** - * Enter description here... - * jQuery difference. - * - * Callback: - * - $index int - * - $node DOMNode - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @link http://docs.jquery.com/Traversing/filter - */ - public function filterCallback($callback, $_skipHistory = false) { - if (! $_skipHistory) { - $this->elementsBackup = $this->elements; - $this->debug("Filtering by callback"); - } - $newStack = array(); - foreach($this->elements as $index => $node) { - $result = phpQuery::callbackRun($callback, array($index, $node)); - if (is_null($result) || (! is_null($result) && $result)) - $newStack[] = $node; - } - $this->elements = $newStack; - return $_skipHistory - ? $this - : $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @link http://docs.jquery.com/Traversing/filter - */ - public function filter($selectors, $_skipHistory = false) { - if ($selectors instanceof Callback OR $selectors instanceof Closure) - return $this->filterCallback($selectors, $_skipHistory); - if (! $_skipHistory) - $this->elementsBackup = $this->elements; - $notSimpleSelector = array(' ', '>', '~', '+', '/'); - if (! is_array($selectors)) - $selectors = $this->parseSelector($selectors); - if (! $_skipHistory) - $this->debug(array("Filtering:", $selectors)); - $finalStack = array(); - foreach($selectors as $selector) { - $stack = array(); - if (! $selector) - break; - // avoid first space or / - if (in_array($selector[0], $notSimpleSelector)) - $selector = array_slice($selector, 1); - // PER NODE selector chunks - foreach($this->stack() as $node) { - $break = false; - foreach($selector as $s) { - if (!($node instanceof DOMELEMENT)) { - // all besides DOMElement - if ( $s[0] == '[') { - $attr = trim($s, '[]'); - if ( mb_strpos($attr, '=')) { - list( $attr, $val ) = explode('=', $attr); - if ($attr == 'nodeType' && $node->nodeType != $val) - $break = true; - } - } else - $break = true; - } else { - // DOMElement only - // ID - if ( $s[0] == '#') { - if ( $node->getAttribute('id') != substr($s, 1) ) - $break = true; - // CLASSES - } else if ( $s[0] == '.') { - if (! $this->matchClasses( $s, $node ) ) - $break = true; - // ATTRS - } else if ( $s[0] == '[') { - // strip side brackets - $attr = trim($s, '[]'); - if (mb_strpos($attr, '=')) { - list($attr, $val) = explode('=', $attr); - $val = self::unQuote($val); - if ($attr == 'nodeType') { - if ($val != $node->nodeType) - $break = true; - } else if ($this->isRegexp($attr)) { - $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport - ? quotemeta(trim($val, '"\'')) - : preg_quote(trim($val, '"\''), '@'); - // switch last character - switch( substr($attr, -1)) { - // quotemeta used insted of preg_quote - // http://code.google.com/p/phpquery/issues/detail?id=76 - case '^': - $pattern = '^'.$val; - break; - case '*': - $pattern = '.*'.$val.'.*'; - break; - case '$': - $pattern = '.*'.$val.'$'; - break; - } - // cut last character - $attr = substr($attr, 0, -1); - $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport - ? mb_ereg_match($pattern, $node->getAttribute($attr)) - : preg_match("@{$pattern}@", $node->getAttribute($attr)); - if (! $isMatch) - $break = true; - } else if ($node->getAttribute($attr) != $val) - $break = true; - } else if (! $node->hasAttribute($attr)) - $break = true; - // PSEUDO CLASSES - } else if ( $s[0] == ':') { - // skip - // TAG - } else if (trim($s)) { - if ($s != '*') { - // TODO namespaces - if (isset($node->tagName)) { - if ($node->tagName != $s) - $break = true; - } else if ($s == 'html' && ! $this->isRoot($node)) - $break = true; - } - // AVOID NON-SIMPLE SELECTORS - } else if (in_array($s, $notSimpleSelector)) { - $break = true; - $this->debug(array('Skipping non simple selector', $selector)); - } - } - if ($break) - break; - } - // if element passed all chunks of selector - add it to new stack - if (! $break ) - $stack[] = $node; - } - $tmpStack = $this->elements; - $this->elements = $stack; - // PER ALL NODES selector chunks - foreach($selector as $s) - // PSEUDO CLASSES - if ($s[0] == ':') - $this->pseudoClasses($s); - foreach($this->elements as $node) - // XXX it should be merged without duplicates - // but jQuery doesnt do that - $finalStack[] = $node; - $this->elements = $tmpStack; - } - $this->elements = $finalStack; - if ($_skipHistory) { - return $this; - } else { - $this->debug("Stack length after filter(): ".count($finalStack)); - return $this->newInstance(); - } - } - /** - * - * @param $value - * @return unknown_type - * @TODO implement in all methods using passed parameters - */ - protected static function unQuote($value) { - return $value[0] == '\'' || $value[0] == '"' - ? substr($value, 1, -1) - : $value; - } - /** - * Enter description here... - * - * @link http://docs.jquery.com/Ajax/load - * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo Support $selector - */ - public function load($url, $data = null, $callback = null) { - if ($data && ! is_array($data)) { - $callback = $data; - $data = null; - } - if (mb_strpos($url, ' ') !== false) { - $matches = null; - if (extension_loaded('mbstring') && phpQuery::$mbstringSupport) - mb_ereg('^([^ ]+) (.*)$', $url, $matches); - else - preg_match('^([^ ]+) (.*)$', $url, $matches); - $url = $matches[1]; - $selector = $matches[2]; - // FIXME this sucks, pass as callback param - $this->_loadSelector = $selector; - } - $ajax = array( - 'url' => $url, - 'type' => $data ? 'POST' : 'GET', - 'data' => $data, - 'complete' => $callback, - 'success' => array($this, '__loadSuccess') - ); - phpQuery::ajax($ajax); - return $this; - } - /** - * @access private - * @param $html - * @return unknown_type - */ - public function __loadSuccess($html) { - if ($this->_loadSelector) { - $html = phpQuery::newDocument($html)->find($this->_loadSelector); - unset($this->_loadSelector); - } - foreach($this->stack(1) as $node) { - phpQuery::pq($node, $this->getDocumentID()) - ->markup($html); - } - } - /** - * Enter description here... - * - * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo - */ - public function css() { - // TODO - return $this; - } - /** - * @todo - * - */ - public function show(){ - // TODO - return $this; - } - /** - * @todo - * - */ - public function hide(){ - // TODO - return $this; - } - /** - * Trigger a type of event on every matched element. - * - * @param unknown_type $type - * @param unknown_type $data - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO support more than event in $type (space-separated) - */ - public function trigger($type, $data = array()) { - foreach($this->elements as $node) - phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node); - return $this; - } - /** - * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions. - * - * @param unknown_type $type - * @param unknown_type $data - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO - */ - public function triggerHandler($type, $data = array()) { - // TODO; - } - /** - * Binds a handler to one or more events (like click) for each matched element. - * Can also bind custom events. - * - * @param unknown_type $type - * @param unknown_type $data Optional - * @param unknown_type $callback - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO support '!' (exclusive) events - * @TODO support more than event in $type (space-separated) - */ - public function bind($type, $data, $callback = null) { - // TODO check if $data is callable, not using is_callable - if (! isset($callback)) { - $callback = $data; - $data = null; - } - foreach($this->elements as $node) - phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback); - return $this; - } - /** - * Enter description here... - * - * @param unknown_type $type - * @param unknown_type $callback - * @return unknown - * @TODO namespace events - * @TODO support more than event in $type (space-separated) - */ - public function unbind($type = null, $callback = null) { - foreach($this->elements as $node) - phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function change($callback = null) { - if ($callback) - return $this->bind('change', $callback); - return $this->trigger('change'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function submit($callback = null) { - if ($callback) - return $this->bind('submit', $callback); - return $this->trigger('submit'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function click($callback = null) { - if ($callback) - return $this->bind('click', $callback); - return $this->trigger('click'); - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapAllOld($wrapper) { - $wrapper = pq($wrapper)->_clone(); - if (! $wrapper->length() || ! $this->length() ) - return $this; - $wrapper->insertBefore($this->elements[0]); - $deepest = $wrapper->elements[0]; - while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) - $deepest = $deepest->firstChild; - pq($deepest)->append($this); - return $this; - } - /** - * Enter description here... - * - * TODO testme... - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapAll($wrapper) { - if (! $this->length()) - return $this; - return phpQuery::pq($wrapper, $this->getDocumentID()) - ->clone() - ->insertBefore($this->get(0)) - ->map(array($this, '___wrapAllCallback')) - ->append($this); - } - /** - * - * @param $node - * @return unknown_type - * @access private - */ - public function ___wrapAllCallback($node) { - $deepest = $node; - while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) - $deepest = $deepest->firstChild; - return $deepest; - } - /** - * Enter description here... - * NON JQUERY METHOD - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapAllPHP($codeBefore, $codeAfter) { - return $this - ->slice(0, 1) - ->beforePHP($codeBefore) - ->end() - ->slice(-1) - ->afterPHP($codeAfter) - ->end(); - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrap($wrapper) { - foreach($this->stack() as $node) - phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper); - return $this; - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapPHP($codeBefore, $codeAfter) { - foreach($this->stack() as $node) - phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter); - return $this; - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapInner($wrapper) { - foreach($this->stack() as $node) - phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper); - return $this; - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapInnerPHP($codeBefore, $codeAfter) { - foreach($this->stack(1) as $node) - phpQuery::pq($node, $this->getDocumentID())->contents() - ->wrapAllPHP($codeBefore, $codeAfter); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @testme Support for text nodes - */ - public function contents() { - $stack = array(); - foreach($this->stack(1) as $el) { - // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56 -// if (! isset($el->childNodes)) -// continue; - foreach($el->childNodes as $node) { - $stack[] = $node; - } - } - return $this->newInstance($stack); - } - /** - * Enter description here... - * - * jQuery difference. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function contentsUnwrap() { - foreach($this->stack(1) as $node) { - if (! $node->parentNode ) - continue; - $childNodes = array(); - // any modification in DOM tree breaks childNodes iteration, so cache them first - foreach($node->childNodes as $chNode ) - $childNodes[] = $chNode; - foreach($childNodes as $chNode ) -// $node->parentNode->appendChild($chNode); - $node->parentNode->insertBefore($chNode, $node); - $node->parentNode->removeChild($node); - } - return $this; - } - /** - * Enter description here... - * - * jQuery difference. - */ - public function switchWith($markup) { - $markup = pq($markup, $this->getDocumentID()); - $content = null; - foreach($this->stack(1) as $node) { - pq($node) - ->contents()->toReference($content)->end() - ->replaceWith($markup->clone()->append($content)); - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function eq($num) { - $oldStack = $this->elements; - $this->elementsBackup = $this->elements; - $this->elements = array(); - if ( isset($oldStack[$num]) ) - $this->elements[] = $oldStack[$num]; - return $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function size() { - return count($this->elements); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @deprecated Use length as attribute - */ - public function length() { - return $this->size(); - } - public function count() { - return $this->size(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo $level - */ - public function end($level = 1) { -// $this->elements = array_pop( $this->history ); -// return $this; -// $this->previous->DOM = $this->DOM; -// $this->previous->XPath = $this->XPath; - return $this->previous - ? $this->previous - : $this; - } - /** - * Enter description here... - * Normal use ->clone() . - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function _clone() { - $newStack = array(); - //pr(array('copy... ', $this->whois())); - //$this->dumpHistory('copy'); - $this->elementsBackup = $this->elements; - foreach($this->elements as $node) { - $newStack[] = $node->cloneNode(true); - } - $this->elements = $newStack; - return $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function replaceWithPHP($code) { - return $this->replaceWith(phpQuery::php($code)); - } - /** - * Enter description here... - * - * @param String|phpQuery $content - * @link http://docs.jquery.com/Manipulation/replaceWith#content - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function replaceWith($content) { - return $this->after($content)->remove(); - } - /** - * Enter description here... - * - * @param String $selector - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo this works ? - */ - public function replaceAll($selector) { - foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node) - phpQuery::pq($node, $this->getDocumentID()) - ->after($this->_clone()) - ->remove(); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function remove($selector = null) { - $loop = $selector - ? $this->filter($selector)->elements - : $this->elements; - foreach($loop as $node) { - if (! $node->parentNode ) - continue; - if (isset($node->tagName)) - $this->debug("Removing '{$node->tagName}'"); - $node->parentNode->removeChild($node); - // Mutation event - $event = new DOMEvent(array( - 'target' => $node, - 'type' => 'DOMNodeRemoved' - )); - phpQueryEvents::trigger($this->getDocumentID(), - $event->type, array($event), $node - ); - } - return $this; - } - protected function markupEvents($newMarkup, $oldMarkup, $node) { - if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) { - $event = new DOMEvent(array( - 'target' => $node, - 'type' => 'change' - )); - phpQueryEvents::trigger($this->getDocumentID(), - $event->type, array($event), $node - ); - } - } - /** - * jQuey difference - * - * @param $markup - * @return unknown_type - * @TODO trigger change event for textarea - */ - public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) { - $args = func_get_args(); - if ($this->documentWrapper->isXML) - return call_user_func_array(array($this, 'xml'), $args); - else - return call_user_func_array(array($this, 'html'), $args); - } - /** - * jQuey difference - * - * @param $markup - * @return unknown_type - */ - public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) { - $args = func_get_args(); - if ($this->documentWrapper->isXML) - return call_user_func_array(array($this, 'xmlOuter'), $args); - else - return call_user_func_array(array($this, 'htmlOuter'), $args); - } - /** - * Enter description here... - * - * @param unknown_type $html - * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO force html result - */ - public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) { - if (isset($html)) { - // INSERT - $nodes = $this->documentWrapper->import($html); - $this->empty(); - foreach($this->stack(1) as $alreadyAdded => $node) { - // for now, limit events for textarea - if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') - $oldHtml = pq($node, $this->getDocumentID())->markup(); - foreach($nodes as $newNode) { - $node->appendChild($alreadyAdded - ? $newNode->cloneNode(true) - : $newNode - ); - } - // for now, limit events for textarea - if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') - $this->markupEvents($html, $oldHtml, $node); - } - return $this; - } else { - // FETCH - $return = $this->documentWrapper->markup($this->elements, true); - $args = func_get_args(); - foreach(array_slice($args, 1) as $callback) { - $return = phpQuery::callbackRun($callback, array($return)); - } - return $return; - } - } - /** - * @TODO force xml result - */ - public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) { - $args = func_get_args(); - return call_user_func_array(array($this, 'html'), $args); - } - /** - * Enter description here... - * @TODO force html result - * - * @return String - */ - public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { - $markup = $this->documentWrapper->markup($this->elements); - // pass thou callbacks - $args = func_get_args(); - foreach($args as $callback) { - $markup = phpQuery::callbackRun($callback, array($markup)); - } - return $markup; - } - /** - * @TODO force xml result - */ - public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { - $args = func_get_args(); - return call_user_func_array(array($this, 'htmlOuter'), $args); - } - public function __toString() { - return $this->markupOuter(); - } - /** - * Just like html(), but returns markup with VALID (dangerous) PHP tags. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo support returning markup with PHP tags when called without param - */ - public function php($code = null) { - return $this->markupPHP($code); - } - /** - * Enter description here... - * - * @param $code - * @return unknown_type - */ - public function markupPHP($code = null) { - return isset($code) - ? $this->markup(phpQuery::php($code)) - : phpQuery::markupToPHP($this->markup()); - } - /** - * Enter description here... - * - * @param $code - * @return unknown_type - */ - public function markupOuterPHP() { - return phpQuery::markupToPHP($this->markupOuter()); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function children($selector = null) { - $stack = array(); - foreach($this->stack(1) as $node) { -// foreach($node->getElementsByTagName('*') as $newNode) { - foreach($node->childNodes as $newNode) { - if ($newNode->nodeType != 1) - continue; - if ($selector && ! $this->is($selector, $newNode)) - continue; - if ($this->elementsContainsNode($newNode, $stack)) - continue; - $stack[] = $newNode; - } - } - $this->elementsBackup = $this->elements; - $this->elements = $stack; - return $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function ancestors($selector = null) { - return $this->children( $selector ); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function append( $content) { - return $this->insert($content, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function appendPHP( $content) { - return $this->insert("", 'append'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function appendTo( $seletor) { - return $this->insert($seletor, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function prepend( $content) { - return $this->insert($content, __FUNCTION__); - } - /** - * Enter description here... - * - * @todo accept many arguments, which are joined, arrays maybe also - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function prependPHP( $content) { - return $this->insert("", 'prepend'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function prependTo( $seletor) { - return $this->insert($seletor, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function before($content) { - return $this->insert($content, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function beforePHP( $content) { - return $this->insert("", 'before'); - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function insertBefore( $seletor) { - return $this->insert($seletor, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function after( $content) { - return $this->insert($content, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function afterPHP( $content) { - return $this->insert("", 'after'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function insertAfter( $seletor) { - return $this->insert($seletor, __FUNCTION__); - } - /** - * Internal insert method. Don't use it. - * - * @param unknown_type $target - * @param unknown_type $type - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function insert($target, $type) { - $this->debug("Inserting data with '{$type}'"); - $to = false; - switch( $type) { - case 'appendTo': - case 'prependTo': - case 'insertBefore': - case 'insertAfter': - $to = true; - } - switch(gettype($target)) { - case 'string': - $insertFrom = $insertTo = array(); - if ($to) { - // INSERT TO - $insertFrom = $this->elements; - if (phpQuery::isMarkup($target)) { - // $target is new markup, import it - $insertTo = $this->documentWrapper->import($target); - // insert into selected element - } else { - // $tagret is a selector - $thisStack = $this->elements; - $this->toRoot(); - $insertTo = $this->find($target)->elements; - $this->elements = $thisStack; - } - } else { - // INSERT FROM - $insertTo = $this->elements; - $insertFrom = $this->documentWrapper->import($target); - } - break; - case 'object': - $insertFrom = $insertTo = array(); - // phpQuery - if ($target instanceof self) { - if ($to) { - $insertTo = $target->elements; - if ($this->documentFragment && $this->stackIsRoot()) - // get all body children -// $loop = $this->find('body > *')->elements; - // TODO test it, test it hard... -// $loop = $this->newInstance($this->root)->find('> *')->elements; - $loop = $this->root->childNodes; - else - $loop = $this->elements; - // import nodes if needed - $insertFrom = $this->getDocumentID() == $target->getDocumentID() - ? $loop - : $target->documentWrapper->import($loop); - } else { - $insertTo = $this->elements; - if ( $target->documentFragment && $target->stackIsRoot() ) - // get all body children -// $loop = $target->find('body > *')->elements; - $loop = $target->root->childNodes; - else - $loop = $target->elements; - // import nodes if needed - $insertFrom = $this->getDocumentID() == $target->getDocumentID() - ? $loop - : $this->documentWrapper->import($loop); - } - // DOMNODE - } elseif ($target instanceof DOMNODE) { - // import node if needed -// if ( $target->ownerDocument != $this->DOM ) -// $target = $this->DOM->importNode($target, true); - if ( $to) { - $insertTo = array($target); - if ($this->documentFragment && $this->stackIsRoot()) - // get all body children - $loop = $this->root->childNodes; -// $loop = $this->find('body > *')->elements; - else - $loop = $this->elements; - foreach($loop as $fromNode) - // import nodes if needed - $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument) - ? $target->ownerDocument->importNode($fromNode, true) - : $fromNode; - } else { - // import node if needed - if (! $target->ownerDocument->isSameNode($this->document)) - $target = $this->document->importNode($target, true); - $insertTo = $this->elements; - $insertFrom[] = $target; - } - } - break; - } - phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes"); - foreach($insertTo as $insertNumber => $toNode) { - // we need static relative elements in some cases - switch( $type) { - case 'prependTo': - case 'prepend': - $firstChild = $toNode->firstChild; - break; - case 'insertAfter': - case 'after': - $nextSibling = $toNode->nextSibling; - break; - } - foreach($insertFrom as $fromNode) { - // clone if inserted already before - $insert = $insertNumber - ? $fromNode->cloneNode(true) - : $fromNode; - switch($type) { - case 'appendTo': - case 'append': -// $toNode->insertBefore( -// $fromNode, -// $toNode->lastChild->nextSibling -// ); - $toNode->appendChild($insert); - $eventTarget = $insert; - break; - case 'prependTo': - case 'prepend': - $toNode->insertBefore( - $insert, - $firstChild - ); - break; - case 'insertBefore': - case 'before': - if (! $toNode->parentNode) - throw new Exception("No parentNode, can't do {$type}()"); - else - $toNode->parentNode->insertBefore( - $insert, - $toNode - ); - break; - case 'insertAfter': - case 'after': - if (! $toNode->parentNode) - throw new Exception("No parentNode, can't do {$type}()"); - else - $toNode->parentNode->insertBefore( - $insert, - $nextSibling - ); - break; - } - // Mutation event - $event = new DOMEvent(array( - 'target' => $insert, - 'type' => 'DOMNodeInserted' - )); - phpQueryEvents::trigger($this->getDocumentID(), - $event->type, array($event), $insert - ); - } - } - return $this; - } - /** - * Enter description here... - * - * @return Int - */ - public function index($subject) { - $index = -1; - $subject = $subject instanceof phpQueryObject - ? $subject->elements[0] - : $subject; - foreach($this->newInstance() as $k => $node) { - if ($node->isSameNode($subject)) - $index = $k; - } - return $index; - } - /** - * Enter description here... - * - * @param unknown_type $start - * @param unknown_type $end - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @testme - */ - public function slice($start, $end = null) { -// $last = count($this->elements)-1; -// $end = $end -// ? min($end, $last) -// : $last; -// if ($start < 0) -// $start = $last+$start; -// if ($start > $last) -// return array(); - if ($end > 0) - $end = $end-$start; - return $this->newInstance( - array_slice($this->elements, $start, $end) - ); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function reverse() { - $this->elementsBackup = $this->elements; - $this->elements = array_reverse($this->elements); - return $this->newInstance(); - } - /** - * Return joined text content. - * @return String - */ - public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) { - if (isset($text)) - return $this->html(htmlspecialchars($text)); - $args = func_get_args(); - $args = array_slice($args, 1); - $return = ''; - foreach($this->elements as $node) { - $text = $node->textContent; - if (count($this->elements) > 1 && $text) - $text .= "\n"; - foreach($args as $callback) { - $text = phpQuery::callbackRun($callback, array($text)); - } - $return .= $text; - } - return $return; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function plugin($class, $file = null) { - phpQuery::plugin($class, $file); - return $this; - } - /** - * Deprecated, use $pq->plugin() instead. - * - * @deprecated - * @param $class - * @param $file - * @return unknown_type - */ - public static function extend($class, $file = null) { - return $this->plugin($class, $file); - } - /** - * - * @access private - * @param $method - * @param $args - * @return unknown_type - */ - public function __call($method, $args) { - $aliasMethods = array('clone', 'empty'); - if (isset(phpQuery::$extendMethods[$method])) { - array_unshift($args, $this); - return phpQuery::callbackRun( - phpQuery::$extendMethods[$method], $args - ); - } else if (isset(phpQuery::$pluginsMethods[$method])) { - array_unshift($args, $this); - $class = phpQuery::$pluginsMethods[$method]; - $realClass = "phpQueryObjectPlugin_$class"; - $return = call_user_func_array( - array($realClass, $method), - $args - ); - // XXX deprecate ? - return is_null($return) - ? $this - : $return; - } else if (in_array($method, $aliasMethods)) { - return call_user_func_array(array($this, '_'.$method), $args); - } else - throw new Exception("Method '{$method}' doesnt exist"); - } - /** - * Safe rename of next(). - * - * Use it ONLY when need to call next() on an iterated object (in same time). - * Normaly there is no need to do such thing ;) - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function _next($selector = null) { - return $this->newInstance( - $this->getElementSiblings('nextSibling', $selector, true) - ); - } - /** - * Use prev() and next(). - * - * @deprecated - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function _prev($selector = null) { - return $this->prev($selector); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function prev($selector = null) { - return $this->newInstance( - $this->getElementSiblings('previousSibling', $selector, true) - ); - } - /** - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo - */ - public function prevAll($selector = null) { - return $this->newInstance( - $this->getElementSiblings('previousSibling', $selector) - ); - } - /** - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo FIXME: returns source elements insted of next siblings - */ - public function nextAll($selector = null) { - return $this->newInstance( - $this->getElementSiblings('nextSibling', $selector) - ); - } - /** - * @access private - */ - protected function getElementSiblings($direction, $selector = null, $limitToOne = false) { - $stack = array(); - $count = 0; - foreach($this->stack() as $node) { - $test = $node; - while( isset($test->{$direction}) && $test->{$direction}) { - $test = $test->{$direction}; - if (! $test instanceof DOMELEMENT) - continue; - $stack[] = $test; - if ($limitToOne) - break; - } - } - if ($selector) { - $stackOld = $this->elements; - $this->elements = $stack; - $stack = $this->filter($selector, true)->stack(); - $this->elements = $stackOld; - } - return $stack; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function siblings($selector = null) { - $stack = array(); - $siblings = array_merge( - $this->getElementSiblings('previousSibling', $selector), - $this->getElementSiblings('nextSibling', $selector) - ); - foreach($siblings as $node) { - if (! $this->elementsContainsNode($node, $stack)) - $stack[] = $node; - } - return $this->newInstance($stack); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function not($selector = null) { - if (is_string($selector)) - phpQuery::debug(array('not', $selector)); - else - phpQuery::debug('not'); - $stack = array(); - if ($selector instanceof self || $selector instanceof DOMNODE) { - foreach($this->stack() as $node) { - if ($selector instanceof self) { - $matchFound = false; - foreach($selector->stack() as $notNode) { - if ($notNode->isSameNode($node)) - $matchFound = true; - } - if (! $matchFound) - $stack[] = $node; - } else if ($selector instanceof DOMNODE) { - if (! $selector->isSameNode($node)) - $stack[] = $node; - } else { - if (! $this->is($selector)) - $stack[] = $node; - } - } - } else { - $orgStack = $this->stack(); - $matched = $this->filter($selector, true)->stack(); -// $matched = array(); -// // simulate OR in filter() instead of AND 5y -// foreach($this->parseSelector($selector) as $s) { -// $matched = array_merge($matched, -// $this->filter(array($s))->stack() -// ); -// } - foreach($orgStack as $node) - if (! $this->elementsContainsNode($node, $matched)) - $stack[] = $node; - } - return $this->newInstance($stack); - } - /** - * Enter description here... - * - * @param string|phpQueryObject - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function add($selector = null) { - if (! $selector) - return $this; - $stack = array(); - $this->elementsBackup = $this->elements; - $found = phpQuery::pq($selector, $this->getDocumentID()); - $this->merge($found->elements); - return $this->newInstance(); - } - /** - * @access private - */ - protected function merge() { - foreach(func_get_args() as $nodes) - foreach($nodes as $newNode ) - if (! $this->elementsContainsNode($newNode) ) - $this->elements[] = $newNode; - } - /** - * @access private - * TODO refactor to stackContainsNode - */ - protected function elementsContainsNode($nodeToCheck, $elementsStack = null) { - $loop = ! is_null($elementsStack) - ? $elementsStack - : $this->elements; - foreach($loop as $node) { - if ( $node->isSameNode( $nodeToCheck ) ) - return true; - } - return false; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function parent($selector = null) { - $stack = array(); - foreach($this->elements as $node ) - if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) ) - $stack[] = $node->parentNode; - $this->elementsBackup = $this->elements; - $this->elements = $stack; - if ( $selector ) - $this->filter($selector, true); - return $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function parents($selector = null) { - $stack = array(); - if (! $this->elements ) - $this->debug('parents() - stack empty'); - foreach($this->elements as $node) { - $test = $node; - while( $test->parentNode) { - $test = $test->parentNode; - if ($this->isRoot($test)) - break; - if (! $this->elementsContainsNode($test, $stack)) { - $stack[] = $test; - continue; - } - } - } - $this->elementsBackup = $this->elements; - $this->elements = $stack; - if ( $selector ) - $this->filter($selector, true); - return $this->newInstance(); - } - /** - * Internal stack iterator. - * - * @access private - */ - public function stack($nodeTypes = null) { - if (!isset($nodeTypes)) - return $this->elements; - if (!is_array($nodeTypes)) - $nodeTypes = array($nodeTypes); - $return = array(); - foreach($this->elements as $node) { - if (in_array($node->nodeType, $nodeTypes)) - $return[] = $node; - } - return $return; - } - // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes - protected function attrEvents($attr, $oldAttr, $oldValue, $node) { - // skip events for XML documents - if (! $this->isXHTML() && ! $this->isHTML()) - return; - $event = null; - // identify - $isInputValue = $node->tagName == 'input' - && ( - in_array($node->getAttribute('type'), - array('text', 'password', 'hidden')) - || !$node->getAttribute('type') - ); - $isRadio = $node->tagName == 'input' - && $node->getAttribute('type') == 'radio'; - $isCheckbox = $node->tagName == 'input' - && $node->getAttribute('type') == 'checkbox'; - $isOption = $node->tagName == 'option'; - if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) { - $event = new DOMEvent(array( - 'target' => $node, - 'type' => 'change' - )); - } else if (($isRadio || $isCheckbox) && $attr == 'checked' && ( - // check - (! $oldAttr && $node->hasAttribute($attr)) - // un-check - || (! $node->hasAttribute($attr) && $oldAttr) - )) { - $event = new DOMEvent(array( - 'target' => $node, - 'type' => 'change' - )); - } else if ($isOption && $node->parentNode && $attr == 'selected' && ( - // select - (! $oldAttr && $node->hasAttribute($attr)) - // un-select - || (! $node->hasAttribute($attr) && $oldAttr) - )) { - $event = new DOMEvent(array( - 'target' => $node->parentNode, - 'type' => 'change' - )); - } - if ($event) { - phpQueryEvents::trigger($this->getDocumentID(), - $event->type, array($event), $node - ); - } - } - public function attr($attr = null, $value = null) { - foreach($this->stack(1) as $node) { - if (! is_null($value)) { - $loop = $attr == '*' - ? $this->getNodeAttrs($node) - : array($attr); - foreach($loop as $a) { - $oldValue = $node->getAttribute($a); - $oldAttr = $node->hasAttribute($a); - // TODO raises an error when charset other than UTF-8 - // while document's charset is also not UTF-8 - @$node->setAttribute($a, $value); - $this->attrEvents($a, $oldAttr, $oldValue, $node); - } - } else if ($attr == '*') { - // jQuery difference - $return = array(); - foreach($node->attributes as $n => $v) - $return[$n] = $v->value; - return $return; - } else - return $node->hasAttribute($attr) - ? $node->getAttribute($attr) - : null; - } - return is_null($value) - ? '' : $this; - } - /** - * @access private - */ - protected function getNodeAttrs($node) { - $return = array(); - foreach($node->attributes as $n => $o) - $return[] = $n; - return $return; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo check CDATA ??? - */ - public function attrPHP($attr, $code) { - if (! is_null($code)) { - $value = '<'.'?php '.$code.' ?'.'>'; - // TODO tempolary solution - // http://code.google.com/p/phpquery/issues/detail?id=17 -// if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII') -// $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES'); - } - foreach($this->stack(1) as $node) { - if (! is_null($code)) { -// $attrNode = $this->DOM->createAttribute($attr); - $node->setAttribute($attr, $value); -// $attrNode->value = $value; -// $node->appendChild($attrNode); - } else if ( $attr == '*') { - // jQuery diff - $return = array(); - foreach($node->attributes as $n => $v) - $return[$n] = $v->value; - return $return; - } else - return $node->getAttribute($attr); - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function removeAttr($attr) { - foreach($this->stack(1) as $node) { - $loop = $attr == '*' - ? $this->getNodeAttrs($node) - : array($attr); - foreach($loop as $a) { - $oldValue = $node->getAttribute($a); - $node->removeAttribute($a); - $this->attrEvents($a, $oldValue, null, $node); - } - } - return $this; - } - /** - * Return form element value. - * - * @return String Fields value. - */ - public function val($val = null) { - if (! isset($val)) { - if ($this->eq(0)->is('select')) { - $selected = $this->eq(0)->find('option[selected=selected]'); - if ($selected->is('[value]')) - return $selected->attr('value'); - else - return $selected->text(); - } else if ($this->eq(0)->is('textarea')) - return $this->eq(0)->markup(); - else - return $this->eq(0)->attr('value'); - } else { - $_val = null; - foreach($this->stack(1) as $node) { - $node = pq($node, $this->getDocumentID()); - if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) { - $isChecked = in_array($node->attr('value'), $val) - || in_array($node->attr('name'), $val); - if ($isChecked) - $node->attr('checked', 'checked'); - else - $node->removeAttr('checked'); - } else if ($node->get(0)->tagName == 'select') { - if (! isset($_val)) { - $_val = array(); - if (! is_array($val)) - $_val = array((string)$val); - else - foreach($val as $v) - $_val[] = $v; - } - foreach($node['option']->stack(1) as $option) { - $option = pq($option, $this->getDocumentID()); - $selected = false; - // XXX: workaround for string comparsion, see issue #96 - // http://code.google.com/p/phpquery/issues/detail?id=96 - $selected = is_null($option->attr('value')) - ? in_array($option->markup(), $_val) - : in_array($option->attr('value'), $_val); -// $optionValue = $option->attr('value'); -// $optionText = $option->text(); -// $optionTextLenght = mb_strlen($optionText); -// foreach($_val as $v) -// if ($optionValue == $v) -// $selected = true; -// else if ($optionText == $v && $optionTextLenght == mb_strlen($v)) -// $selected = true; - if ($selected) - $option->attr('selected', 'selected'); - else - $option->removeAttr('selected'); - } - } else if ($node->get(0)->tagName == 'textarea') - $node->markup($val); - else - $node->attr('value', $val); - } - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function andSelf() { - if ( $this->previous ) - $this->elements = array_merge($this->elements, $this->previous->elements); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function addClass( $className) { - if (! $className) - return $this; - foreach($this->stack(1) as $node) { - if (! $this->is(".$className", $node)) - $node->setAttribute( - 'class', - trim($node->getAttribute('class').' '.$className) - ); - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function addClassPHP( $className) { - foreach($this->stack(1) as $node) { - $classes = $node->getAttribute('class'); - $newValue = $classes - ? $classes.' <'.'?php '.$className.' ?'.'>' - : '<'.'?php '.$className.' ?'.'>'; - $node->setAttribute('class', $newValue); - } - return $this; - } - /** - * Enter description here... - * - * @param string $className - * @return bool - */ - public function hasClass($className) { - foreach($this->stack(1) as $node) { - if ( $this->is(".$className", $node)) - return true; - } - return false; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function removeClass($className) { - foreach($this->stack(1) as $node) { - $classes = explode( ' ', $node->getAttribute('class')); - if ( in_array($className, $classes)) { - $classes = array_diff($classes, array($className)); - if ( $classes ) - $node->setAttribute('class', implode(' ', $classes)); - else - $node->removeAttribute('class'); - } - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function toggleClass($className) { - foreach($this->stack(1) as $node) { - if ( $this->is( $node, '.'.$className )) - $this->removeClass($className); - else - $this->addClass($className); - } - return $this; - } - /** - * Proper name without underscore (just ->empty()) also works. - * - * Removes all child nodes from the set of matched elements. - * - * Example: - * pq("p")._empty() - * - * HTML: - *

    Hello, Person and person

    - * - * Result: - * [

    ] - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function _empty() { - foreach($this->stack(1) as $node) { - // thx to 'dave at dgx dot cz' - $node->nodeValue = ''; - } - return $this; - } - /** - * Enter description here... - * - * @param array|string $callback Expects $node as first param, $index as second - * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope) - * @param array $arg1 Will ba passed as third and futher args to callback. - * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on... - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function each($callback, $param1 = null, $param2 = null, $param3 = null) { - $paramStructure = null; - if (func_num_args() > 1) { - $paramStructure = func_get_args(); - $paramStructure = array_slice($paramStructure, 1); - } - foreach($this->elements as $v) - phpQuery::callbackRun($callback, array($v), $paramStructure); - return $this; - } - /** - * Run callback on actual object. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function callback($callback, $param1 = null, $param2 = null, $param3 = null) { - $params = func_get_args(); - $params[0] = $this; - phpQuery::callbackRun($callback, $params); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo add $scope and $args as in each() ??? - */ - public function map($callback, $param1 = null, $param2 = null, $param3 = null) { -// $stack = array(); -//// foreach($this->newInstance() as $node) { -// foreach($this->newInstance() as $node) { -// $result = call_user_func($callback, $node); -// if ($result) -// $stack[] = $result; -// } - $params = func_get_args(); - array_unshift($params, $this->elements); - return $this->newInstance( - call_user_func_array(array('phpQuery', 'map'), $params) -// phpQuery::map($this->elements, $callback) - ); - } - /** - * Enter description here... - * - * @param $key - * @param $value - */ - public function data($key, $value = null) { - if (! isset($value)) { - // TODO? implement specific jQuery behavior od returning parent values - // is child which we look up doesn't exist - return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID()); - } else { - foreach($this as $node) - phpQuery::data($node, $key, $value, $this->getDocumentID()); - return $this; - } - } - /** - * Enter description here... - * - * @param $key - */ - public function removeData($key) { - foreach($this as $node) - phpQuery::removeData($node, $key, $this->getDocumentID()); - return $this; - } - // INTERFACE IMPLEMENTATIONS - - // ITERATOR INTERFACE - /** - * @access private - */ - public function rewind(){ - $this->debug('iterating foreach'); -// phpQuery::selectDocument($this->getDocumentID()); - $this->elementsBackup = $this->elements; - $this->elementsInterator = $this->elements; - $this->valid = isset( $this->elements[0] ) - ? 1 : 0; -// $this->elements = $this->valid -// ? array($this->elements[0]) -// : array(); - $this->current = 0; - } - /** - * @access private - */ - public function current(){ - return $this->elementsInterator[ $this->current ]; - } - /** - * @access private - */ - public function key(){ - return $this->current; - } - /** - * Double-function method. - * - * First: main iterator interface method. - * Second: Returning next sibling, alias for _next(). - * - * Proper functionality is choosed automagicaly. - * - * @see phpQueryObject::_next() - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function next($cssSelector = null){ -// if ($cssSelector || $this->valid) -// return $this->_next($cssSelector); - $this->valid = isset( $this->elementsInterator[ $this->current+1 ] ) - ? true - : false; - if (! $this->valid && $this->elementsInterator) { - $this->elementsInterator = null; - } else if ($this->valid) { - $this->current++; - } else { - return $this->_next($cssSelector); - } - } - /** - * @access private - */ - public function valid(){ - return $this->valid; - } - // ITERATOR INTERFACE END - // ARRAYACCESS INTERFACE - /** - * @access private - */ - public function offsetExists($offset) { - return $this->find($offset)->size() > 0; - } - /** - * @access private - */ - public function offsetGet($offset) { - return $this->find($offset); - } - /** - * @access private - */ - public function offsetSet($offset, $value) { -// $this->find($offset)->replaceWith($value); - $this->find($offset)->html($value); - } - /** - * @access private - */ - public function offsetUnset($offset) { - // empty - throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML."); - } - // ARRAYACCESS INTERFACE END - /** - * Returns node's XPath. - * - * @param unknown_type $oneNode - * @return string - * @TODO use native getNodePath is avaible - * @access private - */ - protected function getNodeXpath($oneNode = null, $namespace = null) { - $return = array(); - $loop = $oneNode - ? array($oneNode) - : $this->elements; -// if ($namespace) -// $namespace .= ':'; - foreach($loop as $node) { - if ($node instanceof DOMDOCUMENT) { - $return[] = ''; - continue; - } - $xpath = array(); - while(! ($node instanceof DOMDOCUMENT)) { - $i = 1; - $sibling = $node; - while($sibling->previousSibling) { - $sibling = $sibling->previousSibling; - $isElement = $sibling instanceof DOMELEMENT; - if ($isElement && $sibling->tagName == $node->tagName) - $i++; - } - $xpath[] = $this->isXML() - ? "*[local-name()='{$node->tagName}'][{$i}]" - : "{$node->tagName}[{$i}]"; - $node = $node->parentNode; - } - $xpath = join('/', array_reverse($xpath)); - $return[] = '/'.$xpath; - } - return $oneNode - ? $return[0] - : $return; - } - // HELPERS - public function whois($oneNode = null) { - $return = array(); - $loop = $oneNode - ? array( $oneNode ) - : $this->elements; - foreach($loop as $node) { - if (isset($node->tagName)) { - $tag = in_array($node->tagName, array('php', 'js')) - ? strtoupper($node->tagName) - : $node->tagName; - $return[] = $tag - .($node->getAttribute('id') - ? '#'.$node->getAttribute('id'):'') - .($node->getAttribute('class') - ? '.'.join('.', explode(' ', $node->getAttribute('class'))):'') - .($node->getAttribute('name') - ? '[name="'.$node->getAttribute('name').'"]':'') - .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false - ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'') - .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false - ? '[value=PHP]':'') - .($node->getAttribute('selected') - ? '[selected]':'') - .($node->getAttribute('checked') - ? '[checked]':'') - ; - } else if ($node instanceof DOMTEXT) { - if (trim($node->textContent)) - $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15); - } else { - - } - } - return $oneNode && isset($return[0]) - ? $return[0] - : $return; - } - /** - * Dump htmlOuter and preserve chain. Usefull for debugging. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * - */ - public function dump() { - print 'DUMP #'.(phpQuery::$dumpCount++).' '; - $debug = phpQuery::$debug; - phpQuery::$debug = false; -// print __FILE__.':'.__LINE__."\n"; - var_dump($this->htmlOuter()); - return $this; - } - public function dumpWhois() { - print 'DUMP #'.(phpQuery::$dumpCount++).' '; - $debug = phpQuery::$debug; - phpQuery::$debug = false; -// print __FILE__.':'.__LINE__."\n"; - var_dump('whois', $this->whois()); - phpQuery::$debug = $debug; - return $this; - } - public function dumpLength() { - print 'DUMP #'.(phpQuery::$dumpCount++).' '; - $debug = phpQuery::$debug; - phpQuery::$debug = false; -// print __FILE__.':'.__LINE__."\n"; - var_dump('length', $this->length()); - phpQuery::$debug = $debug; - return $this; - } - public function dumpTree($html = true, $title = true) { - $output = $title - ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : ''; - $debug = phpQuery::$debug; - phpQuery::$debug = false; - foreach($this->stack() as $node) - $output .= $this->__dumpTree($node); - phpQuery::$debug = $debug; - print $html - ? nl2br(str_replace(' ', ' ', $output)) - : $output; - return $this; - } - private function __dumpTree($node, $intend = 0) { - $whois = $this->whois($node); - $return = ''; - if ($whois) - $return .= str_repeat(' - ', $intend).$whois."\n"; - if (isset($node->childNodes)) - foreach($node->childNodes as $chNode) - $return .= $this->__dumpTree($chNode, $intend+1); - return $return; - } - /** - * Dump htmlOuter and stop script execution. Usefull for debugging. - * - */ - public function dumpDie() { - print __FILE__.':'.__LINE__; - var_dump($this->htmlOuter()); - die(); - } -} - - -// -- Multibyte Compatibility functions --------------------------------------- -// http://svn.iphonewebdev.com/lace/lib/mb_compat.php - -/** - * mb_internal_encoding() - * - * Included for mbstring pseudo-compatability. - */ -if (!function_exists('mb_internal_encoding')) -{ - function mb_internal_encoding($enc) {return true; } -} - -/** - * mb_regex_encoding() - * - * Included for mbstring pseudo-compatability. - */ -if (!function_exists('mb_regex_encoding')) -{ - function mb_regex_encoding($enc) {return true; } -} - -/** - * mb_strlen() - * - * Included for mbstring pseudo-compatability. - */ -if (!function_exists('mb_strlen')) -{ - function mb_strlen($str) - { - return strlen($str); - } -} - -/** - * mb_strpos() - * - * Included for mbstring pseudo-compatability. - */ -if (!function_exists('mb_strpos')) -{ - function mb_strpos($haystack, $needle, $offset=0) - { - return strpos($haystack, $needle, $offset); - } -} -/** - * mb_stripos() - * - * Included for mbstring pseudo-compatability. - */ -if (!function_exists('mb_stripos')) -{ - function mb_stripos($haystack, $needle, $offset=0) - { - return stripos($haystack, $needle, $offset); - } -} - -/** - * mb_substr() - * - * Included for mbstring pseudo-compatability. - */ -if (!function_exists('mb_substr')) -{ - function mb_substr($str, $start, $length=0) - { - return substr($str, $start, $length); - } -} - -/** - * mb_substr_count() - * - * Included for mbstring pseudo-compatability. - */ -if (!function_exists('mb_substr_count')) -{ - function mb_substr_count($haystack, $needle) - { - return substr_count($haystack, $needle); - } -} - - -/** - * Static namespace for phpQuery functions. - * - * @author Tobiasz Cudnik - * @package phpQuery - */ -abstract class phpQuery { - /** - * XXX: Workaround for mbstring problems - * - * @var bool - */ - public static $mbstringSupport = true; - public static $debug = false; - public static $documents = array(); - public static $defaultDocumentID = null; -// public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'; - /** - * Applies only to HTML. - * - * @var unknown_type - */ - public static $defaultDoctype = ''; - public static $defaultCharset = 'UTF-8'; - /** - * Static namespace for plugins. - * - * @var object - */ - public static $plugins = array(); - /** - * List of loaded plugins. - * - * @var unknown_type - */ - public static $pluginsLoaded = array(); - public static $pluginsMethods = array(); - public static $pluginsStaticMethods = array(); - public static $extendMethods = array(); - /** - * @TODO implement - */ - public static $extendStaticMethods = array(); - /** - * Hosts allowed for AJAX connections. - * Dot '.' means $_SERVER['HTTP_HOST'] (if any). - * - * @var array - */ - public static $ajaxAllowedHosts = array( - '.' - ); - /** - * AJAX settings. - * - * @var array - * XXX should it be static or not ? - */ - public static $ajaxSettings = array( - 'url' => '',//TODO - 'global' => true, - 'type' => "GET", - 'timeout' => null, - 'contentType' => "application/x-www-form-urlencoded", - 'processData' => true, -// 'async' => true, - 'data' => null, - 'username' => null, - 'password' => null, - 'accepts' => array( - 'xml' => "application/xml, text/xml", - 'html' => "text/html", - 'script' => "text/javascript, application/javascript", - 'json' => "application/json, text/javascript", - 'text' => "text/plain", - '_default' => "*/*" - ) - ); - public static $lastModified = null; - public static $active = 0; - public static $dumpCount = 0; - /** - * Multi-purpose function. - * Use pq() as shortcut. - * - * In below examples, $pq is any result of pq(); function. - * - * 1. Import markup into existing document (without any attaching): - * - Import into selected document: - * pq('
    ') // DOESNT accept text nodes at beginning of input string ! - * - Import into document with ID from $pq->getDocumentID(): - * pq('
    ', $pq->getDocumentID()) - * - Import into same document as DOMNode belongs to: - * pq('
    ', DOMNode) - * - Import into document from phpQuery object: - * pq('
    ', $pq) - * - * 2. Run query: - * - Run query on last selected document: - * pq('div.myClass') - * - Run query on document with ID from $pq->getDocumentID(): - * pq('div.myClass', $pq->getDocumentID()) - * - Run query on same document as DOMNode belongs to and use node(s)as root for query: - * pq('div.myClass', DOMNode) - * - Run query on document from phpQuery object - * and use object's stack as root node(s) for query: - * pq('div.myClass', $pq) - * - * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes - * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root) - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false - * phpQuery object or false in case of error. - */ - public static function pq($arg1, $context = null) { - if ($arg1 instanceof DOMNODE && ! isset($context)) { - foreach(phpQuery::$documents as $documentWrapper) { - $compare = $arg1 instanceof DOMDocument - ? $arg1 : $arg1->ownerDocument; - if ($documentWrapper->document->isSameNode($compare)) - $context = $documentWrapper->id; - } - } - if (! $context) { - $domId = self::$defaultDocumentID; - if (! $domId) - throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first."); -// } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) - } else if (is_object($context) && $context instanceof phpQueryObject) - $domId = $context->getDocumentID(); - else if ($context instanceof DOMDOCUMENT) { - $domId = self::getDocumentID($context); - if (! $domId) { - //throw new Exception('Orphaned DOMDocument'); - $domId = self::newDocument($context)->getDocumentID(); - } - } else if ($context instanceof DOMNODE) { - $domId = self::getDocumentID($context); - if (! $domId) { - throw new Exception('Orphaned DOMNode'); -// $domId = self::newDocument($context->ownerDocument); - } - } else - $domId = $context; - if ($arg1 instanceof phpQueryObject) { -// if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) { - /** - * Return $arg1 or import $arg1 stack if document differs: - * pq(pq('
    ')) - */ - if ($arg1->getDocumentID() == $domId) - return $arg1; - $class = get_class($arg1); - // support inheritance by passing old object to overloaded constructor - $phpQuery = $class != 'phpQuery' - ? new $class($arg1, $domId) - : new phpQueryObject($domId); - $phpQuery->elements = array(); - foreach($arg1->elements as $node) - $phpQuery->elements[] = $phpQuery->document->importNode($node, true); - return $phpQuery; - } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) { - /* - * Wrap DOM nodes with phpQuery object, import into document when needed: - * pq(array($domNode1, $domNode2)) - */ - $phpQuery = new phpQueryObject($domId); - if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1)) - $arg1 = array($arg1); - $phpQuery->elements = array(); - foreach($arg1 as $node) { - $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT - && ! $node->ownerDocument->isSameNode($phpQuery->document); - $phpQuery->elements[] = $sameDocument - ? $phpQuery->document->importNode($node, true) - : $node; - } - return $phpQuery; - } else if (self::isMarkup($arg1)) { - /** - * Import HTML: - * pq('
    ') - */ - $phpQuery = new phpQueryObject($domId); - return $phpQuery->newInstance( - $phpQuery->documentWrapper->import($arg1) - ); - } else { - /** - * Run CSS query: - * pq('div.myClass') - */ - $phpQuery = new phpQueryObject($domId); -// if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) - if ($context && $context instanceof phpQueryObject) - $phpQuery->elements = $context->elements; - else if ($context && $context instanceof DOMNODELIST) { - $phpQuery->elements = array(); - foreach($context as $node) - $phpQuery->elements[] = $node; - } else if ($context && $context instanceof DOMNODE) - $phpQuery->elements = array($context); - return $phpQuery->find($arg1); - } - } - /** - * Sets default document to $id. Document has to be loaded prior - * to using this method. - * $id can be retrived via getDocumentID() or getDocumentIDRef(). - * - * @param unknown_type $id - */ - public static function selectDocument($id) { - $id = self::getDocumentID($id); - self::debug("Selecting document '$id' as default one"); - self::$defaultDocumentID = self::getDocumentID($id); - } - /** - * Returns document with id $id or last used as phpQueryObject. - * $id can be retrived via getDocumentID() or getDocumentIDRef(). - * Chainable. - * - * @see phpQuery::selectDocument() - * @param unknown_type $id - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function getDocument($id = null) { - if ($id) - phpQuery::selectDocument($id); - else - $id = phpQuery::$defaultDocumentID; - return new phpQueryObject($id); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocument($markup = null, $contentType = null) { - if (! $markup) - $markup = ''; - $documentID = phpQuery::createDocumentWrapper($markup, $contentType); - return new phpQueryObject($documentID); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentHTML($markup = null, $charset = null) { - $contentType = $charset - ? ";charset=$charset" - : ''; - return self::newDocument($markup, "text/html{$contentType}"); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentXML($markup = null, $charset = null) { - $contentType = $charset - ? ";charset=$charset" - : ''; - return self::newDocument($markup, "text/xml{$contentType}"); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentXHTML($markup = null, $charset = null) { - $contentType = $charset - ? ";charset=$charset" - : ''; - return self::newDocument($markup, "application/xhtml+xml{$contentType}"); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentPHP($markup = null, $contentType = "text/html") { - // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function) - $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset); - return self::newDocument($markup, $contentType); - } - public static function phpToMarkup($php, $charset = 'utf-8') { - $regexes = array( - '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s', - '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s', - ); - foreach($regexes as $regex) - while (preg_match($regex, $php, $matches)) { - $php = preg_replace_callback( - $regex, -// create_function('$m, $charset = "'.$charset.'"', -// 'return $m[1].$m[2] -// .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) -// .$m[5].$m[2];' -// ), - array('phpQuery', '_phpToMarkupCallback'), - $php - ); - } - $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s'; -//preg_match_all($regex, $php, $matches); -//var_dump($matches); - $php = preg_replace($regex, '\\1', $php); - return $php; - } - public static function _phpToMarkupCallback($php, $charset = 'utf-8') { - return $m[1].$m[2] - .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) - .$m[5].$m[2]; - } - public static function _markupToPHPCallback($m) { - return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">"; - } - /** - * Converts document markup containing PHP code generated by phpQuery::php() - * into valid (executable) PHP code syntax. - * - * @param string|phpQueryObject $content - * @return string PHP code. - */ - public static function markupToPHP($content) { - if ($content instanceof phpQueryObject) - $content = $content->markupOuter(); - /* ... to */ - $content = preg_replace_callback( - '@\s*\s*@s', -// create_function('$m', -// 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";' -// ), - array('phpQuery', '_markupToPHPCallback'), - $content - ); - /* extra space added to save highlighters */ - $regexes = array( - '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^\']*)\'@s', - '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^"]*)"@s', - ); - foreach($regexes as $regex) - while (preg_match($regex, $content)) - $content = preg_replace_callback( - $regex, - create_function('$m', - 'return $m[1].$m[2].$m[3]."", " ", "\n", " ", "{", "$", "}", \'"\', "[", "]"), - htmlspecialchars_decode($m[4]) - ) - ." ?>".$m[5].$m[2];' - ), - $content - ); - return $content; - } - /** - * Creates new document from file $file. - * Chainable. - * - * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources. - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentFile($file, $contentType = null) { - $documentID = self::createDocumentWrapper( - file_get_contents($file), $contentType - ); - return new phpQueryObject($documentID); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentFileHTML($file, $charset = null) { - $contentType = $charset - ? ";charset=$charset" - : ''; - return self::newDocumentFile($file, "text/html{$contentType}"); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentFileXML($file, $charset = null) { - $contentType = $charset - ? ";charset=$charset" - : ''; - return self::newDocumentFile($file, "text/xml{$contentType}"); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentFileXHTML($file, $charset = null) { - $contentType = $charset - ? ";charset=$charset" - : ''; - return self::newDocumentFile($file, "application/xhtml+xml{$contentType}"); - } - /** - * Creates new document from markup. - * Chainable. - * - * @param unknown_type $markup - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public static function newDocumentFilePHP($file, $contentType = null) { - return self::newDocumentPHP(file_get_contents($file), $contentType); - } - /** - * Reuses existing DOMDocument object. - * Chainable. - * - * @param $document DOMDocument - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO support DOMDocument - */ - public static function loadDocument($document) { - // TODO - die('TODO loadDocument'); - } - /** - * Enter description here... - * - * @param unknown_type $html - * @param unknown_type $domId - * @return unknown New DOM ID - * @todo support PHP tags in input - * @todo support passing DOMDocument object from self::loadDocument - */ - protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) { - if (function_exists('domxml_open_mem')) - throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled."); -// $id = $documentID -// ? $documentID -// : md5(microtime()); - $document = null; - if ($html instanceof DOMDOCUMENT) { - if (self::getDocumentID($html)) { - // document already exists in phpQuery::$documents, make a copy - $document = clone $html; - } else { - // new document, add it to phpQuery::$documents - $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); - } - } else { - $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); - } -// $wrapper->id = $id; - // bind document - phpQuery::$documents[$wrapper->id] = $wrapper; - // remember last loaded document - phpQuery::selectDocument($wrapper->id); - return $wrapper->id; - } - /** - * Extend class namespace. - * - * @param string|array $target - * @param array $source - * @TODO support string $source - * @return unknown_type - */ - public static function extend($target, $source) { - switch($target) { - case 'phpQueryObject': - $targetRef = &self::$extendMethods; - $targetRef2 = &self::$pluginsMethods; - break; - case 'phpQuery': - $targetRef = &self::$extendStaticMethods; - $targetRef2 = &self::$pluginsStaticMethods; - break; - default: - throw new Exception("Unsupported \$target type"); - } - if (is_string($source)) - $source = array($source => $source); - foreach($source as $method => $callback) { - if (isset($targetRef[$method])) { -// throw new Exception - self::debug("Duplicate method '{$method}', can\'t extend '{$target}'"); - continue; - } - if (isset($targetRef2[$method])) { -// throw new Exception - self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}'," - ." can\'t extend '{$target}'"); - continue; - } - $targetRef[$method] = $callback; - } - return true; - } - /** - * Extend phpQuery with $class from $file. - * - * @param string $class Extending class name. Real class name can be prepended phpQuery_. - * @param string $file Filename to include. Defaults to "{$class}.php". - */ - public static function plugin($class, $file = null) { - // TODO $class checked agains phpQuery_$class -// if (strpos($class, 'phpQuery') === 0) -// $class = substr($class, 8); - if (in_array($class, self::$pluginsLoaded)) - return true; - if (! $file) - $file = $class.'.php'; - $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class); - $staticClassExists = class_exists('phpQueryPlugin_'.$class); - if (! $objectClassExists && ! $staticClassExists) - require_once($file); - self::$pluginsLoaded[] = $class; - // static methods - if (class_exists('phpQueryPlugin_'.$class)) { - $realClass = 'phpQueryPlugin_'.$class; - $vars = get_class_vars($realClass); - $loop = isset($vars['phpQueryMethods']) - && ! is_null($vars['phpQueryMethods']) - ? $vars['phpQueryMethods'] - : get_class_methods($realClass); - foreach($loop as $method) { - if ($method == '__initialize') - continue; - if (! is_callable(array($realClass, $method))) - continue; - if (isset(self::$pluginsStaticMethods[$method])) { - throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'"); - return; - } - self::$pluginsStaticMethods[$method] = $class; - } - if (method_exists($realClass, '__initialize')) - call_user_func_array(array($realClass, '__initialize'), array()); - } - // object methods - if (class_exists('phpQueryObjectPlugin_'.$class)) { - $realClass = 'phpQueryObjectPlugin_'.$class; - $vars = get_class_vars($realClass); - $loop = isset($vars['phpQueryMethods']) - && ! is_null($vars['phpQueryMethods']) - ? $vars['phpQueryMethods'] - : get_class_methods($realClass); - foreach($loop as $method) { - if (! is_callable(array($realClass, $method))) - continue; - if (isset(self::$pluginsMethods[$method])) { - throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'"); - continue; - } - self::$pluginsMethods[$method] = $class; - } - } - return true; - } - /** - * Unloades all or specified document from memory. - * - * @param mixed $documentID @see phpQuery::getDocumentID() for supported types. - */ - public static function unloadDocuments($id = null) { - if (isset($id)) { - if ($id = self::getDocumentID($id)) - unset(phpQuery::$documents[$id]); - } else { - foreach(phpQuery::$documents as $k => $v) { - unset(phpQuery::$documents[$k]); - } - } - } - /** - * Parses phpQuery object or HTML result against PHP tags and makes them active. - * - * @param phpQuery|string $content - * @deprecated - * @return string - */ - public static function unsafePHPTags($content) { - return self::markupToPHP($content); - } - public static function DOMNodeListToArray($DOMNodeList) { - $array = array(); - if (! $DOMNodeList) - return $array; - foreach($DOMNodeList as $node) - $array[] = $node; - return $array; - } - /** - * Checks if $input is HTML string, which has to start with '<'. - * - * @deprecated - * @param String $input - * @return Bool - * @todo still used ? - */ - public static function isMarkup($input) { - return ! is_array($input) && substr(trim($input), 0, 1) == '<'; - } - public static function debug($text) { - if (self::$debug) - print var_dump($text); - } - /** - * Make an AJAX request. - * - * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions - * Additional options are: - * 'document' - document for global events, @see phpQuery::getDocumentID() - * 'referer' - implemented - * 'requested_with' - TODO; not implemented (X-Requested-With) - * @return Zend_Http_Client - * @link http://docs.jquery.com/Ajax/jQuery.ajax - * - * @TODO $options['cache'] - * @TODO $options['processData'] - * @TODO $options['xhr'] - * @TODO $options['data'] as string - * @TODO XHR interface - */ - public static function ajax($options = array(), $xhr = null) { - $options = array_merge( - self::$ajaxSettings, $options - ); - $documentID = isset($options['document']) - ? self::getDocumentID($options['document']) - : null; - if ($xhr) { - // reuse existing XHR object, but clean it up - $client = $xhr; -// $client->setParameterPost(null); -// $client->setParameterGet(null); - $client->setAuth(false); - $client->setHeaders("If-Modified-Since", null); - $client->setHeaders("Referer", null); - $client->resetParameters(); - } else { - // create new XHR object - require_once('Zend/Http/Client.php'); - $client = new Zend_Http_Client(); - $client->setCookieJar(); - } - if (isset($options['timeout'])) - $client->setConfig(array( - 'timeout' => $options['timeout'], - )); -// 'maxredirects' => 0, - foreach(self::$ajaxAllowedHosts as $k => $host) - if ($host == '.' && isset($_SERVER['HTTP_HOST'])) - self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST']; - $host = parse_url($options['url'], PHP_URL_HOST); - if (! in_array($host, self::$ajaxAllowedHosts)) { - throw new Exception("Request not permitted, host '$host' not present in " - ."phpQuery::\$ajaxAllowedHosts"); - } - // JSONP - $jsre = "/=\\?(&|$)/"; - if (isset($options['dataType']) && $options['dataType'] == 'jsonp') { - $jsonpCallbackParam = $options['jsonp'] - ? $options['jsonp'] : 'callback'; - if (strtolower($options['type']) == 'get') { - if (! preg_match($jsre, $options['url'])) { - $sep = strpos($options['url'], '?') - ? '&' : '?'; - $options['url'] .= "$sep$jsonpCallbackParam=?"; - } - } else if ($options['data']) { - $jsonp = false; - foreach($options['data'] as $n => $v) { - if ($v == '?') - $jsonp = true; - } - if (! $jsonp) { - $options['data'][$jsonpCallbackParam] = '?'; - } - } - $options['dataType'] = 'json'; - } - if (isset($options['dataType']) && $options['dataType'] == 'json') { - $jsonpCallback = 'json_'.md5(microtime()); - $jsonpData = $jsonpUrl = false; - if ($options['data']) { - foreach($options['data'] as $n => $v) { - if ($v == '?') - $jsonpData = $n; - } - } - if (preg_match($jsre, $options['url'])) - $jsonpUrl = true; - if ($jsonpData !== false || $jsonpUrl) { - // remember callback name for httpData() - $options['_jsonp'] = $jsonpCallback; - if ($jsonpData !== false) - $options['data'][$jsonpData] = $jsonpCallback; - if ($jsonpUrl) - $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']); - } - } - $client->setUri($options['url']); - $client->setMethod(strtoupper($options['type'])); - if (isset($options['referer']) && $options['referer']) - $client->setHeaders('Referer', $options['referer']); - $client->setHeaders(array( -// 'content-type' => $options['contentType'], - 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko' - .'/2008122010 Firefox/3.0.5', - // TODO custom charset - 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', -// 'Connection' => 'keep-alive', -// 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language' => 'en-us,en;q=0.5', - )); - if ($options['username']) - $client->setAuth($options['username'], $options['password']); - if (isset($options['ifModified']) && $options['ifModified']) - $client->setHeaders("If-Modified-Since", - self::$lastModified - ? self::$lastModified - : "Thu, 01 Jan 1970 00:00:00 GMT" - ); - $client->setHeaders("Accept", - isset($options['dataType']) - && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ]) - ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*" - : self::$ajaxSettings['accepts']['_default'] - ); - // TODO $options['processData'] - if ($options['data'] instanceof phpQueryObject) { - $serialized = $options['data']->serializeArray($options['data']); - $options['data'] = array(); - foreach($serialized as $r) - $options['data'][ $r['name'] ] = $r['value']; - } - if (strtolower($options['type']) == 'get') { - $client->setParameterGet($options['data']); - } else if (strtolower($options['type']) == 'post') { - $client->setEncType($options['contentType']); - $client->setParameterPost($options['data']); - } - if (self::$active == 0 && $options['global']) - phpQueryEvents::trigger($documentID, 'ajaxStart'); - self::$active++; - // beforeSend callback - if (isset($options['beforeSend']) && $options['beforeSend']) - phpQuery::callbackRun($options['beforeSend'], array($client)); - // ajaxSend event - if ($options['global']) - phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options)); - if (phpQuery::$debug) { - self::debug("{$options['type']}: {$options['url']}\n"); - self::debug("Options:
    ".var_export($options, true)."
    \n"); -// if ($client->getCookieJar()) -// self::debug("Cookies:
    ".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."
    \n"); - } - // request - $response = $client->request(); - if (phpQuery::$debug) { - self::debug('Status: '.$response->getStatus().' / '.$response->getMessage()); - self::debug($client->getLastRequest()); - self::debug($response->getHeaders()); - } - if ($response->isSuccessful()) { - // XXX tempolary - self::$lastModified = $response->getHeader('Last-Modified'); - $data = self::httpData($response->getBody(), $options['dataType'], $options); - if (isset($options['success']) && $options['success']) - phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options)); - if ($options['global']) - phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options)); - } else { - if (isset($options['error']) && $options['error']) - phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage())); - if ($options['global']) - phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options)); - } - if (isset($options['complete']) && $options['complete']) - phpQuery::callbackRun($options['complete'], array($client, $response->getStatus())); - if ($options['global']) - phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options)); - if ($options['global'] && ! --self::$active) - phpQueryEvents::trigger($documentID, 'ajaxStop'); - return $client; -// if (is_null($domId)) -// $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false; -// return new phpQueryAjaxResponse($response, $domId); - } - protected static function httpData($data, $type, $options) { - if (isset($options['dataFilter']) && $options['dataFilter']) - $data = self::callbackRun($options['dataFilter'], array($data, $type)); - if (is_string($data)) { - if ($type == "json") { - if (isset($options['_jsonp']) && $options['_jsonp']) { - $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data); - } - $data = self::parseJSON($data); - } - } - return $data; - } - /** - * Enter description here... - * - * @param array|phpQuery $data - * - */ - public static function param($data) { - return http_build_query($data, null, '&'); - } - public static function get($url, $data = null, $callback = null, $type = null) { - if (!is_array($data)) { - $callback = $data; - $data = null; - } - // TODO some array_values on this shit - return phpQuery::ajax(array( - 'type' => 'GET', - 'url' => $url, - 'data' => $data, - 'success' => $callback, - 'dataType' => $type, - )); - } - public static function post($url, $data = null, $callback = null, $type = null) { - if (!is_array($data)) { - $callback = $data; - $data = null; - } - return phpQuery::ajax(array( - 'type' => 'POST', - 'url' => $url, - 'data' => $data, - 'success' => $callback, - 'dataType' => $type, - )); - } - public static function getJSON($url, $data = null, $callback = null) { - if (!is_array($data)) { - $callback = $data; - $data = null; - } - // TODO some array_values on this shit - return phpQuery::ajax(array( - 'type' => 'GET', - 'url' => $url, - 'data' => $data, - 'success' => $callback, - 'dataType' => 'json', - )); - } - public static function ajaxSetup($options) { - self::$ajaxSettings = array_merge( - self::$ajaxSettings, - $options - ); - } - public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) { - $loop = is_array($host1) - ? $host1 - : func_get_args(); - foreach($loop as $host) { - if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) { - phpQuery::$ajaxAllowedHosts[] = $host; - } - } - } - public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) { - $loop = is_array($url1) - ? $url1 - : func_get_args(); - foreach($loop as $url) - phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST)); - } - /** - * Returns JSON representation of $data. - * - * @static - * @param mixed $data - * @return string - */ - public static function toJSON($data) { - if (function_exists('json_encode')) - return json_encode($data); - require_once('Zend/Json/Encoder.php'); - return Zend_Json_Encoder::encode($data); - } - /** - * Parses JSON into proper PHP type. - * - * @static - * @param string $json - * @return mixed - */ - public static function parseJSON($json) { - if (function_exists('json_decode')) { - $return = json_decode(trim($json), true); - // json_decode and UTF8 issues - if (isset($return)) - return $return; - } - require_once('Zend/Json/Decoder.php'); - return Zend_Json_Decoder::decode($json); - } - /** - * Returns source's document ID. - * - * @param $source DOMNode|phpQueryObject - * @return string - */ - public static function getDocumentID($source) { - if ($source instanceof DOMDOCUMENT) { - foreach(phpQuery::$documents as $id => $document) { - if ($source->isSameNode($document->document)) - return $id; - } - } else if ($source instanceof DOMNODE) { - foreach(phpQuery::$documents as $id => $document) { - if ($source->ownerDocument->isSameNode($document->document)) - return $id; - } - } else if ($source instanceof phpQueryObject) - return $source->getDocumentID(); - else if (is_string($source) && isset(phpQuery::$documents[$source])) - return $source; - } - /** - * Get DOMDocument object related to $source. - * Returns null if such document doesn't exist. - * - * @param $source DOMNode|phpQueryObject|string - * @return string - */ - public static function getDOMDocument($source) { - if ($source instanceof DOMDOCUMENT) - return $source; - $source = self::getDocumentID($source); - return $source - ? self::$documents[$id]['document'] - : null; - } - - // UTILITIES - // http://docs.jquery.com/Utilities - - /** - * - * @return unknown_type - * @link http://docs.jquery.com/Utilities/jQuery.makeArray - */ - public static function makeArray($obj) { - $array = array(); - if (is_object($object) && $object instanceof DOMNODELIST) { - foreach($object as $value) - $array[] = $value; - } else if (is_object($object) && ! ($object instanceof Iterator)) { - foreach(get_object_vars($object) as $name => $value) - $array[0][$name] = $value; - } else { - foreach($object as $name => $value) - $array[0][$name] = $value; - } - return $array; - } - public static function inArray($value, $array) { - return in_array($value, $array); - } - /** - * - * @param $object - * @param $callback - * @return unknown_type - * @link http://docs.jquery.com/Utilities/jQuery.each - */ - public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) { - $paramStructure = null; - if (func_num_args() > 2) { - $paramStructure = func_get_args(); - $paramStructure = array_slice($paramStructure, 2); - } - if (is_object($object) && ! ($object instanceof Iterator)) { - foreach(get_object_vars($object) as $name => $value) - phpQuery::callbackRun($callback, array($name, $value), $paramStructure); - } else { - foreach($object as $name => $value) - phpQuery::callbackRun($callback, array($name, $value), $paramStructure); - } - } - /** - * - * @link http://docs.jquery.com/Utilities/jQuery.map - */ - public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) { - $result = array(); - $paramStructure = null; - if (func_num_args() > 2) { - $paramStructure = func_get_args(); - $paramStructure = array_slice($paramStructure, 2); - } - foreach($array as $v) { - $vv = phpQuery::callbackRun($callback, array($v), $paramStructure); -// $callbackArgs = $args; -// foreach($args as $i => $arg) { -// $callbackArgs[$i] = $arg instanceof CallbackParam -// ? $v -// : $arg; -// } -// $vv = call_user_func_array($callback, $callbackArgs); - if (is_array($vv)) { - foreach($vv as $vvv) - $result[] = $vvv; - } else if ($vv !== null) { - $result[] = $vv; - } - } - return $result; - } - /** - * - * @param $callback Callback - * @param $params - * @param $paramStructure - * @return unknown_type - */ - public static function callbackRun($callback, $params = array(), $paramStructure = null) { - if (! $callback) - return; - if ($callback instanceof CallbackParameterToReference) { - // TODO support ParamStructure to select which $param push to reference - if (isset($params[0])) - $callback->callback = $params[0]; - return true; - } - if ($callback instanceof Callback) { - $paramStructure = $callback->params; - $callback = $callback->callback; - } - if (! $paramStructure) - return call_user_func_array($callback, $params); - $p = 0; - foreach($paramStructure as $i => $v) { - $paramStructure[$i] = $v instanceof CallbackParam - ? $params[$p++] - : $v; - } - return call_user_func_array($callback, $paramStructure); - } - /** - * Merge 2 phpQuery objects. - * @param array $one - * @param array $two - * @protected - * @todo node lists, phpQueryObject - */ - public static function merge($one, $two) { - $elements = $one->elements; - foreach($two->elements as $node) { - $exists = false; - foreach($elements as $node2) { - if ($node2->isSameNode($node)) - $exists = true; - } - if (! $exists) - $elements[] = $node; - } - return $elements; -// $one = $one->newInstance(); -// $one->elements = $elements; -// return $one; - } - /** - * - * @param $array - * @param $callback - * @param $invert - * @return unknown_type - * @link http://docs.jquery.com/Utilities/jQuery.grep - */ - public static function grep($array, $callback, $invert = false) { - $result = array(); - foreach($array as $k => $v) { - $r = call_user_func_array($callback, array($v, $k)); - if ($r === !(bool)$invert) - $result[] = $v; - } - return $result; - } - public static function unique($array) { - return array_unique($array); - } - /** - * - * @param $function - * @return unknown_type - * @TODO there are problems with non-static methods, second parameter pass it - * but doesnt verify is method is really callable - */ - public static function isFunction($function) { - return is_callable($function); - } - public static function trim($str) { - return trim($str); - } - /* PLUGINS NAMESPACE */ - /** - * - * @param $url - * @param $callback - * @param $param1 - * @param $param2 - * @param $param3 - * @return phpQueryObject - */ - public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) { - if (self::plugin('WebBrowser')) { - $params = func_get_args(); - return self::callbackRun(array(self::$plugins, 'browserGet'), $params); - } else { - self::debug('WebBrowser plugin not available...'); - } - } - /** - * - * @param $url - * @param $data - * @param $callback - * @param $param1 - * @param $param2 - * @param $param3 - * @return phpQueryObject - */ - public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) { - if (self::plugin('WebBrowser')) { - $params = func_get_args(); - return self::callbackRun(array(self::$plugins, 'browserPost'), $params); - } else { - self::debug('WebBrowser plugin not available...'); - } - } - /** - * - * @param $ajaxSettings - * @param $callback - * @param $param1 - * @param $param2 - * @param $param3 - * @return phpQueryObject - */ - public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) { - if (self::plugin('WebBrowser')) { - $params = func_get_args(); - return self::callbackRun(array(self::$plugins, 'browser'), $params); - } else { - self::debug('WebBrowser plugin not available...'); - } - } - /** - * - * @param $code - * @return string - */ - public static function php($code) { - return self::code('php', $code); - } - /** - * - * @param $type - * @param $code - * @return string - */ - public static function code($type, $code) { - return "<$type>"; - } - - public static function __callStatic($method, $params) { - return call_user_func_array( - array(phpQuery::$plugins, $method), - $params - ); - } - protected static function dataSetupNode($node, $documentID) { - // search are return if alredy exists - foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) { - if ($node->isSameNode($dataNode)) - return $dataNode; - } - // if doesn't, add it - phpQuery::$documents[$documentID]->dataNodes[] = $node; - return $node; - } - protected static function dataRemoveNode($node, $documentID) { - // search are return if alredy exists - foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) { - if ($node->isSameNode($dataNode)) { - unset(self::$documents[$documentID]->dataNodes[$k]); - unset(self::$documents[$documentID]->data[ $dataNode->dataID ]); - } - } - } - public static function data($node, $name, $data, $documentID = null) { - if (! $documentID) - // TODO check if this works - $documentID = self::getDocumentID($node); - $document = phpQuery::$documents[$documentID]; - $node = self::dataSetupNode($node, $documentID); - if (! isset($node->dataID)) - $node->dataID = ++phpQuery::$documents[$documentID]->uuid; - $id = $node->dataID; - if (! isset($document->data[$id])) - $document->data[$id] = array(); - if (! is_null($data)) - $document->data[$id][$name] = $data; - if ($name) { - if (isset($document->data[$id][$name])) - return $document->data[$id][$name]; - } else - return $id; - } - public static function removeData($node, $name, $documentID) { - if (! $documentID) - // TODO check if this works - $documentID = self::getDocumentID($node); - $document = phpQuery::$documents[$documentID]; - $node = self::dataSetupNode($node, $documentID); - $id = $node->dataID; - if ($name) { - if (isset($document->data[$id][$name])) - unset($document->data[$id][$name]); - $name = null; - foreach($document->data[$id] as $name) - break; - if (! $name) - self::removeData($node, $name, $documentID); - } else { - self::dataRemoveNode($node, $documentID); - } - } -} -/** - * Plugins static namespace class. - * - * @author Tobiasz Cudnik - * @package phpQuery - * @todo move plugin methods here (as statics) - */ -class phpQueryPlugins { - public function __call($method, $args) { - if (isset(phpQuery::$extendStaticMethods[$method])) { - $return = call_user_func_array( - phpQuery::$extendStaticMethods[$method], - $args - ); - } else if (isset(phpQuery::$pluginsStaticMethods[$method])) { - $class = phpQuery::$pluginsStaticMethods[$method]; - $realClass = "phpQueryPlugin_$class"; - $return = call_user_func_array( - array($realClass, $method), - $args - ); - return isset($return) - ? $return - : $this; - } else - throw new Exception("Method '{$method}' doesnt exist"); - } -} -/** - * Shortcut to phpQuery::pq($arg1, $context) - * Chainable. - * - * @see phpQuery::pq() - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @author Tobiasz Cudnik - * @package phpQuery - */ -function pq($arg1, $context = null) { - $args = func_get_args(); - return call_user_func_array( - array('phpQuery', 'pq'), - $args - ); -} -// add plugins dir and Zend framework to include path -set_include_path( - get_include_path() - .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/' - .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/' -); -// why ? no __call nor __get for statics in php... -// XXX __callStatic will be available in PHP 5.3 -phpQuery::$plugins = new phpQueryPlugins(); -// include bootstrap file (personal library config) -if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php')) - require_once dirname(__FILE__).'/phpQuery/bootstrap.php'; diff --git a/core/Objects/ApiObject.class.php b/core/Objects/ApiObject.class.php index 52ab893..a0206dd 100644 --- a/core/Objects/ApiObject.class.php +++ b/core/Objects/ApiObject.class.php @@ -6,9 +6,6 @@ abstract class ApiObject implements \JsonSerializable { public abstract function jsonSerialize(); - public function __construct() { } public function __toString() { return json_encode($this); } } - -?> diff --git a/core/Objects/ConnectionData.class.php b/core/Objects/ConnectionData.class.php index e6a59c8..0523aba 100644 --- a/core/Objects/ConnectionData.class.php +++ b/core/Objects/ConnectionData.class.php @@ -4,11 +4,11 @@ namespace Objects; class ConnectionData { - private $host; - private $port; - private $login; - private $password; - private $properties; + private string $host; + private int $port; + private string $login; + private string $password; + private array $properties; public function __construct($host, $port, $login, $password) { $this->host = $host; @@ -32,12 +32,11 @@ class ConnectionData { } $this->properties[$key] = $val; + return true; } public function getHost() { return $this->host; } public function getPort() { return $this->port; } public function getLogin() { return $this->login; } public function getPassword() { return $this->password; } -} - -?> +} \ No newline at end of file diff --git a/core/Objects/Language.class.php b/core/Objects/Language.class.php index 928f3b5..84a19fc 100644 --- a/core/Objects/Language.class.php +++ b/core/Objects/Language.class.php @@ -2,16 +2,18 @@ namespace Objects { - class Language extends ApiObject { + use Objects\lang\LanguageModule; + + class Language extends ApiObject { const LANG_CODE_PATTERN = "/^[a-zA-Z]+_[a-zA-Z]+$/"; - private $languageId; - private $langCode; - private $langName; - private $modules; + private int $languageId; + private string $langCode; + private string $langName; + private array $modules; - protected $entries; + protected array $entries; public function __construct($languageId, $langCode, $langName) { $this->languageId = $languageId; @@ -29,7 +31,7 @@ namespace Objects { public function getEntries() { return $this->entries; } public function getModules() { return $this->modules; } - public function loadModule($module) { + public function loadModule(LanguageModule $module) { if(!is_object($module)) $module = new $module; @@ -100,7 +102,8 @@ namespace Objects { } namespace { - function L($key) { + + function L($key) { if(!array_key_exists('LANGUAGE', $GLOBALS)) return $key; @@ -132,4 +135,3 @@ namespace { return $LANGUAGE->getShortCode(); } } -?> diff --git a/core/Objects/Session.class.php b/core/Objects/Session.class.php index 3ac560a..e3e9d35 100644 --- a/core/Objects/Session.class.php +++ b/core/Objects/Session.class.php @@ -2,28 +2,34 @@ namespace Objects; +use DateTime; use \Driver\SQL\Condition\Compare; +use Exception; +use External\JWT; class Session extends ApiObject { - const DURATION = 120; + # in minutes + const DURATION = 60*24; - private $sessionId; - private $user; - private $expires; - private $ipAddress; - private $os; - private $browser; - private $stayLoggedIn; + private ?int $sessionId; + private User $user; + private int $expires; + private string $ipAddress; + private ?string $os; + private ?string $browser; + private bool $stayLoggedIn; + private string $csrfToken; - public function __construct($user, $sessionId) { + public function __construct(User $user, ?int $sessionId, ?string $csrfToken) { $this->user = $user; $this->sessionId = $sessionId; $this->stayLoggedIn = true; + $this->csrfToken = $csrfToken ?? generateRandomString(16); } public static function create($user, $stayLoggedIn) { - $session = new Session($user, null); + $session = new Session($user, null, null); if($session->insert($stayLoggedIn)) { return $session; } @@ -38,7 +44,7 @@ class Session extends ApiObject { $userAgent = @get_browser($_SERVER['HTTP_USER_AGENT'], true); $this->os = $userAgent['platform'] ?? "Unknown"; $this->browser = $userAgent['parent'] ?? "Unknown"; - } catch(\Exception $ex) { + } catch(Exception $ex) { $this->os = "Unknown"; $this->browser = "Unknown"; } @@ -56,13 +62,11 @@ class Session extends ApiObject { public function sendCookie() { $this->updateMetaData(); - $jwt = $this->user->getConfiguration()->getJwt(); - if($jwt) { - $token = array('userId' => $this->user->getId(), 'sessionId' => $this->sessionId); - $sessionCookie = \External\JWT::encode($token, $jwt->getKey()); - $secure = strcmp(getProtocol(), "https") === 0; - setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", "", $secure); - } + $settings = $this->user->getConfiguration()->getSettings(); + $token = array('userId' => $this->user->getId(), 'sessionId' => $this->sessionId); + $sessionCookie = JWT::encode($token, $settings->getJwtSecret()); + $secure = strcmp(getProtocol(), "https") === 0; + setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", "", $secure); } public function getExpiresTime() { @@ -81,6 +85,7 @@ class Session extends ApiObject { 'ipAddress' => $this->ipAddress, 'os' => $this->os, 'browser' => $this->browser, + 'csrf_token' => $this->csrfToken ); } @@ -88,19 +93,20 @@ class Session extends ApiObject { $this->updateMetaData(); $sql = $this->user->getSQL(); - $hours = Session::DURATION; - $columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in"); + $minutes = Session::DURATION; + $columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in", "csrf_token"); $success = $sql ->insert("Session", $columns) ->addRow( - (new \DateTime)->modify("+$hours hour"), + (new DateTime())->modify("+$minutes minute"), $this->user->getId(), $this->ipAddress, $this->os, $this->browser, json_encode($_SESSION), - $stayLoggedIn) + $stayLoggedIn, + $this->csrfToken) ->returning("uid") ->execute(); @@ -113,32 +119,31 @@ class Session extends ApiObject { } public function destroy() { - $success = $this->user->getSQL()->update("Session") + return $this->user->getSQL()->update("Session") ->set("active", false) ->where(new Compare("Session.uid", $this->sessionId)) ->where(new Compare("Session.user_id", $this->user->getId())) ->execute(); - - return $success; } public function update() { $this->updateMetaData(); - $hours = Session::DURATION; + $minutes = Session::DURATION; $sql = $this->user->getSQL(); - $success = $sql->update("Session") - ->set("Session.expires", (new \DateTime)->modify("+$hours hour")) + return $sql->update("Session") + ->set("Session.expires", (new DateTime())->modify("+$minutes minute")) ->set("Session.ipAddress", $this->ipAddress) ->set("Session.os", $this->os) ->set("Session.browser", $this->browser) ->set("Session.data", json_encode($_SESSION)) + ->set("Session.csrf_token", $this->csrfToken) ->where(new Compare("Session.uid", $this->sessionId)) ->where(new Compare("Session.user_id", $this->user->getId())) ->execute(); + } - return $success; + public function getCsrfToken(): string { + return $this->csrfToken; } } - -?> diff --git a/core/Objects/User.class.php b/core/Objects/User.class.php index 40628a1..f442006 100644 --- a/core/Objects/User.class.php +++ b/core/Objects/User.class.php @@ -2,25 +2,32 @@ namespace Objects; -use \External\JWT; -use Driver\SQL\Column\Column; +use Configuration\Configuration; +use DateTime; +use Driver\SQL\Expression\Add; +use Driver\SQL\Strategy\UpdateStrategy; +use Exception; +use External\JWT; +use Driver\SQL\SQL; use Driver\SQL\Condition\Compare; use Driver\SQL\Condition\CondBool; class User extends ApiObject { - private $sql; - private $configuration; - private $loggedIn; - private $session; - private $uid; - private $username; - private $language; + private ?SQL $sql; + private Configuration $configuration; + private bool $loggedIn; + private ?Session $session; + private int $uid; + private string $username; + private ?string $email; + private Language $language; + private array $groups; public function __construct($configuration) { session_start(); $this->configuration = $configuration; - $this->setLangauge(Language::DEFAULT_LANGUAGE()); + $this->setLanguage(Language::DEFAULT_LANGUAGE()); $this->reset(); $this->connectDb(); $this->parseCookies(); @@ -35,18 +42,27 @@ class User extends ApiObject { private function connectDb() { $databaseConf = $this->configuration->getDatabase(); if($databaseConf) { - $this->sql = \Driver\SQL\SQL::createConnection($databaseConf); + $this->sql = SQL::createConnection($databaseConf); + if ($this->sql->isConnected()) { + $settings = $this->configuration->getSettings(); + $settings->loadFromDatabase($this); + } + } else { + $this->sql = null; } } public function getId() { return $this->uid; } public function isLoggedIn() { return $this->loggedIn; } public function getUsername() { return $this->username; } + public function getEmail() { return $this->email; } public function getSQL() { return $this->sql; } public function getLanguage() { return $this->language; } - public function setLangauge($language) { $this->language = $language; $language->load(); } + public function setLanguage(Language $language) { $this->language = $language; $language->load(); } public function getSession() { return $this->session; } public function getConfiguration() { return $this->configuration; } + public function getGroups() { return $this->groups; } + public function hasGroup(int $group) { return isset($this->groups[$group]); } public function __debugInfo() { $debugInfo = array( @@ -63,19 +79,28 @@ class User extends ApiObject { } public function jsonSerialize() { - return array( - 'uid' => $this->uid, - 'name' => $this->username, - 'language' => $this->language, - 'session' => $this->session, - ); + if ($this->isLoggedIn()) { + return array( + 'uid' => $this->uid, + 'name' => $this->username, + 'email' => $this->email, + 'groups' => $this->groups, + 'language' => $this->language->jsonSerialize(), + 'session' => $this->session->jsonSerialize(), + ); + } else { + return array( + 'language' => $this->language->jsonSerialize(), + ); + } } private function reset() { $this->uid = 0; $this->username = ''; + $this->email = ''; $this->loggedIn = false; - $this->session = false; + $this->session = null; } public function logout() { @@ -90,8 +115,10 @@ class User extends ApiObject { public function updateLanguage($lang) { if($this->sql) { - $request = new \Api\SetLanguage($this); + $request = new \Api\Language\Set($this); return $request->execute(array("langCode" => $lang)); + } else { + return false; } } @@ -101,15 +128,19 @@ class User extends ApiObject { } $this->language->sendCookie(); + session_write_close(); } public function readData($userId, $sessionId, $sessionUpdate = true) { - $res = $this->sql->select("User.name", "Language.uid as langId", "Language.code as langCode", "Language.name as langName", - "Session.data", "Session.stay_logged_in") + $res = $this->sql->select("User.name", "User.email", + "Language.uid as langId", "Language.code as langCode", "Language.name as langName", + "Session.data", "Session.stay_logged_in", "Session.csrf_token", "Group.uid as groupId", "Group.name as groupName") ->from("User") ->innerJoin("Session", "Session.user_id", "User.uid") ->leftJoin("Language", "User.language_id", "Language.uid") + ->leftJoin("UserGroup", "UserGroup.user_id", "User.uid") + ->leftJoin("Group", "UserGroup.group_id", "Group.uid") ->where(new Compare("User.uid", $userId)) ->where(new Compare("Session.uid", $sessionId)) ->where(new Compare("Session.active", true)) @@ -122,15 +153,22 @@ class User extends ApiObject { $success = false; } else { $row = $res[0]; + $csrfToken = $row["csrf_token"]; $this->username = $row['name']; + $this->email = $row["email"]; $this->uid = $userId; - $this->session = new Session($this, $sessionId); + $this->session = new Session($this, $sessionId, $csrfToken); $this->session->setData(json_decode($row["data"] ?? '{}')); - $this->session->stayLoggedIn($row["stay_logged_in"]); + $this->session->stayLoggedIn($this->sql->parseBool(["stay_logged_in"])); if($sessionUpdate) $this->session->update(); $this->loggedIn = true; + if(!is_null($row['langId'])) { - $this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName'])); + $this->setLanguage(Language::newInstance($row['langId'], $row['langCode'], $row['langName'])); + } + + foreach($res as $row) { + $this->groups[$row["groupId"]] = $row["groupName"]; } } } @@ -141,11 +179,11 @@ class User extends ApiObject { private function parseCookies() { if(isset($_COOKIE['session']) && is_string($_COOKIE['session']) - && !empty($_COOKIE['session']) - && ($jwt = $this->configuration->getJWT())) { + && !empty($_COOKIE['session'])) { try { $token = $_COOKIE['session']; - $decoded = (array)JWT::decode($token, $jwt->getKey()); + $settings = $this->configuration->getSettings(); + $decoded = (array)JWT::decode($token, $settings->getJwtSecret()); if(!is_null($decoded)) { $userId = (isset($decoded['userId']) ? $decoded['userId'] : NULL); $sessionId = (isset($decoded['sessionId']) ? $decoded['sessionId'] : NULL); @@ -153,7 +191,7 @@ class User extends ApiObject { $this->readData($userId, $sessionId); } } - } catch(\Exception $e) { + } catch(Exception $e) { // ignored } } @@ -181,13 +219,14 @@ class User extends ApiObject { if($this->loggedIn) return true; - $res = $this->sql->select("ApiKey.user_id as uid", "User.name as username", "Language.uid as langId", "Language.code as langCode", "Language.name as langName") + $res = $this->sql->select("ApiKey.user_id as uid", "User.name", "User.email", "User.confirmed", + "Language.uid as langId", "Language.code as langCode", "Language.name as langName") ->from("ApiKey") ->innerJoin("User", "ApiKey.user_id", "User.uid") ->leftJoin("Language", "User.language_id", "Language.uid") ->where(new Compare("ApiKey.api_key", $apiKey)) ->where(new Compare("valid_until", $this->sql->currentTimestamp(), ">")) - ->where(new COmpare("ApiKey.active", 1)) + ->where(new Compare("ApiKey.active", 1)) ->execute(); $success = ($res !== FALSE); @@ -196,17 +235,47 @@ class User extends ApiObject { $success = false; } else { $row = $res[0]; + if (!$this->sql->parseBool($row["confirmed"])) { + return false; + } + $this->uid = $row['uid']; - $this->username = $row['username']; + $this->username = $row['name']; + $this->email = $row['email']; if(!is_null($row['langId'])) { - $this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName'])); + $this->setLanguage(Language::newInstance($row['langId'], $row['langCode'], $row['langName'])); } } } return $success; } -} -?> + public function processVisit() { + if ($this->sql && $this->sql->isConnected() && isset($_COOKIE["PHPSESSID"]) && !empty($_COOKIE["PHPSESSID"])) { + + if ($this->isBot()) { + return; + } + + $cookie = $_COOKIE["PHPSESSID"]; + $day = (new DateTime())->format("Ymd"); + + $this->sql->insert("Visitor", array("cookie", "day")) + ->addRow($cookie, $day) + ->onDuplicateKeyStrategy(new UpdateStrategy( + array("month", "cookie"), + array("count" => new Add("Visitor.count", 1)))) + ->execute(); + } + } + + private function isBot() { + if (!isset($_SERVER["HTTP_USER_AGENT"]) || empty($_SERVER["HTTP_USER_AGENT"])) { + return false; + } + + return preg_match('/robot|spider|crawler|curl|^$/i', $_SERVER['HTTP_USER_AGENT']) === 1; + } +} diff --git a/core/Objects/lang/General.php b/core/Objects/lang/General.php deleted file mode 100644 index 6d1874c..0000000 --- a/core/Objects/lang/General.php +++ /dev/null @@ -1,17 +0,0 @@ -entries[""] = ""; - break; - } - } - -} - -?> diff --git a/core/Objects/lang/LanguageModule.php b/core/Objects/lang/LanguageModule.php index 6a24aa5..20a932d 100644 --- a/core/Objects/lang/LanguageModule.php +++ b/core/Objects/lang/LanguageModule.php @@ -1,9 +1,8 @@ + public abstract function getEntries(string $langCode); +} \ No newline at end of file diff --git a/core/View.class.php b/core/View.class.php deleted file mode 100644 index 664a887..0000000 --- a/core/View.class.php +++ /dev/null @@ -1,230 +0,0 @@ -document = $document; - $this->searchable = false; - $this->printable = false; - $this->reference = ""; - $this->title = "Untitled View"; - $this->langModules = array(); - $this->loadView = $loadView; - } - - public function getTitle() { return $this->title; } - public function __toString() { return $this->getCode(); } - public function getDocument() { return $this->document; } - public function isSearchable() { return $this->searchable; } - public function getReference() { return $this->reference; } - - private function loadLanguageModules() { - $lang = $this->document->getUser()->getLanguage(); - foreach($this->langModules as $langModule) { - $lang->loadModule($langModule); - } - } - - // Virtual Methods - public function loadView() { } - - public function getCode() { - - // Load translations - $this->loadLanguageModules(); - - // Load Meta Data + Head (title, scripts, includes, ...) - if($this->loadView) { - $this->loadView(); - } - - return ''; - } - - // UI Functions - - // TODO: do we need this in our general web-base? - public function createFileIcon($mimeType) { - $mimeType = htmlspecialchars($mimeType); - return "\"[$mimeType"; - } - - public function createParagraph($title, $id, $content) { - $id = replaceCssSelector($id); - $iconId = urlencode("$id-icon"); - return " -
    -
    - -

    $title

    -

    $content
    -
    -
    "; - } - - public function createSimpleParagraph($content, $class="") { - if($class) $class = " class=\"$class\""; - return "$content

    "; - } - - private function createList($items, $tag) { - if(count($items) === 0) - return "<$tag>"; - else - return "<$tag>
  • " . implode("
  • ", $items) . "
  • "; - } - - public function createOrderedList($items=array()) { - return $this->createList($items, "ol"); - } - - public function createUnorderedList($items=array()) { - return $this->createList($items, "ul"); - } - - public function createJumbotron($content, $lastModified=false) { - $lastModified = ($lastModified ? "Last modified: $lastModified" : ""); - return " -
    -
    -
    - $content - $lastModified -
    -
    -
    "; - } - - protected function createLink($link, $title=null) { - if(is_null($title)) $title=$link; - return "$title"; - } - - protected function createExternalLink($link, $title=null) { - if(is_null($title)) $title=$link; - return "$title"; - } - - protected function createCodeBlock($code, $lang="") { - if($lang) $lang = " class=\"$lang\""; - $html = "
    ";
    -    $html .= intendCode($code);
    -    $html .= "
    "; - return $html; - } - - protected function createIcon($icon, $margin = NULL) { - $marginStr = (is_null($margin) ? "" : " margin-$margin"); - $iconClass = $this->getIconClass($icon); - return ""; - } - - protected function getIconClass($icon) { - - $mappings = array( - "sign-out" => "sign-out-alt", - "bank" => "university", - "line-chart" => "chart-line", - "circle-right" => "arrow-alt-circle-right", - "refresh" => "sync" - ); - - if(isset($mappings[$icon])) - $icon = $mappings[$icon]; - - if($icon === "spinner") - $icon .= " fa-spin"; - - return "fas fa-$icon"; - } - - protected function createBootstrapTable($data) { - $code = "
    "; - foreach($data as $row) { - $code .= "
    "; - $columnCount = count($row); - if($columnCount > 0) { - $remainingSize = 12; - $columnSize = 12 / $columnCount; - foreach($row as $col) { - $size = ($columnSize <= $remainingSize ? $columnSize : $remainingSize); - $content = $col; - $class = ""; - $code .= " $val) { - if(strcmp($key, "content") === 0) { - $content = $val; - } else if(strcmp($key, "class") === 0) { - $class = " " . $col["class"]; - } else if(strcmp($key, "cols") === 0 && is_numeric($val)) { - $size = intval($val); - } else { - $code .= " $key=\"$val\""; - } - } - - $content = (isset($col["content"]) ? $col["content"] : ""); - if(isset($col["class"])) $class = " " . $col["class"]; - } - - if($size <= 6) $class .= " col-md-" . intval($size * 2); - $code .= " class=\"col-lg-$size$class\">$content
    "; - $remainingSize -= $size; - } - } - $code .= "
    "; - } - - $code .= "
    "; - return $code; - } - - protected function createBash($command, $output="", $prefix="") { - $command = htmlspecialchars($command); - $output = htmlspecialchars($output); - $output = str_replace("\n", "
    ", $output); - return "
    - $prefix$  - $command
    - $output -
    "; - } - - protected function createErrorText($text, $id="", $hidden=false) { - return $this->createStatusText("danger", $text, $id, $hidden); - } - - protected function createWarningText($text, $id="", $hidden=false) { - return $this->createStatusText("warning", $text, $id, $hidden); - } - - protected function createSuccessText($text, $id="", $hidden=false) { - return $this->createStatusText("success", $text, $id, $hidden); - } - - protected function createSecondaryText($text, $id="", $hidden=false) { - return $this->createStatusText("secondary", $text, $id, $hidden); - } - - protected function createInfoText($text, $id="", $hidden=false) { - return $this->createStatusText("info", $text, $id, $hidden); - } - - protected function createStatusText($type, $text, $id="", $hidden=false) { - if(strlen($id) > 0) $id = " id=\"$id\""; - $hidden = ($hidden?" hidden" : ""); - return "
    $text
    "; - } -}; - - -?> diff --git a/core/Views/Account/AcceptInvite.class.php b/core/Views/Account/AcceptInvite.class.php new file mode 100644 index 0000000..d1c7c4b --- /dev/null +++ b/core/Views/Account/AcceptInvite.class.php @@ -0,0 +1,89 @@ +title = "Invitation"; + $this->description = "Finnish your account registration by choosing a password."; + $this->icon = "user-check"; + $this->success = false; + $this->message = "No content"; + $this->invitedUser = array(); + } + + public function loadView() { + parent::loadView(); + + if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) { + $req = new \Api\User\CheckToken($this->getDocument()->getUser()); + $this->success = $req->execute(array("token" => $_GET["token"])); + if ($this->success) { + if (strcmp($req->getResult()["token"]["type"], "invite") !== 0) { + $this->success = false; + $this->message = "The given token has a wrong type."; + } else { + $this->invitedUser = $req->getResult()["user"]; + } + } else { + $this->message = "Error confirming e-mail address: " . $req->getLastError(); + } + } else { + $this->success = false; + $this->message = "The link you visited is no longer valid"; + } + } + + protected function getAccountContent() { + if (!$this->success) { + return $this->createErrorText($this->message); + } + + $token = htmlspecialchars($_GET["token"], ENT_QUOTES); + $username = $this->invitedUser["name"]; + $emailAddress = $this->invitedUser["email"]; + + return "

    Please fill with your details

    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    "; + } +} \ No newline at end of file diff --git a/core/Views/Account/AccountView.class.php b/core/Views/Account/AccountView.class.php new file mode 100644 index 0000000..126e93e --- /dev/null +++ b/core/Views/Account/AccountView.class.php @@ -0,0 +1,61 @@ +description = ""; + $this->icon = "image"; + } + + public function loadView() { + parent::loadView(); + + $document = $this->getDocument(); + $settings = $document->getUser()->getConfiguration()->getSettings(); + if ($settings->isRecaptchaEnabled()) { + $document->getHead()->loadGoogleRecaptcha($settings->getRecaptchaSiteKey()); + } + } + + public function getCode() { + $html = parent::getCode(); + + $content = $this->getAccountContent(); + $icon = $this->createIcon($this->icon, "fas", "fa-3x"); + + $html .= "
    +
    +
    +
    + $icon +

    $this->title

    +

    $this->description

    +
    +
    +
    + $content + +
    +
    +
    "; + + $settings = $this->getDocument()->getUser()->getConfiguration()->getSettings(); + if ($settings->isRecaptchaEnabled()) { + $siteKey = $settings->getRecaptchaSiteKey(); + $html .= ""; + } + + return $html; + } + + protected abstract function getAccountContent(); +} \ No newline at end of file diff --git a/core/Views/Account/ConfirmEmail.class.php b/core/Views/Account/ConfirmEmail.class.php new file mode 100644 index 0000000..47abe06 --- /dev/null +++ b/core/Views/Account/ConfirmEmail.class.php @@ -0,0 +1,46 @@ +title = "Confirm Email"; + $this->icon = "user-check"; + $this->success = false; + $this->message = "No content"; + } + + public function loadView() { + parent::loadView(); + + if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) { + $req = new \Api\User\ConfirmEmail($this->getDocument()->getUser()); + $this->success = $req->execute(array("token" => $_GET["token"])); + if ($this->success) { + $this->message = "Your e-mail address was successfully confirmed, you may now log in"; + } else { + $this->message = "Error confirming e-mail address: " . $req->getLastError(); + } + } else { + $this->success = false; + $this->message = "The link you visited is no longer valid"; + } + } + + protected function getAccountContent() { + if ($this->success) { + return $this->createSuccessText($this->message); + } else { + return $this->createErrorText($this->message); + } + } +} \ No newline at end of file diff --git a/core/Views/Account/Register.class.php b/core/Views/Account/Register.class.php new file mode 100644 index 0000000..3cb384a --- /dev/null +++ b/core/Views/Account/Register.class.php @@ -0,0 +1,58 @@ +title = "Registration"; + $this->description = "Create a new account"; + $this->icon = "user-plus"; + } + + public function getAccountContent() { + + $settings = $this->getDocument()->getUser()->getConfiguration()->getSettings(); + if (!$settings->isRegistrationAllowed()) { + return $this->createErrorText( + "Registration is not enabled on this website. If you are an administrator, + goto /admin/settings, to enable the user registration" + ); + } + + return "

    Please fill with your details

    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    "; + } +} \ No newline at end of file diff --git a/core/Views/Account/ResetPassword.class.php b/core/Views/Account/ResetPassword.class.php new file mode 100644 index 0000000..ba6a4b2 --- /dev/null +++ b/core/Views/Account/ResetPassword.class.php @@ -0,0 +1,87 @@ +title = "Reset Password"; + $this->description = "Request a password reset, once you got the e-mail address, you can choose a new password"; + $this->icon = "user-lock"; + $this->success = true; + $this->message = ""; + $this->token = NULL; + } + + public function loadView() { + parent::loadView(); + + if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) { + $this->token = $_GET["token"]; + $req = new \Api\User\CheckToken($this->getDocument()->getUser()); + $this->success = $req->execute(array("token" => $_GET["token"])); + if ($this->success) { + if (strcmp($req->getResult()["token"]["type"], "password_reset") !== 0) { + $this->success = false; + $this->message = "The given token has a wrong type."; + } + } else { + $this->message = "Error requesting password reset: " . $req->getLastError(); + } + } + } + + protected function getAccountContent() { + if (!$this->success) { + $html = $this->createErrorText($this->message); + if ($this->token !== null) { + $html .= "Go back"; + } + return $html; + } + + if ($this->token === null) { + return "

    Enter your E-Mail address, to receive a password reset token.

    +
    +
    +
    + +
    + +
    +
    + +
    + "; + } else { + return "

    Choose a new password

    + + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    "; + } + } +} \ No newline at end of file diff --git a/core/Views/Admin.class.php b/core/Views/Admin.class.php deleted file mode 100644 index af91e21..0000000 --- a/core/Views/Admin.class.php +++ /dev/null @@ -1,47 +0,0 @@ -createIcon("bars"); - $iconSearch = $this->createIcon("search"); - $iconNotifications = $this->createIcon("bell"); - $header = ""; - - return $header; - } - - private function getMainContent() { - return ""; - } - - private function getSideBar() { - return ""; - } - - public function getCode() { - $html = parent::getCode(); - - $html .= "
    "; - $html .= $this->getMainHeader(); - $html .= "
    "; - $html .= $this->getSideBar(); - $html .= $this->getMainContent(); - $html .= "
    -
    "; - - return $html; - } -} - -?> diff --git a/core/Views/Admin/AdminDashboardBody.class.php b/core/Views/Admin/AdminDashboardBody.class.php new file mode 100644 index 0000000..fc5fde4 --- /dev/null +++ b/core/Views/Admin/AdminDashboardBody.class.php @@ -0,0 +1,20 @@ +
    $script
    "; + return $html; + } +} diff --git a/core/Views/Admin/LoginBody.class.php b/core/Views/Admin/LoginBody.class.php new file mode 100644 index 0000000..b165656 --- /dev/null +++ b/core/Views/Admin/LoginBody.class.php @@ -0,0 +1,72 @@ +getDocument()->getHead(); + $head->loadJQuery(); + $head->loadBootstrap(); + $head->addJS(Script::CORE); + $head->addCSS(Link::CORE); + $head->addJS(Script::ACCOUNT); + $head->addCSS(Link::ACCOUNT); + } + + public function getCode() { + $html = parent::getCode(); + + $username = L("Username"); + $password = L("Password"); + $login = L("Login"); + $backToStartPage = L("Back to Start Page"); + $stayLoggedIn = L("Stay logged in"); + + $flags = $this->load(LanguageFlags::class); + $iconBack = $this->createIcon("arrow-circle-left"); + $domain = $_SERVER['HTTP_HOST']; + $protocol = getProtocol(); + + $html .= " + +
    +
    +

    Admin Control Panel

    +
    +
    +
    +
    + + + + +
    + + +
    + + + $flags +
    + +
    +
    +
    + "; + + return $html; + } +} diff --git a/core/Views/LanguageFlags.class.php b/core/Views/LanguageFlags.class.php index 72916b7..381ef1b 100644 --- a/core/Views/LanguageFlags.class.php +++ b/core/Views/LanguageFlags.class.php @@ -2,53 +2,61 @@ namespace Views; -class LanguageFlags extends \View { +use Elements\View; + +class LanguageFlags extends View { + + private array $languageFlags; public function __construct($document) { parent::__construct($document); + $this->languageFlags = array(); + $this->searchable = false; } - public function getCode() { + public function loadView() { + parent::loadView(); - $requestUri = $_SERVER["REQUEST_URI"]; - $queryString = $_SERVER['QUERY_STRING']; + $request = new \Api\Language\Get($this->getDocument()->getUser()); + if ($request->execute()) { - $flags = array(); - $request = new \Api\GetLanguages($this->getDocument()->getUser()); - $params = explode("&", $queryString); - $query = array(); - foreach($params as $param) { - $aParam = explode("=", $param); - $key = $aParam[0]; + $requestUri = $_SERVER["REQUEST_URI"]; + $queryString = $_SERVER['QUERY_STRING']; - if($key == "s" && startsWith($requestUri, "/s/")) - continue; + $params = explode("&", $queryString); + $query = array(); + foreach ($params as $param) { + $aParam = explode("=", $param); + $key = $aParam[0]; - $val = (isset($aParam[1]) ? $aParam[1] : ""); - if(!empty($key)) { - $query[$key] = $val; + if ($key === "site" && + (!startsWith($_SERVER["REQUEST_URI"], "/index.php") || $_SERVER["REQUEST_URI"] === "/")) { + continue; + } + + $val = (isset($aParam[1]) ? $aParam[1] : ""); + if (!empty($key)) { + $query[$key] = $val; + } } - } - $url = parse_url($requestUri, PHP_URL_PATH) . "?"; - if($request->execute()) { - foreach($request->getResult()['languages'] as $lang) { + $url = parse_url($requestUri, PHP_URL_PATH) . "?"; + + foreach ($request->getResult()['languages'] as $lang) { $langCode = $lang['code']; $langName = $lang['name']; $query['lang'] = $langCode; $queryString = http_build_query($query); - $flags[] = $this->createLink( + $this->languageFlags[] = $this->createLink( "$url$queryString", - "\"$langName\"" + "\"$langName\"" ); } - } else { - $flags[] = $this->createErrorText($request->getLastError()); } - - return implode('', $flags); } -} -?> + public function getCode() { + return implode('', $this->languageFlags); + } +} \ No newline at end of file diff --git a/core/Views/Login.class.php b/core/Views/Login.class.php deleted file mode 100644 index bb4bf19..0000000 --- a/core/Views/Login.class.php +++ /dev/null @@ -1,55 +0,0 @@ -getDocument()); - $iconBack = $this->createIcon("arrow-circle-left", "right"); - $domain = $_SERVER['HTTP_HOST']; - $protocol = getProtocol(); - - $accountCreated = ""; - if(isset($_GET["accountCreated"])) { - $accountCreated .= ' -
    - Your account was successfully created, you may now login with your credentials -
    '; - } - - $html = " -
    -
    -

    Admin Control Panel

    -
    -
    -
    - - - - - -
    -
    - $flags - $iconBack $backToStartPage - $accountCreated -
    -
    "; - - return $html; - } -} - -?> diff --git a/core/Views/View404.class.php b/core/Views/View404.class.php new file mode 100644 index 0000000..7f972d2 --- /dev/null +++ b/core/Views/View404.class.php @@ -0,0 +1,13 @@ +Not found"; + } + +}; \ No newline at end of file diff --git a/core/constants.php b/core/constants.php index 43e399c..6989e62 100644 --- a/core/constants.php +++ b/core/constants.php @@ -1,6 +1,22 @@ +const DEFAULT_GROUPS = array( + USER_GROUP_MODERATOR, USER_GROUP_SUPPORT, USER_GROUP_ADMIN +); + +function GroupName($index) { + $groupNames = array( + USER_GROUP_MODERATOR => USER_GROUP_MODERATOR_NAME, + USER_GROUP_SUPPORT => USER_GROUP_SUPPORT_NAME, + USER_GROUP_ADMIN => USER_GROUP_ADMIN_NAME, + ); + + return ($groupNames[$index] ?? "Unknown Group"); +} \ No newline at end of file diff --git a/core/core.php b/core/core.php index b4be22a..a27673b 100644 --- a/core/core.php +++ b/core/core.php @@ -1,141 +1,159 @@ $max) return $max; - return $val; - } +define("WEBBASE_VERSION", "1.0.0"); - function downloadFile($url) { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_URL, $url); - $data = curl_exec($ch); - curl_close($ch); - return $data; - } +function getProtocol() { + return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https" : "http"; +} - function getSubclassesOf($parent) { - $result = array(); - foreach (get_declared_classes() as $class) { - if (is_subclass_of($class, $parent)) - $result[] = $class; - } - return $result; - } - - function getProtocol() { - return stripos($_SERVER['SERVER_PROTOCOL'],'https') === 0 ? 'https' : 'http'; - } - - function includeDir($dir, $aIgnore = array(), $recursive = false) { - $aIgnore[] = '.'; - $aIgnore[] = '..'; - $aFiles = array_diff(scandir($dir), $aIgnore); - - foreach($aFiles as $file) { - $file = $dir . '/' . $file; - if(is_dir($file)) { - if($recursive) { - includeDir($file, $aIgnore, true); - } - } else { - require_once $file; - } - } - } - - function generateRandomString($length) { - $randomString = ''; - if($length > 0) { - $numCharacters = 26 + 26 + 10; // a-z + A-Z + 0-9 - for ($i = 0; $i < $length; $i++) - { +function generateRandomString($length): string { + $randomString = ''; + if ($length > 0) { + $numCharacters = 26 + 26 + 10; // a-z + A-Z + 0-9 + for ($i = 0; $i < $length; $i++) { + try { $num = random_int(0, $numCharacters - 1); - if($num < 26) $randomString .= chr(ord('a') + $num); - else if($num - 26 < 26) $randomString .= chr(ord('A') + $num - 26); - else $randomString .= chr(ord('0') + $num - 26 - 26); - } - } - - return $randomString; - } - - function cleanPath($path) { - if($path === '') - return $path; - - $path = str_replace('\\', '/', $path); - $path = str_replace('/./', '/', $path); - - if($path[0] !== '/') - $path = '/' . $path; - - $path = str_replace('/../', '/', $path); - return $path; - } - - function startsWith($haystack, $needle) { - $length = strlen($needle); - return (substr($haystack, 0, $length) === $needle); - } - - function endsWith($haystack, $needle) { - $length = strlen($needle); - if ($length == 0) - return true; - - return (substr($haystack, -$length) === $needle); - } - - function isCalledDirectly($file) { - return $_SERVER['SCRIPT_FILENAME'] === $file; - } - - function anonymzeEmail($mail) { - if(($pos = strpos($mail, '@')) !== -1) { - $name = substr($mail, 0, $pos); - $host = substr($mail, $pos + 1); - if(strlen($name) > 2) $mail = substr($name, 0, 2) . str_repeat('*', strlen($name) - 2) . "@$host"; - else $mail = $mail = str_repeat('*', strlen($name)) . "@$host"; - } - - return $mail; - } - - function intendCode($code, $escape=true) { - $newCode = ""; - $first = true; - $brackets = array(); - $intend = 0; - - foreach(explode("\n", $code) as $line) { - if(!$first) $newCode .= "\n"; - if($escape) $line = htmlspecialchars($line); - $line = trim($line); - - if(count($brackets) > 0 && startsWith($line, current($brackets))) { - $intend = max(0, $intend - 2); - array_pop($brackets); + } catch (Exception $e) { + $num = rand(0, $numCharacters - 1); } - $newCode .= str_repeat(" ", $intend); - $newCode .= $line; - $first = false; + if ($num < 26) $randomString .= chr(ord('a') + $num); + else if ($num - 26 < 26) $randomString .= chr(ord('A') + $num - 26); + else $randomString .= chr(ord('0') + $num - 26 - 26); + } + } - if(endsWith($line, "{")) { - $intend += 2; - array_push($brackets, "}"); - } else if(endsWith($line, "(")) { - $intend += 2; - array_push($brackets, ")"); - } + return $randomString; +} + +function startsWith($haystack, $needle) { + $length = strlen($needle); + return (substr($haystack, 0, $length) === $needle); +} + +function endsWith($haystack, $needle) { + $length = strlen($needle); + if ($length == 0) + return true; + + return (substr($haystack, -$length) === $needle); +} + +function intendCode($code, $escape = true) { + $newCode = ""; + $first = true; + $brackets = array(); + $intend = 0; + + foreach (explode("\n", $code) as $line) { + if (!$first) $newCode .= "\n"; + if ($escape) $line = htmlspecialchars($line); + $line = trim($line); + + if (count($brackets) > 0 && startsWith($line, current($brackets))) { + $intend = max(0, $intend - 2); + array_pop($brackets); } - return $newCode; + $newCode .= str_repeat(" ", $intend); + $newCode .= $line; + $first = false; + + if (endsWith($line, "{")) { + $intend += 2; + array_push($brackets, "}"); + } else if (endsWith($line, "(")) { + $intend += 2; + array_push($brackets, ")"); + } } - function replaceCssSelector($sel) { - return preg_replace("~[.#<>]~", "_", preg_replace("~[:\-]~", "", $sel)); + return $newCode; +} + +function replaceCssSelector($sel) { + return preg_replace("~[.#<>]~", "_", preg_replace("~[:\-]~", "", $sel)); +} + +function getClassPath($class, $suffix = true) { + $path = str_replace('\\', '/', $class); + $path = array_values(array_filter(explode("/", $path))); + + if (strcasecmp($path[0], "api") === 0 && count($path) > 2 && strcasecmp($path[1], "Parameter") !== 0) { + $path = "Api/" . $path[1] . "API"; + } else { + $path = implode("/", $path); } -?> + + $suffix = ($suffix ? ".class" : ""); + return "core/$path$suffix.php"; +} + +function createError($msg) { + return json_encode(array("success" => false, "msg" => $msg)); +} + +function serveStatic(string $webRoot, string $file) { + + $path = realpath($webRoot . "/" . $file); + if (!startsWith($path, $webRoot . "/")) { + http_response_code(406); + return "Access restricted, requested file outside web root: " . htmlspecialchars($path); + } + + if (!file_exists($path) || !is_file($path) || !is_readable($path)) { + http_response_code(500); + return "Unable to read file: " . htmlspecialchars($path); + } + + $pathInfo = pathinfo($path); + + // TODO: add more file extensions here + $allowedExtension = array("html", "htm"); + $ext = $pathInfo["extension"] ?? ""; + if (!in_array($ext, $allowedExtension)) { + http_response_code(406); + return "Access restricted: Extension '" . htmlspecialchars($ext) . "' not allowed."; + } + + $size = filesize($path); + $mimeType = mime_content_type($path); + header("Content-Type: $mimeType"); // TODO: do we need to check mime type? + header("Content-Length: $size"); + header('Accept-Ranges: bytes'); + + if (strcasecmp($_SERVER["REQUEST_METHOD"], "HEAD") !== 0) { + $bufferSize = 1024*16; + $handle = fopen($path, "rb"); + if($handle === false) { + http_response_code(500); + return "Unable to read file: " . htmlspecialchars($path); + } + + $offset = 0; + $length = $size; + + if (isset($_SERVER['HTTP_RANGE'])) { + $partialContent = true; + preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches); + $offset = intval($matches[1]); + $length = intval($matches[2]) - $offset; + http_response_code(206); + header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $size); + } + + if ($offset > 0) { + fseek($handle, $offset); + } + + $bytesRead = 0; + while (!feof($handle) && $bytesRead < $length) { + $chunkSize = min($length - $bytesRead, $bufferSize); + echo fread($handle, $chunkSize); + } + + fclose($handle); + } + + return ""; +} diff --git a/core/datetime.php b/core/datetime.php index 8b2ec23..377c1bc 100644 --- a/core/datetime.php +++ b/core/datetime.php @@ -58,8 +58,8 @@ function formatPeriod($d1, $d2) { function now() { return new DateTime(); } function getTime($d = NULL) { return dateFunction('H:i', $d); } -function getHour($d = NULL){ return dateFunction('H', $d); } -function getMinute($d = NULL){ return dateFunction('i', $d); } +function getHour($d = NULL) { return dateFunction('H', $d); } +function getMinute($d = NULL) { return dateFunction('i', $d); } function getYear($d = NULL) { return intval(dateFunction('Y', $d)); } function getMonth($d = NULL) { return intval(dateFunction('n', $d)); } function getDay($d = NULL) { return intval(dateFunction('d', $d)); } @@ -118,4 +118,55 @@ function dateFunction($str, $d = NULL) { return $d->format($str); } -?> +function getPeriodString($d) { + + try { + $d = new DateTime($d); + } catch(Exception $e) { + return L("Unknown"); + } + + $diff = datetimeDiff(new DateTime(), $d); + $diff = abs($diff); + + + if ($diff < 60) { + $str = "< %d min"; + $diff = 1; + } else if($diff < 60*60) { + $diff = intval($diff / 60); + $str = "%d min."; + } else if($diff < 60*60*24) { + $diff = intval($diff / (60*60)); + $str = "%d h."; + } else { + $diff = intval($diff / (60*60*24)); + $str = "%d d."; + } + + return L(sprintf($str, $diff)); +} + +function formatDateTime($d) { + $format = L("Y/m/d H:i:s"); + return apply_format($d, $format); +} + +function formatTime($d) { + $format = L("H:i:s"); + return apply_format($d, $format); +} + +function formatDate($d) { + $format = L("Y/m/d"); + return apply_format($d, $format); +} + +function apply_format($d, $fmt) { + try { + $dt = new DateTime($d); + return $dt->format($fmt); + } catch(Exception $e) { + return L("Unknown"); + } +} diff --git a/css/admin.css b/css/account.css similarity index 51% rename from css/admin.css rename to css/account.css index 64713f6..aed3902 100644 --- a/css/admin.css +++ b/css/account.css @@ -1,15 +1,10 @@ -.loginContainer { - border-radius: 5px; - width: 600px; - position: relative; -} - .loginForm { padding: 25px; border: 1px solid #bbb; border-radius: 5px; background-color: #bbb; color: black; + position: relative; } .loginForm input { @@ -27,27 +22,9 @@ vertical-align: bottom; } -.main-header { - border-bottom: 1px solid #dee2e6; -} - -.main-sidebar { - background-color: #343a40; - width: 250px; - position: fixed; - top: 0; - left: 0; - height: 100vh; - z-index: 999; - color: #fff; - transition: all 0.3s; -} - -.content-wrapper { - background: #f4f6f9; -} - -.main-wrapper { - display: flex; - width: 100%; -} +.flags { + right: 3px; + bottom: -22px; + background-color: darkgray; + border-radius: 5px; +} \ No newline at end of file diff --git a/css/bootstrap.min.css b/css/bootstrap.min.css index 92e3fe8..7d2a868 100644 --- a/css/bootstrap.min.css +++ b/css/bootstrap.min.css @@ -1,7 +1,7 @@ /*! - * Bootstrap v4.3.1 (https://getbootstrap.com/) - * Copyright 2011-2019 The Bootstrap Authors - * Copyright 2011-2019 Twitter, Inc. + * Bootstrap v4.5.0 (https://getbootstrap.com/) + * Copyright 2011-2020 The Bootstrap Authors + * Copyright 2011-2020 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item{display:-ms-flexbox;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;-ms-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} /*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/css/style.css b/css/style.css index 24f9e65..f02197a 100644 --- a/css/style.css +++ b/css/style.css @@ -2,138 +2,11 @@ html, body { height: 100%; margin: 0; padding: 0; - font-family: "Verdana"; + font-family: "Verdana", serif; color: #555; } -.syntaxhighlighter { margin-left: -25px; } - -.no-underline:hover { text-decoration: none; } -.background { background-color: #fefefe; } -.background-gray { background-color: gray; } -.background-lightgray { background-color: lightgray; } -.background-light-blue { background-color: rgb(88, 160, 224); } -.background-jumbotron { background-color: #e9ecef; } - -/* TEXT COLOR */ -.text-white, .text-white:hover { color: white } -.text-black, .text-black:hover { color: black; } -.text-gray, .text-gray:hover { color: gray; } -.text-default, .text-default:hover { color: #555; } -.text-title, .text-title:hover { color: #333; text-decoration: underline; font-weight: bold; font-size: 1.7em; } -.text-red, .text-red:hover { color: red; } -.text-inherit, .text-inherit:hover { color: inherit; } -.text-green-attr { color: #50a14f; } -.text-cyan { color: cyan; } -.highlight { background-color: yellow; } - -.code-box { background-color: #e0e0e0; - color: rgba(0,0,0,.87); - border-radius: 1px; - padding: 2px; - border: 1px solid #ddd; - word-break: break-all; - overflow-wrap: anywhere; -} - - -/* TEXT TRANSFORM */ -.underline { text-decoration: underline; } -.italic { font-style: italic; } -.bold { font-weight: bold; } -.text-super { vertical-align : super; } - -/* TEXT SIZE */ -.text-xxs { font-size: 13px; } -.text-xs { font-size: 15px; } -.text-m { font-size: 20px; } -.text-xl { font-size: 22px; } -.text-xxl { font-size: 25px; } -.text-xxxl { font-size: 30px; } - -/* MARGINS */ -.margin-xs { margin: 5px; } -.margin-m { margin: 10px; } -.margin-xl { margin: 15px; } -.margin-top-xxs { margin-top: 10px; } -.margin-top-xs { margin-top: 15px; } -.margin-top-m { margin-top: 20px; } -.margin-top-xl { margin-top: 25px; } -.margin-top-xxl { margin-top: 30px; } -.margin-top-xxxl { margin-top: 40px; } -.margin-bottom-xxs { margin-bottom: 10px; } -.margin-bottom-xs { margin-bottom: 15px; } -.margin-bottom-m { margin-bottom: 20px; } -.margin-bottom-xl { margin-bottom: 25px; } -.margin-bottom-xxl { margin-bottom: 30px; } -.margin-bottom-xxxl { margin-bottom: 40px; } -.margin-left-xxs { margin-left: 10px; } -.margin-left-xs { margin-left: 15px; } -.margin-left-m { margin-left: 20px; } -.margin-left-xl { margin-left: 25px; } -.margin-left-xxl { margin-left: 30px; } -.margin-center { margin-left: auto; margin-right: auto; } -.margin-none { margin: 0 !important; } - -/* PADDINGS */ -.padding-xs { padding: 5px; } -.padding-m { padding: 10px; } -.padding-xl { padding: 15px; } -.padding-xxl { padding: 20px; } -.padding-xxxl { padding: 25px; } -.padding-top-m { padding-top: 10px; } -.padding-top-xl { padding-top: 15px; } -.padding-top-xxl { padding-top: 20px; } -.padding-bottom-m { padding-bottom: 10px; } -.padding-bottom-xxl { padding-bottom: 20px; } -.padding-left-xs { padding-left: 5px; } -.padding-left-xl { padding-left: 15px; } -.padding-none { padding: 0; } - -/* BORDER */ -.round { border-radius: 50%; } -.border-white { border-color: white; } -.border-grey { border-color: grey; } -.border-xxs { border: 1px solid; } -.border-xs { border: 2px solid; } -.border-m { border: 3px solid; } -.border-bottom-xss { border-bottom: 1px solid; } -.round-border-xs { border-radius: 2px; } -.round-border { border-radius: 5px; } -.round-border-xl { border-radius: 10px; } -.self-center { align-self: center; } - -/* LAYOUT */ -.fullheight { height: 100%; } -.fullwidth { width: 100%; } -.max-full-width { max-width: 100%; } -.restwidth { width: auto; } -.width-75 { width: 75%; } -.relative { position: relative; } -.absolute { position: absolute; } - -.bottom { bottom: 0; } -.hide-overflow { overflow: hidden; } .hidden { display: none; } -.clickable { cursor: pointer; } - -/* SCROLLBARS */ -.vertical-scroll { overflow-y: auto; } - -.mainContent { - margin-top: 40px; - margin-bottom: 100px; -} - -.sidebar-toggle { - position: absolute; - top: 15px; - left: 15px; -} - -.sidebar-toggle:hover { - background-color: rgb(69, 150, 240); -} .external::after { font-family: "Font Awesome 5 Free"; @@ -141,86 +14,4 @@ html, body { content: " \f35d"; font-size: 10px; vertical-align: super; -} - -.navigation { - padding-left: 15px; -} - -.fullscreen-container { - margin: auto; - position: fixed; - top: 0; left: 0; bottom: 0; right: 0; - z-index: 9999; - overflow-y: auto; - overflow-x: hidden; - background-color: transparent; - background-color: #000; - background-color: rgba(0, 0, 0, 0.6); -} - -.thumbnail { - border: 3px solid gray; -} - -.fullscreen-container > img { - position: fixed; - z-index: 1000; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - max-width: 95%; - max-height: 95%; - height: auto; - border: 3px solid gray; -} - -.closeButton { - position: fixed; - top: 30px; - right: 30px; - padding: 5px 9px 5px 9px; - background-color: #ddd; - border: 1px solid black; - font-size: 20px; - font-weight: bold; - cursor: pointer; -} - -.flags { - padding: 3px; -} - -.flags > a { - margin: 5px; -} - -span > .hljs { - display: inline; - padding: .2em; -} - -.inlineLink:hover { - text-decoration: underline; - cursor: pointer; -} - -.bash { - background-color: #2E3436; - padding: 3px 5px 3px 5px; -} - -.bash > span { - color: #D3D7CF; - overflow-wrap: anywhere; - word-break: break-all; -} - -.bash > span:first-child { - display: inline; - color: #4E9A06; -} - -.file-icon { - padding-bottom: 5px; -} +} \ No newline at end of file diff --git a/docs/LICENSE_ADMINLTE b/docs/LICENSE_ADMINLTE new file mode 100644 index 0000000..0ed9554 --- /dev/null +++ b/docs/LICENSE_ADMINLTE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014-2018 almasaeed2010 + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/img/icons/logo.png b/img/icons/logo.png new file mode 100644 index 0000000..1de1b6f Binary files /dev/null and b/img/icons/logo.png differ diff --git a/index.php b/index.php index 6cf38d3..6ab723a 100644 --- a/index.php +++ b/index.php @@ -1,31 +1,34 @@ false, "msg" => $msg)); -} - -spl_autoload_extensions(".php"); -spl_autoload_register(function($class) { - $full_path = getClassPath($class); - if(file_exists($full_path)) - include_once $full_path; - else - include_once getClassPath($class, false); -}); +use Api\Request; +use Configuration\Configuration; +use Documents\Document404; +use Elements\Document; include_once 'core/core.php'; include_once 'core/datetime.php'; include_once 'core/constants.php'; -$config = new Configuration\Configuration(); -$installation = (!$config->load()); +if (!is_readable(getClassPath(Configuration::class))) { + header("Content-Type: application/json"); + die(json_encode(array( "success" => false, "msg" => "Configuration directory is not readable, check permissions before proceeding." ))); +} + +spl_autoload_extensions(".php"); +spl_autoload_register(function($class) { + $full_path = getClassPath($class, true); + if(file_exists($full_path)) { + include_once $full_path; + } else { + include_once getClassPath($class, false); + } +}); + +$config = new Configuration(); $user = new Objects\User($config); +$sql = $user->getSQL(); +$settings = $config->getSettings(); +$installation = !$sql || ($sql->isConnected() && !$settings->isInstalled()); if(isset($_GET["api"]) && is_string($_GET["api"])) { header("Content-Type: application/json"); @@ -40,28 +43,54 @@ if(isset($_GET["api"]) && is_string($_GET["api"])) { header("400 Bad Request"); $response = createError("Invalid Method"); } else { - $apiFunction = implode("\\", array_map('ucfirst', explode("/", $apiFunction))); - if($apiFunction[0] !== "\\") $apiFunction = "\\$apiFunction"; - $class = "\\Api$apiFunction"; - $file = getClassPath($class); - if(!file_exists($file)) { - header("404 Not Found"); - $response = createError("Not found"); - } else if(!is_subclass_of($class, \Api\Request::class)) { - header("400 Bad Request"); - $response = createError("Inalid Method"); + $apiFunction = array_filter(array_map('ucfirst', explode("/", $apiFunction))); + if (count($apiFunction) > 1) { + $parentClass = "\\Api\\" . reset($apiFunction) . "API"; + $apiClass = "\\Api\\" . implode("\\", $apiFunction); } else { - $request = new $class($user, true); - $success = $request->execute(); - $msg = $request->getLastError(); - $response = $request->getJsonResult(); + $apiClass = "\\Api\\" . implode("\\", $apiFunction); + $parentClass = $apiClass; + } + + try { + $file = getClassPath($parentClass); + if(!file_exists($file) || !class_exists($parentClass) || !class_exists($apiClass)) { + header("404 Not Found"); + $response = createError("Not found"); + } else { + $parentClass = new ReflectionClass($parentClass); + $apiClass = new ReflectionClass($apiClass); + if(!$apiClass->isSubclassOf(Request::class) || !$apiClass->isInstantiable()) { + header("400 Bad Request"); + $response = createError("Invalid Method"); + } else { + $request = $apiClass->newInstanceArgs(array($user, true)); + $success = $request->execute(); + $msg = $request->getLastError(); + $response = $request->getJsonResult(); + } + } + } catch (ReflectionException $e) { + $response = createError("Error instantiating class: $e"); } } } } else { - $documentName = $_GET["site"]; + $requestedUri = $_GET["site"] ?? $_SERVER["REQUEST_URI"]; + if (($index = strpos($requestedUri, "?")) !== false) { + $requestedUri = substr($requestedUri, 0, $index); + } + + if (($index = strpos($requestedUri, "#")) !== false) { + $requestedUri = substr($requestedUri, 0, $index); + } + + if (startsWith($requestedUri, "/")) { + $requestedUri = substr($requestedUri, 1); + } + if ($installation) { - if ($documentName !== "" && $documentName !== "index.php") { + if ($requestedUri !== "" && $requestedUri !== "index.php") { $response = "Redirecting to /"; header("Location: /"); } else { @@ -69,26 +98,50 @@ if(isset($_GET["api"]) && is_string($_GET["api"])) { $response = $document->getCode(); } } else { - if(empty($documentName) || strcasecmp($documentName, "install") === 0) { - $documentName = "home"; - } else if(!preg_match("/[a-zA-Z]+(\/[a-zA-Z]+)*/", $documentName)) { - $documentName = "Document404"; - } - $documentName = strtoupper($documentName[0]) . substr($documentName, 1); - $documentName = str_replace("/", "\\", $documentName); - $class = "\\Documents\\$documentName"; - $file = getClassPath($class); - if(!file_exists($file) || !is_subclass_of($class, \Elements\Document::class)) { - $document = new \Documents\Document404($user); + $req = new \Api\Routes\Find($user); + $success = $req->execute(array("request" => $requestedUri)); + $response = ""; + if (!$success) { + http_response_code(500); + $response = "Unable to find route: " . $req->getLastError(); } else { - $document = new $class($user); + $route = $req->getResult()["route"]; + if (is_null($route)) { + $response = (new Document404($user))->getCode(); + } else { + $target = trim(explode("\n", $route["target"])[0]); + switch ($route["action"]) { + case "redirect_temporary": + http_response_code(307); + header("Location: $target"); + break; + case "redirect_permanently": + http_response_code(308); + header("Location: $target"); + break; + case "static": + $currentDir = dirname(__FILE__); + $response = serveStatic($currentDir, $target); + break; + case "dynamic": + $view = $route["extra"] ?? ""; + $file = getClassPath($target); + if(!file_exists($file) || !is_subclass_of($target, Document::class)) { + $document = new Document404($user, $view); + } else { + $document = new $target($user, $view); + } + + $response = $document->getCode(); + break; + } + } } - $response = $document->getCode(); + $user->processVisit(); } } $user->sendCookies(); -die($response); -?> +die($response); \ No newline at end of file diff --git a/js/account.js b/js/account.js new file mode 100644 index 0000000..830dae8 --- /dev/null +++ b/js/account.js @@ -0,0 +1,175 @@ +$(document).ready(function () { + + function showAlert(type, msg) { + let alert = $("#alertMessage"); + alert.text(msg); + alert.attr("class", "mt-2 alert alert-" + type); + alert.show(); + } + + function hideAlert() { + $("#alertMessage").hide(); + } + + function submitForm(btn, method, params, onSuccess) { + let textBefore = btn.text(); + btn.prop("disabled", true); + btn.html("Submitting… ") + jsCore.apiCall(method, params, (res) => { + btn.prop("disabled", false); + btn.text(textBefore); + if (!res.success) { + showAlert("danger", res.msg); + } else { + onSuccess(); + } + }); + } + + // Login + $("#btnLogin").click(function() { + const username = $("#username").val(); + const password = $("#password").val(); + const createdDiv = $("#accountCreated"); + const stayLoggedIn = $("#stayLoggedIn").is(":checked"); + const btn = $(this); + + hideAlert(); + btn.prop("disabled", true); + btn.html("Logging in… "); + jsCore.apiCall("/user/login", {"username": username, "password": password, "stayLoggedIn": stayLoggedIn }, function(res) { + if (res.success) { + document.location.reload(); + } else { + btn.html("Login"); + btn.prop("disabled", false); + $("#password").val(""); + createdDiv.hide(); + showAlert(res.msg); + } + }); + }); + + $("#btnRegister").click(function (e) { + e.preventDefault(); + e.stopPropagation(); + + let btn = $(this); + let username = $("#username").val().trim(); + let email = $("#email").val().trim(); + let password = $("#password").val(); + let confirmPassword = $("#confirmPassword").val(); + let siteKey = $("#siteKey").val().trim(); + + if (username === '' || email === '' || password === '' || confirmPassword === '') { + showAlert("danger", "Please fill out every field."); + } else if(password !== confirmPassword) { + showAlert("danger", "Your passwords did not match."); + } else { + let params = { username: username, email: email, password: password, confirmPassword: confirmPassword }; + if (typeof grecaptcha !== 'undefined') { + grecaptcha.ready(function() { + grecaptcha.execute(siteKey, {action: 'register'}).then(function(captcha) { + params["captcha"] = captcha; + submitForm(btn, "user/register", params, () => { + showAlert("success", "Account successfully created, check your emails."); + $("input").val(""); + }); + }); + }); + } else { + submitForm(btn, "user/register", params, () => { + showAlert("success", "Account successfully created, check your emails."); + $("input").val(""); + }); + } + } + }); + + $("#btnAcceptInvite").click(function (e) { + e.preventDefault(); + e.stopPropagation(); + + let btn = $(this); + let token = $("#token").val(); + let password = $("#password").val(); + let confirmPassword = $("#confirmPassword").val(); + + if(password !== confirmPassword) { + showAlert("danger", "Your passwords did not match."); + } else { + let textBefore = btn.text(); + let params = { token: token, password: password, confirmPassword: confirmPassword }; + + btn.prop("disabled", true); + btn.html("Submitting… ") + jsCore.apiCall("user/acceptInvite", params, (res) => { + btn.prop("disabled", false); + btn.text(textBefore); + if (!res.success) { + showAlert("danger", res.msg); + } else { + showAlert("success", "Account successfully created. You may now login."); + $("input").val(""); + } + }); + } + }); + + $("#btnRequestPasswordReset").click(function (e) { + e.preventDefault(); + e.stopPropagation(); + + let btn = $(this); + let email = $("#email").val(); + let siteKey = $("#siteKey").val().trim(); + + let params = { email: email }; + if (typeof grecaptcha !== 'undefined') { + grecaptcha.ready(function() { + grecaptcha.execute(siteKey, {action: 'resetPassword'}).then(function(captcha) { + params["captcha"] = captcha; + submitForm(btn, "user/requestPasswordReset", params, () => { + showAlert("success", "If the e-mail address exists and is linked to a account, you will receive a password reset token."); + $("input").val(""); + }); + }); + }); + } else { + submitForm(btn, "user/requestPasswordReset", params, () => { + showAlert("success", "If the e-mail address exists and is linked to a account, you will receive a password reset token."); + $("input").val(""); + }); + } + }); + + $("#btnResetPassword").click(function (e) { + e.preventDefault(); + e.stopPropagation(); + + let btn = $(this); + let token = $("#token").val(); + let password = $("#password").val(); + let confirmPassword = $("#confirmPassword").val(); + + if(password !== confirmPassword) { + showAlert("danger", "Your passwords did not match."); + } else { + let textBefore = btn.text(); + let params = { token: token, password: password, confirmPassword: confirmPassword }; + + btn.prop("disabled", true); + btn.html("Submitting… ") + jsCore.apiCall("user/resetPassword", params, (res) => { + btn.prop("disabled", false); + btn.text(textBefore); + if (!res.success) { + showAlert("danger", res.msg); + } else { + showAlert("success", "Your password was successfully changed. You may now login."); + $("input").val(""); + } + }); + } + }); +}); \ No newline at end of file diff --git a/js/admin.js b/js/admin.js deleted file mode 100644 index 48e339d..0000000 --- a/js/admin.js +++ /dev/null @@ -1,25 +0,0 @@ -$(document).ready(function() { - $("#username").keypress(function(e) { if(e.which == 13) $("#password").focus(); }); - $("#password").keypress(function(e) { if(e.which == 13) $("#btnLogin").click(); }); - $("#btnLogin").click(function() { - var username = $("#username").val(); - var password = $("#password").val(); - var errorDiv = $("#loginError"); - var createdDiv = $("#accountCreated"); - var btn = $(this); - - errorDiv.hide(); - btn.prop("disabled", true); - btn.html("Logging in… "); - jsCore.apiCall("user/login", {"username": username, "password": password}, function(data) { - window.location.reload(); - }, function(err) { - btn.html("Login"); - btn.prop("disabled", false); - $("#password").val(""); - createdDiv.hide(); - errorDiv.html(err); - errorDiv.show(); - }); - }); -}); diff --git a/js/admin.min.js b/js/admin.min.js new file mode 100644 index 0000000..61de199 --- /dev/null +++ b/js/admin.min.js @@ -0,0 +1,354 @@ +!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1022)}([function(e,t,r){"use strict";e.exports=r(504)},function(e,t,r){e.exports=r(716)()},function(e,t,r){(function(e){e.exports=function(){"use strict";var t,n;function o(){return t.apply(null,arguments)}function a(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function i(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function s(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function c(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;var t;for(t in e)if(s(e,t))return!1;return!0}function l(e){return void 0===e}function d(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function u(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function p(e,t){var r,n=[];for(r=0;r>>0;for(t=0;t0)for(r=0;r=0?r?"+":"":"-")+Math.pow(10,Math.max(0,o)).toString().substr(1)+n}o.suppressDeprecationWarnings=!1,o.deprecationHandler=null,S=Object.keys?Object.keys:function(e){var t,r=[];for(t in e)s(e,t)&&r.push(t);return r};var j=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,A=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,N={},P={};function I(e,t,r,n){var o=n;"string"==typeof n&&(o=function(){return this[n]()}),e&&(P[e]=o),t&&(P[t[0]]=function(){return T(o.apply(this,arguments),t[1],t[2])}),r&&(P[r]=function(){return this.localeData().ordinal(o.apply(this,arguments),e)})}function Y(e,t){return e.isValid()?(t=z(t,e.localeData()),N[t]=N[t]||function(e){var t,r,n,o=e.match(j);for(t=0,r=o.length;t=0&&A.test(e);)e=e.replace(A,n),A.lastIndex=0,r-=1;return e}var F={};function R(e,t){var r=e.toLowerCase();F[r]=F[r+"s"]=F[t]=e}function H(e){return"string"==typeof e?F[e]||F[e.toLowerCase()]:void 0}function B(e){var t,r,n={};for(r in e)s(e,r)&&(t=H(r))&&(n[t]=e[r]);return n}var U={};function W(e,t){U[e]=t}function q(e){return e%4==0&&e%100!=0||e%400==0}function V(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function K(e){var t=+e,r=0;return 0!==t&&isFinite(t)&&(r=V(t)),r}function G(e,t){return function(r){return null!=r?(J(this,e,r),o.updateOffset(this,t),this):Q(this,e)}}function Q(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function J(e,t,r){e.isValid()&&!isNaN(r)&&("FullYear"===t&&q(e.year())&&1===e.month()&&29===e.date()?(r=K(r),e._d["set"+(e._isUTC?"UTC":"")+t](r,e.month(),ke(r,e.month()))):e._d["set"+(e._isUTC?"UTC":"")+t](r))}var Z,$=/\d/,X=/\d\d/,ee=/\d{3}/,te=/\d{4}/,re=/[+-]?\d{6}/,ne=/\d\d?/,oe=/\d\d\d\d?/,ae=/\d\d\d\d\d\d?/,ie=/\d{1,3}/,se=/\d{1,4}/,ce=/[+-]?\d{1,6}/,le=/\d+/,de=/[+-]?\d+/,ue=/Z|[+-]\d\d:?\d\d/gi,pe=/Z|[+-]\d\d(?::?\d\d)?/gi,fe=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;function me(e,t,r){Z[e]=D(t)?t:function(e,n){return e&&r?r:t}}function he(e,t){return s(Z,e)?Z[e](t._strict,t._locale):new RegExp(ge(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,(function(e,t,r,n,o){return t||r||n||o}))))}function ge(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}Z={};var be,ve={};function ye(e,t){var r,n=t;for("string"==typeof e&&(e=[e]),d(t)&&(n=function(e,r){r[t]=K(e)}),r=0;r68?1900:2e3)};var Ae=G("FullYear",!0);function Ne(e,t,r,n,o,a,i){var s;return e<100&&e>=0?(s=new Date(e+400,t,r,n,o,a,i),isFinite(s.getFullYear())&&s.setFullYear(e)):s=new Date(e,t,r,n,o,a,i),s}function Pe(e){var t,r;return e<100&&e>=0?((r=Array.prototype.slice.call(arguments))[0]=e+400,t=new Date(Date.UTC.apply(null,r)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function Ie(e,t,r){var n=7+t-r;return-(7+Pe(e,0,n).getUTCDay()-t)%7+n-1}function Ye(e,t,r,n,o){var a,i,s=1+7*(t-1)+(7+r-n)%7+Ie(e,n,o);return s<=0?i=je(a=e-1)+s:s>je(e)?(a=e+1,i=s-je(e)):(a=e,i=s),{year:a,dayOfYear:i}}function ze(e,t,r){var n,o,a=Ie(e.year(),t,r),i=Math.floor((e.dayOfYear()-a-1)/7)+1;return i<1?n=i+Fe(o=e.year()-1,t,r):i>Fe(e.year(),t,r)?(n=i-Fe(e.year(),t,r),o=e.year()+1):(o=e.year(),n=i),{week:n,year:o}}function Fe(e,t,r){var n=Ie(e,t,r),o=Ie(e+1,t,r);return(je(e)-n+o)/7}function Re(e,t){return e.slice(t,7).concat(e.slice(0,t))}I("w",["ww",2],"wo","week"),I("W",["WW",2],"Wo","isoWeek"),R("week","w"),R("isoWeek","W"),W("week",5),W("isoWeek",5),me("w",ne),me("ww",ne,X),me("W",ne),me("WW",ne,X),_e(["w","ww","W","WW"],(function(e,t,r,n){t[n.substr(0,1)]=K(e)})),I("d",0,"do","day"),I("dd",0,0,(function(e){return this.localeData().weekdaysMin(this,e)})),I("ddd",0,0,(function(e){return this.localeData().weekdaysShort(this,e)})),I("dddd",0,0,(function(e){return this.localeData().weekdays(this,e)})),I("e",0,0,"weekday"),I("E",0,0,"isoWeekday"),R("day","d"),R("weekday","e"),R("isoWeekday","E"),W("day",11),W("weekday",11),W("isoWeekday",11),me("d",ne),me("e",ne),me("E",ne),me("dd",(function(e,t){return t.weekdaysMinRegex(e)})),me("ddd",(function(e,t){return t.weekdaysShortRegex(e)})),me("dddd",(function(e,t){return t.weekdaysRegex(e)})),_e(["dd","ddd","dddd"],(function(e,t,r,n){var o=r._locale.weekdaysParse(e,n,r._strict);null!=o?t.d=o:h(r).invalidWeekday=e})),_e(["d","e","E"],(function(e,t,r,n){t[n]=K(e)}));var He="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Be="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Ue="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),We=fe,qe=fe,Ve=fe;function Ke(e,t,r){var n,o,a,i=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],n=0;n<7;++n)a=m([2e3,1]).day(n),this._minWeekdaysParse[n]=this.weekdaysMin(a,"").toLocaleLowerCase(),this._shortWeekdaysParse[n]=this.weekdaysShort(a,"").toLocaleLowerCase(),this._weekdaysParse[n]=this.weekdays(a,"").toLocaleLowerCase();return r?"dddd"===t?-1!==(o=be.call(this._weekdaysParse,i))?o:null:"ddd"===t?-1!==(o=be.call(this._shortWeekdaysParse,i))?o:null:-1!==(o=be.call(this._minWeekdaysParse,i))?o:null:"dddd"===t?-1!==(o=be.call(this._weekdaysParse,i))||-1!==(o=be.call(this._shortWeekdaysParse,i))||-1!==(o=be.call(this._minWeekdaysParse,i))?o:null:"ddd"===t?-1!==(o=be.call(this._shortWeekdaysParse,i))||-1!==(o=be.call(this._weekdaysParse,i))||-1!==(o=be.call(this._minWeekdaysParse,i))?o:null:-1!==(o=be.call(this._minWeekdaysParse,i))||-1!==(o=be.call(this._weekdaysParse,i))||-1!==(o=be.call(this._shortWeekdaysParse,i))?o:null}function Ge(){function e(e,t){return t.length-e.length}var t,r,n,o,a,i=[],s=[],c=[],l=[];for(t=0;t<7;t++)r=m([2e3,1]).day(t),n=ge(this.weekdaysMin(r,"")),o=ge(this.weekdaysShort(r,"")),a=ge(this.weekdays(r,"")),i.push(n),s.push(o),c.push(a),l.push(n),l.push(o),l.push(a);i.sort(e),s.sort(e),c.sort(e),l.sort(e),this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function Qe(){return this.hours()%12||12}function Je(e,t){I(e,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)}))}function Ze(e,t){return t._meridiemParse}I("H",["HH",2],0,"hour"),I("h",["hh",2],0,Qe),I("k",["kk",2],0,(function(){return this.hours()||24})),I("hmm",0,0,(function(){return""+Qe.apply(this)+T(this.minutes(),2)})),I("hmmss",0,0,(function(){return""+Qe.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)})),I("Hmm",0,0,(function(){return""+this.hours()+T(this.minutes(),2)})),I("Hmmss",0,0,(function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)})),Je("a",!0),Je("A",!1),R("hour","h"),W("hour",13),me("a",Ze),me("A",Ze),me("H",ne),me("h",ne),me("k",ne),me("HH",ne,X),me("hh",ne,X),me("kk",ne,X),me("hmm",oe),me("hmmss",ae),me("Hmm",oe),me("Hmmss",ae),ye(["H","HH"],3),ye(["k","kk"],(function(e,t,r){var n=K(e);t[3]=24===n?0:n})),ye(["a","A"],(function(e,t,r){r._isPm=r._locale.isPM(e),r._meridiem=e})),ye(["h","hh"],(function(e,t,r){t[3]=K(e),h(r).bigHour=!0})),ye("hmm",(function(e,t,r){var n=e.length-2;t[3]=K(e.substr(0,n)),t[4]=K(e.substr(n)),h(r).bigHour=!0})),ye("hmmss",(function(e,t,r){var n=e.length-4,o=e.length-2;t[3]=K(e.substr(0,n)),t[4]=K(e.substr(n,2)),t[5]=K(e.substr(o)),h(r).bigHour=!0})),ye("Hmm",(function(e,t,r){var n=e.length-2;t[3]=K(e.substr(0,n)),t[4]=K(e.substr(n))})),ye("Hmmss",(function(e,t,r){var n=e.length-4,o=e.length-2;t[3]=K(e.substr(0,n)),t[4]=K(e.substr(n,2)),t[5]=K(e.substr(o))}));var $e,Xe=G("Hours",!0),et={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:xe,monthsShort:Me,week:{dow:0,doy:6},weekdays:He,weekdaysMin:Ue,weekdaysShort:Be,meridiemParse:/[ap]\.?m?\.?/i},tt={},rt={};function nt(e,t){var r,n=Math.min(e.length,t.length);for(r=0;r0;){if(n=at(o.slice(0,t).join("-")))return n;if(r&&r.length>=t&&nt(o,r)>=t-1)break;t--}a++}return $e}(e)}function lt(e){var t,r=e._a;return r&&-2===h(e).overflow&&(t=r[1]<0||r[1]>11?1:r[2]<1||r[2]>ke(r[0],r[1])?2:r[3]<0||r[3]>24||24===r[3]&&(0!==r[4]||0!==r[5]||0!==r[6])?3:r[4]<0||r[4]>59?4:r[5]<0||r[5]>59?5:r[6]<0||r[6]>999?6:-1,h(e)._overflowDayOfYear&&(t<0||t>2)&&(t=2),h(e)._overflowWeeks&&-1===t&&(t=7),h(e)._overflowWeekday&&-1===t&&(t=8),h(e).overflow=t),e}var dt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ut=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,pt=/Z|[+-]\d\d(?::?\d\d)?/,ft=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/],["YYYYMM",/\d{6}/,!1],["YYYY",/\d{4}/,!1]],mt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],ht=/^\/?Date\((-?\d+)/i,gt=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,bt={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function vt(e){var t,r,n,o,a,i,s=e._i,c=dt.exec(s)||ut.exec(s);if(c){for(h(e).iso=!0,t=0,r=ft.length;t7)&&(c=!0)):(a=e._locale._week.dow,i=e._locale._week.doy,l=ze(Et(),a,i),r=wt(t.gg,e._a[0],l.year),n=wt(t.w,l.week),null!=t.d?((o=t.d)<0||o>6)&&(c=!0):null!=t.e?(o=t.e+a,(t.e<0||t.e>6)&&(c=!0)):o=a),n<1||n>Fe(r,a,i)?h(e)._overflowWeeks=!0:null!=c?h(e)._overflowWeekday=!0:(s=Ye(r,n,o,a,i),e._a[0]=s.year,e._dayOfYear=s.dayOfYear)}(e),null!=e._dayOfYear&&(i=wt(e._a[0],n[0]),(e._dayOfYear>je(i)||0===e._dayOfYear)&&(h(e)._overflowDayOfYear=!0),r=Pe(i,0,e._dayOfYear),e._a[1]=r.getUTCMonth(),e._a[2]=r.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=s[t]=n[t];for(;t<7;t++)e._a[t]=s[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[3]&&0===e._a[4]&&0===e._a[5]&&0===e._a[6]&&(e._nextDay=!0,e._a[3]=0),e._d=(e._useUTC?Pe:Ne).apply(null,s),a=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[3]=24),e._w&&void 0!==e._w.d&&e._w.d!==a&&(h(e).weekdayMismatch=!0)}}function xt(e){if(e._f!==o.ISO_8601)if(e._f!==o.RFC_2822){e._a=[],h(e).empty=!0;var t,r,n,a,i,s,c=""+e._i,l=c.length,d=0;for(n=z(e._f,e._locale).match(j)||[],t=0;t0&&h(e).unusedInput.push(i),c=c.slice(c.indexOf(r)+r.length),d+=r.length),P[a]?(r?h(e).empty=!1:h(e).unusedTokens.push(a),we(a,r,e)):e._strict&&!r&&h(e).unusedTokens.push(a);h(e).charsLeftOver=l-d,c.length>0&&h(e).unusedInput.push(c),e._a[3]<=12&&!0===h(e).bigHour&&e._a[3]>0&&(h(e).bigHour=void 0),h(e).parsedDateParts=e._a.slice(0),h(e).meridiem=e._meridiem,e._a[3]=function(e,t,r){var n;return null==r?t:null!=e.meridiemHour?e.meridiemHour(t,r):null!=e.isPM?((n=e.isPM(r))&&t<12&&(t+=12),n||12!==t||(t=0),t):t}(e._locale,e._a[3],e._meridiem),null!==(s=h(e).era)&&(e._a[0]=e._locale.erasConvertYear(s,e._a[0])),kt(e),lt(e)}else _t(e);else vt(e)}function Mt(e){var t=e._i,r=e._f;return e._locale=e._locale||ct(e._l),null===t||void 0===r&&""===t?b({nullInput:!0}):("string"==typeof t&&(e._i=t=e._locale.preparse(t)),k(t)?new w(lt(t)):(u(t)?e._d=t:a(r)?function(e){var t,r,n,o,a,i,s=!1;if(0===e._f.length)return h(e).invalidFormat=!0,void(e._d=new Date(NaN));for(o=0;othis?this:e:b()}));function Ct(e,t){var r,n;if(1===t.length&&a(t[0])&&(t=t[0]),!t.length)return Et();for(r=t[0],n=1;n=0?new Date(e+400,t,r)-126227808e5:new Date(e,t,r).valueOf()}function or(e,t,r){return e<100&&e>=0?Date.UTC(e+400,t,r)-126227808e5:Date.UTC(e,t,r)}function ar(e,t){return t.erasAbbrRegex(e)}function ir(){var e,t,r=[],n=[],o=[],a=[],i=this.eras();for(e=0,t=i.length;e(a=Fe(e,n,o))&&(t=a),lr.call(this,e,t,r,n,o))}function lr(e,t,r,n,o){var a=Ye(e,t,r,n,o),i=Pe(a.year,0,a.dayOfYear);return this.year(i.getUTCFullYear()),this.month(i.getUTCMonth()),this.date(i.getUTCDate()),this}I("N",0,0,"eraAbbr"),I("NN",0,0,"eraAbbr"),I("NNN",0,0,"eraAbbr"),I("NNNN",0,0,"eraName"),I("NNNNN",0,0,"eraNarrow"),I("y",["y",1],"yo","eraYear"),I("y",["yy",2],0,"eraYear"),I("y",["yyy",3],0,"eraYear"),I("y",["yyyy",4],0,"eraYear"),me("N",ar),me("NN",ar),me("NNN",ar),me("NNNN",(function(e,t){return t.erasNameRegex(e)})),me("NNNNN",(function(e,t){return t.erasNarrowRegex(e)})),ye(["N","NN","NNN","NNNN","NNNNN"],(function(e,t,r,n){var o=r._locale.erasParse(e,n,r._strict);o?h(r).era=o:h(r).invalidEra=e})),me("y",le),me("yy",le),me("yyy",le),me("yyyy",le),me("yo",(function(e,t){return t._eraYearOrdinalRegex||le})),ye(["y","yy","yyy","yyyy"],0),ye(["yo"],(function(e,t,r,n){var o;r._locale._eraYearOrdinalRegex&&(o=e.match(r._locale._eraYearOrdinalRegex)),r._locale.eraYearOrdinalParse?t[0]=r._locale.eraYearOrdinalParse(e,o):t[0]=parseInt(e,10)})),I(0,["gg",2],0,(function(){return this.weekYear()%100})),I(0,["GG",2],0,(function(){return this.isoWeekYear()%100})),sr("gggg","weekYear"),sr("ggggg","weekYear"),sr("GGGG","isoWeekYear"),sr("GGGGG","isoWeekYear"),R("weekYear","gg"),R("isoWeekYear","GG"),W("weekYear",1),W("isoWeekYear",1),me("G",de),me("g",de),me("GG",ne,X),me("gg",ne,X),me("GGGG",se,te),me("gggg",se,te),me("GGGGG",ce,re),me("ggggg",ce,re),_e(["gggg","ggggg","GGGG","GGGGG"],(function(e,t,r,n){t[n.substr(0,2)]=K(e)})),_e(["gg","GG"],(function(e,t,r,n){t[n]=o.parseTwoDigitYear(e)})),I("Q",0,"Qo","quarter"),R("quarter","Q"),W("quarter",7),me("Q",$),ye("Q",(function(e,t){t[1]=3*(K(e)-1)})),I("D",["DD",2],"Do","date"),R("date","D"),W("date",9),me("D",ne),me("DD",ne,X),me("Do",(function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient})),ye(["D","DD"],2),ye("Do",(function(e,t){t[2]=K(e.match(ne)[0])}));var dr=G("Date",!0);I("DDD",["DDDD",3],"DDDo","dayOfYear"),R("dayOfYear","DDD"),W("dayOfYear",4),me("DDD",ie),me("DDDD",ee),ye(["DDD","DDDD"],(function(e,t,r){r._dayOfYear=K(e)})),I("m",["mm",2],0,"minute"),R("minute","m"),W("minute",14),me("m",ne),me("mm",ne,X),ye(["m","mm"],4);var ur=G("Minutes",!1);I("s",["ss",2],0,"second"),R("second","s"),W("second",15),me("s",ne),me("ss",ne,X),ye(["s","ss"],5);var pr,fr,mr=G("Seconds",!1);for(I("S",0,0,(function(){return~~(this.millisecond()/100)})),I(0,["SS",2],0,(function(){return~~(this.millisecond()/10)})),I(0,["SSS",3],0,"millisecond"),I(0,["SSSS",4],0,(function(){return 10*this.millisecond()})),I(0,["SSSSS",5],0,(function(){return 100*this.millisecond()})),I(0,["SSSSSS",6],0,(function(){return 1e3*this.millisecond()})),I(0,["SSSSSSS",7],0,(function(){return 1e4*this.millisecond()})),I(0,["SSSSSSSS",8],0,(function(){return 1e5*this.millisecond()})),I(0,["SSSSSSSSS",9],0,(function(){return 1e6*this.millisecond()})),R("millisecond","ms"),W("millisecond",16),me("S",ie,$),me("SS",ie,X),me("SSS",ie,ee),pr="SSSS";pr.length<=9;pr+="S")me(pr,le);function hr(e,t){t[6]=K(1e3*("0."+e))}for(pr="S";pr.length<=9;pr+="S")ye(pr,hr);fr=G("Milliseconds",!1),I("z",0,0,"zoneAbbr"),I("zz",0,0,"zoneName");var gr=w.prototype;function br(e){return e}gr.add=Kt,gr.calendar=function(e,t){1===arguments.length&&(Jt(arguments[0])?(e=arguments[0],t=void 0):Zt(arguments[0])&&(t=arguments[0],e=void 0));var r=e||Et(),n=Yt(r,this).startOf("day"),a=o.calendarFormat(this,n)||"sameElse",i=t&&(D(t[a])?t[a].call(this,r):t[a]);return this.format(i||this.localeData().calendar(a,this,Et(r)))},gr.clone=function(){return new w(this)},gr.diff=function(e,t,r){var n,o,a;if(!this.isValid())return NaN;if(!(n=Yt(e,this)).isValid())return NaN;switch(o=6e4*(n.utcOffset()-this.utcOffset()),t=H(t)){case"year":a=$t(this,n)/12;break;case"month":a=$t(this,n);break;case"quarter":a=$t(this,n)/3;break;case"second":a=(this-n)/1e3;break;case"minute":a=(this-n)/6e4;break;case"hour":a=(this-n)/36e5;break;case"day":a=(this-n-o)/864e5;break;case"week":a=(this-n-o)/6048e5;break;default:a=this-n}return r?a:V(a)},gr.endOf=function(e){var t,r;if(void 0===(e=H(e))||"millisecond"===e||!this.isValid())return this;switch(r=this._isUTC?or:nr,e){case"year":t=r(this.year()+1,0,1)-1;break;case"quarter":t=r(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":t=r(this.year(),this.month()+1,1)-1;break;case"week":t=r(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":t=r(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":t=r(this.year(),this.month(),this.date()+1)-1;break;case"hour":t=this._d.valueOf(),t+=36e5-rr(t+(this._isUTC?0:6e4*this.utcOffset()),36e5)-1;break;case"minute":t=this._d.valueOf(),t+=6e4-rr(t,6e4)-1;break;case"second":t=this._d.valueOf(),t+=1e3-rr(t,1e3)-1}return this._d.setTime(t),o.updateOffset(this,!0),this},gr.format=function(e){e||(e=this.isUtc()?o.defaultFormatUtc:o.defaultFormat);var t=Y(this,e);return this.localeData().postformat(t)},gr.from=function(e,t){return this.isValid()&&(k(e)&&e.isValid()||Et(e).isValid())?Bt({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},gr.fromNow=function(e){return this.from(Et(),e)},gr.to=function(e,t){return this.isValid()&&(k(e)&&e.isValid()||Et(e).isValid())?Bt({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},gr.toNow=function(e){return this.to(Et(),e)},gr.get=function(e){return D(this[e=H(e)])?this[e]():this},gr.invalidAt=function(){return h(this).overflow},gr.isAfter=function(e,t){var r=k(e)?e:Et(e);return!(!this.isValid()||!r.isValid())&&("millisecond"===(t=H(t)||"millisecond")?this.valueOf()>r.valueOf():r.valueOf()9999?Y(r,t?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):D(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",Y(r,"Z")):Y(r,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},gr.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e,t,r,n="moment",o="";return this.isLocal()||(n=0===this.utcOffset()?"moment.utc":"moment.parseZone",o="Z"),e="["+n+'("]',t=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",r=o+'[")]',this.format(e+t+"-MM-DD[T]HH:mm:ss.SSS"+r)},"undefined"!=typeof Symbol&&null!=Symbol.for&&(gr[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"}),gr.toJSON=function(){return this.isValid()?this.toISOString():null},gr.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},gr.unix=function(){return Math.floor(this.valueOf()/1e3)},gr.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},gr.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},gr.eraName=function(){var e,t,r,n=this.localeData().eras();for(e=0,t=n.length;ethis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},gr.isLocal=function(){return!!this.isValid()&&!this._isUTC},gr.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},gr.isUtc=Ft,gr.isUTC=Ft,gr.zoneAbbr=function(){return this._isUTC?"UTC":""},gr.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},gr.dates=M("dates accessor is deprecated. Use date instead.",dr),gr.months=M("months accessor is deprecated. Use month instead",Oe),gr.years=M("years accessor is deprecated. Use year instead",Ae),gr.zone=M("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()})),gr.isDSTShifted=M("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e,t={};return _(t,this),(t=Mt(t))._a?(e=t._isUTC?m(t._a):Et(t._a),this._isDSTShifted=this.isValid()&&function(e,t,r){var n,o=Math.min(e.length,t.length),a=Math.abs(e.length-t.length),i=0;for(n=0;n0):this._isDSTShifted=!1,this._isDSTShifted}));var vr=O.prototype;function yr(e,t,r,n){var o=ct(),a=m().set(n,t);return o[r](a,e)}function _r(e,t,r){if(d(e)&&(t=e,e=void 0),e=e||"",null!=t)return yr(e,t,r,"month");var n,o=[];for(n=0;n<12;n++)o[n]=yr(e,n,r,"month");return o}function wr(e,t,r,n){"boolean"==typeof e?(d(t)&&(r=t,t=void 0),t=t||""):(r=t=e,e=!1,d(t)&&(r=t,t=void 0),t=t||"");var o,a=ct(),i=e?a._week.dow:0,s=[];if(null!=r)return yr(t,(r+i)%7,n,"day");for(o=0;o<7;o++)s[o]=yr(t,(o+i)%7,n,"day");return s}vr.calendar=function(e,t,r){var n=this._calendar[e]||this._calendar.sameElse;return D(n)?n.call(t,r):n},vr.longDateFormat=function(e){var t=this._longDateFormat[e],r=this._longDateFormat[e.toUpperCase()];return t||!r?t:(this._longDateFormat[e]=r.match(j).map((function(e){return"MMMM"===e||"MM"===e||"DD"===e||"dddd"===e?e.slice(1):e})).join(""),this._longDateFormat[e])},vr.invalidDate=function(){return this._invalidDate},vr.ordinal=function(e){return this._ordinal.replace("%d",e)},vr.preparse=br,vr.postformat=br,vr.relativeTime=function(e,t,r,n){var o=this._relativeTime[r];return D(o)?o(e,t,r,n):o.replace(/%d/i,e)},vr.pastFuture=function(e,t){var r=this._relativeTime[e>0?"future":"past"];return D(r)?r(t):r.replace(/%s/i,t)},vr.set=function(e){var t,r;for(r in e)s(e,r)&&(D(t=e[r])?this[r]=t:this["_"+r]=t);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},vr.eras=function(e,t){var r,n,a,i=this._eras||ct("en")._eras;for(r=0,n=i.length;r=0)return c[n]},vr.erasConvertYear=function(e,t){var r=e.since<=e.until?1:-1;return void 0===t?o(e.since).year():o(e.since).year()+(t-e.offset)*r},vr.erasAbbrRegex=function(e){return s(this,"_erasAbbrRegex")||ir.call(this),e?this._erasAbbrRegex:this._erasRegex},vr.erasNameRegex=function(e){return s(this,"_erasNameRegex")||ir.call(this),e?this._erasNameRegex:this._erasRegex},vr.erasNarrowRegex=function(e){return s(this,"_erasNarrowRegex")||ir.call(this),e?this._erasNarrowRegex:this._erasRegex},vr.months=function(e,t){return e?a(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||Se).test(t)?"format":"standalone"][e.month()]:a(this._months)?this._months:this._months.standalone},vr.monthsShort=function(e,t){return e?a(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[Se.test(t)?"format":"standalone"][e.month()]:a(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},vr.monthsParse=function(e,t,r){var n,o,a;if(this._monthsParseExact)return De.call(this,e,t,r);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),n=0;n<12;n++){if(o=m([2e3,n]),r&&!this._longMonthsParse[n]&&(this._longMonthsParse[n]=new RegExp("^"+this.months(o,"").replace(".","")+"$","i"),this._shortMonthsParse[n]=new RegExp("^"+this.monthsShort(o,"").replace(".","")+"$","i")),r||this._monthsParse[n]||(a="^"+this.months(o,"")+"|^"+this.monthsShort(o,""),this._monthsParse[n]=new RegExp(a.replace(".",""),"i")),r&&"MMMM"===t&&this._longMonthsParse[n].test(e))return n;if(r&&"MMM"===t&&this._shortMonthsParse[n].test(e))return n;if(!r&&this._monthsParse[n].test(e))return n}},vr.monthsRegex=function(e){return this._monthsParseExact?(s(this,"_monthsRegex")||Te.call(this),e?this._monthsStrictRegex:this._monthsRegex):(s(this,"_monthsRegex")||(this._monthsRegex=Le),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},vr.monthsShortRegex=function(e){return this._monthsParseExact?(s(this,"_monthsRegex")||Te.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(s(this,"_monthsShortRegex")||(this._monthsShortRegex=Ee),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},vr.week=function(e){return ze(e,this._week.dow,this._week.doy).week},vr.firstDayOfYear=function(){return this._week.doy},vr.firstDayOfWeek=function(){return this._week.dow},vr.weekdays=function(e,t){var r=a(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?"format":"standalone"];return!0===e?Re(r,this._week.dow):e?r[e.day()]:r},vr.weekdaysMin=function(e){return!0===e?Re(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin},vr.weekdaysShort=function(e){return!0===e?Re(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort},vr.weekdaysParse=function(e,t,r){var n,o,a;if(this._weekdaysParseExact)return Ke.call(this,e,t,r);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),n=0;n<7;n++){if(o=m([2e3,1]).day(n),r&&!this._fullWeekdaysParse[n]&&(this._fullWeekdaysParse[n]=new RegExp("^"+this.weekdays(o,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[n]=new RegExp("^"+this.weekdaysShort(o,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[n]=new RegExp("^"+this.weekdaysMin(o,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[n]||(a="^"+this.weekdays(o,"")+"|^"+this.weekdaysShort(o,"")+"|^"+this.weekdaysMin(o,""),this._weekdaysParse[n]=new RegExp(a.replace(".",""),"i")),r&&"dddd"===t&&this._fullWeekdaysParse[n].test(e))return n;if(r&&"ddd"===t&&this._shortWeekdaysParse[n].test(e))return n;if(r&&"dd"===t&&this._minWeekdaysParse[n].test(e))return n;if(!r&&this._weekdaysParse[n].test(e))return n}},vr.weekdaysRegex=function(e){return this._weekdaysParseExact?(s(this,"_weekdaysRegex")||Ge.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(s(this,"_weekdaysRegex")||(this._weekdaysRegex=We),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},vr.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(s(this,"_weekdaysRegex")||Ge.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(s(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=qe),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},vr.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(s(this,"_weekdaysRegex")||Ge.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(s(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Ve),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},vr.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},vr.meridiem=function(e,t,r){return e>11?r?"pm":"PM":r?"am":"AM"},it("en",{eras:[{since:"0001-01-01",until:1/0,offset:1,name:"Anno Domini",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"Before Christ",narrow:"BC",abbr:"BC"}],dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===K(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")}}),o.lang=M("moment.lang is deprecated. Use moment.locale instead.",it),o.langData=M("moment.langData is deprecated. Use moment.localeData instead.",ct);var kr=Math.abs;function xr(e,t,r,n){var o=Bt(t,r);return e._milliseconds+=n*o._milliseconds,e._days+=n*o._days,e._months+=n*o._months,e._bubble()}function Mr(e){return e<0?Math.floor(e):Math.ceil(e)}function Sr(e){return 4800*e/146097}function Er(e){return 146097*e/4800}function Lr(e){return function(){return this.as(e)}}var Dr=Lr("ms"),Cr=Lr("s"),Or=Lr("m"),Tr=Lr("h"),jr=Lr("d"),Ar=Lr("w"),Nr=Lr("M"),Pr=Lr("Q"),Ir=Lr("y");function Yr(e){return function(){return this.isValid()?this._data[e]:NaN}}var zr=Yr("milliseconds"),Fr=Yr("seconds"),Rr=Yr("minutes"),Hr=Yr("hours"),Br=Yr("days"),Ur=Yr("months"),Wr=Yr("years"),qr=Math.round,Vr={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function Kr(e,t,r,n,o){return o.relativeTime(t||1,!!r,e,n)}var Gr=Math.abs;function Qr(e){return(e>0)-(e<0)||+e}function Jr(){if(!this.isValid())return this.localeData().invalidDate();var e,t,r,n,o,a,i,s,c=Gr(this._milliseconds)/1e3,l=Gr(this._days),d=Gr(this._months),u=this.asSeconds();return u?(e=V(c/60),t=V(e/60),c%=60,e%=60,r=V(d/12),d%=12,n=c?c.toFixed(3).replace(/\.?0+$/,""):"",o=u<0?"-":"",a=Qr(this._months)!==Qr(u)?"-":"",i=Qr(this._days)!==Qr(u)?"-":"",s=Qr(this._milliseconds)!==Qr(u)?"-":"",o+"P"+(r?a+r+"Y":"")+(d?a+d+"M":"")+(l?i+l+"D":"")+(t||e||c?"T":"")+(t?s+t+"H":"")+(e?s+e+"M":"")+(c?s+n+"S":"")):"P0D"}var Zr=Tt.prototype;return Zr.isValid=function(){return this._isValid},Zr.abs=function(){var e=this._data;return this._milliseconds=kr(this._milliseconds),this._days=kr(this._days),this._months=kr(this._months),e.milliseconds=kr(e.milliseconds),e.seconds=kr(e.seconds),e.minutes=kr(e.minutes),e.hours=kr(e.hours),e.months=kr(e.months),e.years=kr(e.years),this},Zr.add=function(e,t){return xr(this,e,t,1)},Zr.subtract=function(e,t){return xr(this,e,t,-1)},Zr.as=function(e){if(!this.isValid())return NaN;var t,r,n=this._milliseconds;if("month"===(e=H(e))||"quarter"===e||"year"===e)switch(t=this._days+n/864e5,r=this._months+Sr(t),e){case"month":return r;case"quarter":return r/3;case"year":return r/12}else switch(t=this._days+Math.round(Er(this._months)),e){case"week":return t/7+n/6048e5;case"day":return t+n/864e5;case"hour":return 24*t+n/36e5;case"minute":return 1440*t+n/6e4;case"second":return 86400*t+n/1e3;case"millisecond":return Math.floor(864e5*t)+n;default:throw new Error("Unknown unit "+e)}},Zr.asMilliseconds=Dr,Zr.asSeconds=Cr,Zr.asMinutes=Or,Zr.asHours=Tr,Zr.asDays=jr,Zr.asWeeks=Ar,Zr.asMonths=Nr,Zr.asQuarters=Pr,Zr.asYears=Ir,Zr.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*K(this._months/12):NaN},Zr._bubble=function(){var e,t,r,n,o,a=this._milliseconds,i=this._days,s=this._months,c=this._data;return a>=0&&i>=0&&s>=0||a<=0&&i<=0&&s<=0||(a+=864e5*Mr(Er(s)+i),i=0,s=0),c.milliseconds=a%1e3,e=V(a/1e3),c.seconds=e%60,t=V(e/60),c.minutes=t%60,r=V(t/60),c.hours=r%24,i+=V(r/24),o=V(Sr(i)),s+=o,i-=Mr(Er(o)),n=V(s/12),s%=12,c.days=i,c.months=s,c.years=n,this},Zr.clone=function(){return Bt(this)},Zr.get=function(e){return e=H(e),this.isValid()?this[e+"s"]():NaN},Zr.milliseconds=zr,Zr.seconds=Fr,Zr.minutes=Rr,Zr.hours=Hr,Zr.days=Br,Zr.weeks=function(){return V(this.days()/7)},Zr.months=Ur,Zr.years=Wr,Zr.humanize=function(e,t){if(!this.isValid())return this.localeData().invalidDate();var r,n,o=!1,a=Vr;return"object"==typeof e&&(t=e,e=!1),"boolean"==typeof e&&(o=e),"object"==typeof t&&(a=Object.assign({},Vr,t),null!=t.s&&null==t.ss&&(a.ss=t.s-1)),r=this.localeData(),n=function(e,t,r,n){var o=Bt(e).abs(),a=qr(o.as("s")),i=qr(o.as("m")),s=qr(o.as("h")),c=qr(o.as("d")),l=qr(o.as("M")),d=qr(o.as("w")),u=qr(o.as("y")),p=a<=r.ss&&["s",a]||a0,p[4]=n,Kr.apply(null,p)}(this,!o,a,r),o&&(n=r.pastFuture(+this,n)),r.postformat(n)},Zr.toISOString=Jr,Zr.toString=Jr,Zr.toJSON=Jr,Zr.locale=Xt,Zr.localeData=tr,Zr.toIsoString=M("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Jr),Zr.lang=er,I("X",0,0,"unix"),I("x",0,0,"valueOf"),me("x",de),me("X",/[+-]?\d+(\.\d{1,3})?/),ye("X",(function(e,t,r){r._d=new Date(1e3*parseFloat(e))})),ye("x",(function(e,t,r){r._d=new Date(K(e))})), +//! moment.js +o.version="2.26.0",t=Et,o.fn=gr,o.min=function(){var e=[].slice.call(arguments,0);return Ct("isBefore",e)},o.max=function(){var e=[].slice.call(arguments,0);return Ct("isAfter",e)},o.now=function(){return Date.now?Date.now():+new Date},o.utc=m,o.unix=function(e){return Et(1e3*e)},o.months=function(e,t){return _r(e,t,"months")},o.isDate=u,o.locale=it,o.invalid=b,o.duration=Bt,o.isMoment=k,o.weekdays=function(e,t,r){return wr(e,t,r,"weekdays")},o.parseZone=function(){return Et.apply(null,arguments).parseZone()},o.localeData=ct,o.isDuration=jt,o.monthsShort=function(e,t){return _r(e,t,"monthsShort")},o.weekdaysMin=function(e,t,r){return wr(e,t,r,"weekdaysMin")},o.defineLocale=st,o.updateLocale=function(e,t){if(null!=t){var r,n,o=et;null!=tt[e]&&null!=tt[e].parentLocale?tt[e].set(C(tt[e]._config,t)):(null!=(n=at(e))&&(o=n._config),t=C(o,t),null==n&&(t.abbr=e),(r=new O(t)).parentLocale=tt[e],tt[e]=r),it(e)}else null!=tt[e]&&(null!=tt[e].parentLocale?(tt[e]=tt[e].parentLocale,e===it()&&it(e)):null!=tt[e]&&delete tt[e]);return tt[e]},o.locales=function(){return S(tt)},o.weekdaysShort=function(e,t,r){return wr(e,t,r,"weekdaysShort")},o.normalizeUnits=H,o.relativeTimeRounding=function(e){return void 0===e?qr:"function"==typeof e&&(qr=e,!0)},o.relativeTimeThreshold=function(e,t){return void 0!==Vr[e]&&(void 0===t?Vr[e]:(Vr[e]=t,"s"===e&&(Vr.ss=t-1),!0))},o.calendarFormat=function(e,t){var r=e.diff(t,"days",!0);return r<-6?"sameElse":r<-1?"lastWeek":r<0?"lastDay":r<1?"sameDay":r<2?"nextDay":r<7?"nextWeek":"sameElse"},o.prototype=gr,o.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},o}()}).call(this,r(199)(e))},function(e,t,r){"use strict";function n(e,t){if(t.length1?"s":"")+" required, but only "+t.length+" present")}r.d(t,"a",(function(){return n}))},function(e,t,r){var n=r(8),o=r(42),a=r(26),i=r(27),s=r(43),c=function(e,t,r){var l,d,u,p,f=e&c.F,m=e&c.G,h=e&c.S,g=e&c.P,b=e&c.B,v=m?n:h?n[t]||(n[t]={}):(n[t]||{}).prototype,y=m?o:o[t]||(o[t]={}),_=y.prototype||(y.prototype={});for(l in m&&(r=t),r)u=((d=!f&&v&&void 0!==v[l])?v:r)[l],p=b&&d?s(u,n):g&&"function"==typeof u?s(Function.call,u):u,v&&i(v,l,u,e&c.U),y[l]!=u&&a(y,l,p),g&&_[l]!=u&&(_[l]=u)};n.core=o,c.F=1,c.G=2,c.S=4,c.P=8,c.B=16,c.W=32,c.U=64,c.R=128,e.exports=c},function(e,t,r){"use strict";r.r(t),r.d(t,"default",(function(){return o}));var n=r(3);function o(e){Object(n.a)(1,arguments);var t=Object.prototype.toString.call(e);return e instanceof Date||"object"==typeof e&&"[object Date]"===t?new Date(e.getTime()):"number"==typeof e||"[object Number]"===t?new Date(e):("string"!=typeof e&&"[object String]"!==t||"undefined"==typeof console||(console.warn("Starting with v2.0.0-beta.1 date-fns doesn't accept strings as arguments. Please use `parseISO` to parse strings. See: https://git.io/fjule"),console.warn((new Error).stack)),new Date(NaN))}},function(e,t,r){"use strict";function n(e){if(null===e||!0===e||!1===e)return NaN;var t=Number(e);return isNaN(t)?t:t<0?Math.ceil(t):Math.floor(t)}r.d(t,"a",(function(){return n}))},function(e,t,r){var n=r(13);e.exports=function(e){if(!n(e))throw TypeError(e+" is not an object!");return e}},function(e,t){var r=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(e,t,r){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function r(e){return i(e)?e:B(e)}function n(e){return s(e)?e:U(e)}function o(e){return c(e)?e:W(e)}function a(e){return i(e)&&!l(e)?e:q(e)}function i(e){return!(!e||!e[u])}function s(e){return!(!e||!e[p])}function c(e){return!(!e||!e[f])}function l(e){return s(e)||c(e)}function d(e){return!(!e||!e[m])}t(n,r),t(o,r),t(a,r),r.isIterable=i,r.isKeyed=s,r.isIndexed=c,r.isAssociative=l,r.isOrdered=d,r.Keyed=n,r.Indexed=o,r.Set=a;var u="@@__IMMUTABLE_ITERABLE__@@",p="@@__IMMUTABLE_KEYED__@@",f="@@__IMMUTABLE_INDEXED__@@",m="@@__IMMUTABLE_ORDERED__@@",h={},g={value:!1},b={value:!1};function v(e){return e.value=!1,e}function y(e){e&&(e.value=!0)}function _(){}function w(e,t){t=t||0;for(var r=Math.max(0,e.length-t),n=new Array(r),o=0;o>>0;if(""+r!==t||4294967295===r)return NaN;t=r}return t<0?k(e)+t:t}function M(){return!0}function S(e,t,r){return(0===e||void 0!==r&&e<=-r)&&(void 0===t||void 0!==r&&t>=r)}function E(e,t){return D(e,t,0)}function L(e,t){return D(e,t,t)}function D(e,t,r){return void 0===e?r:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var C,O,T,j="function"==typeof Symbol&&Symbol.iterator,A=j||"@@iterator";function N(e){this.next=e}function P(e,t,r,n){var o=0===e?t:1===e?r:[t,r];return n?n.value=o:n={value:o,done:!1},n}function I(){return{value:void 0,done:!0}}function Y(e){return!!R(e)}function z(e){return e&&"function"==typeof e.next}function F(e){var t=R(e);return t&&t.call(e)}function R(e){var t=e&&(j&&e[j]||e["@@iterator"]);if("function"==typeof t)return t}function H(e){return e&&"number"==typeof e.length}function B(e){return null==e?Z():i(e)?e.toSeq():function(e){var t=ee(e)||"object"==typeof e&&new K(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}(e)}function U(e){return null==e?Z().toKeyedSeq():i(e)?s(e)?e.toSeq():e.fromEntrySeq():$(e)}function W(e){return null==e?Z():i(e)?s(e)?e.entrySeq():e.toIndexedSeq():X(e)}function q(e){return(null==e?Z():i(e)?s(e)?e.entrySeq():e:X(e)).toSetSeq()}function V(e){this._array=e,this.size=e.length}function K(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function G(e){this._iterable=e,this.size=e.length||e.size}function Q(e){this._iterator=e,this._iteratorCache=[]}function J(e){return!(!e||!e["@@__IMMUTABLE_SEQ__@@"])}function Z(){return C||(C=new V([]))}function $(e){var t=Array.isArray(e)?new V(e).fromEntrySeq():z(e)?new Q(e).fromEntrySeq():Y(e)?new G(e).fromEntrySeq():"object"==typeof e?new K(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function X(e){var t=ee(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function ee(e){return H(e)?new V(e):z(e)?new Q(e):Y(e)?new G(e):void 0}function te(e,t,r,n){var o=e._cache;if(o){for(var a=o.length-1,i=0;i<=a;i++){var s=o[r?a-i:i];if(!1===t(s[1],n?s[0]:i,e))return i+1}return i}return e.__iterateUncached(t,r)}function re(e,t,r,n){var o=e._cache;if(o){var a=o.length-1,i=0;return new N((function(){var e=o[r?a-i:i];return i++>a?{value:void 0,done:!0}:P(t,n?e[0]:i-1,e[1])}))}return e.__iteratorUncached(t,r)}function ne(e,t){return t?function e(t,r,n,o){return Array.isArray(r)?t.call(o,n,W(r).map((function(n,o){return e(t,n,o,r)}))):ae(r)?t.call(o,n,U(r).map((function(n,o){return e(t,n,o,r)}))):r}(t,e,"",{"":e}):oe(e)}function oe(e){return Array.isArray(e)?W(e).map(oe).toList():ae(e)?U(e).map(oe).toMap():e}function ae(e){return e&&(e.constructor===Object||void 0===e.constructor)}function ie(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function se(e,t){if(e===t)return!0;if(!i(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||s(e)!==s(t)||c(e)!==c(t)||d(e)!==d(t))return!1;if(0===e.size&&0===t.size)return!0;var r=!l(e);if(d(e)){var n=e.entries();return t.every((function(e,t){var o=n.next().value;return o&&ie(o[1],e)&&(r||ie(o[0],t))}))&&n.next().done}var o=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{o=!0;var a=e;e=t,t=a}var u=!0,p=t.__iterate((function(t,n){if(r?!e.has(t):o?!ie(t,e.get(n,h)):!ie(e.get(n,h),t))return u=!1,!1}));return u&&e.size===p}function ce(e,t){if(!(this instanceof ce))return new ce(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(O)return O;O=this}}function le(e,t){if(!e)throw new Error(t)}function de(e,t,r){if(!(this instanceof de))return new de(e,t,r);if(le(0!==r,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),r=void 0===r?1:Math.abs(r),tn?{value:void 0,done:!0}:P(e,o,r[t?n-o++:o++])}))},t(K,U),K.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},K.prototype.has=function(e){return this._object.hasOwnProperty(e)},K.prototype.__iterate=function(e,t){for(var r=this._object,n=this._keys,o=n.length-1,a=0;a<=o;a++){var i=n[t?o-a:a];if(!1===e(r[i],i,this))return a+1}return a},K.prototype.__iterator=function(e,t){var r=this._object,n=this._keys,o=n.length-1,a=0;return new N((function(){var i=n[t?o-a:a];return a++>o?{value:void 0,done:!0}:P(e,i,r[i])}))},K.prototype[m]=!0,t(G,W),G.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var r=F(this._iterable),n=0;if(z(r))for(var o;!(o=r.next()).done&&!1!==e(o.value,n++,this););return n},G.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var r=F(this._iterable);if(!z(r))return new N(I);var n=0;return new N((function(){var t=r.next();return t.done?t:P(e,n++,t.value)}))},t(Q,W),Q.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var r,n=this._iterator,o=this._iteratorCache,a=0;a=n.length){var t=r.next();if(t.done)return t;n[o]=t.value}return P(e,o,n[o++])}))},t(ce,W),ce.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},ce.prototype.get=function(e,t){return this.has(e)?this._value:t},ce.prototype.includes=function(e){return ie(this._value,e)},ce.prototype.slice=function(e,t){var r=this.size;return S(e,t,r)?this:new ce(this._value,L(t,r)-E(e,r))},ce.prototype.reverse=function(){return this},ce.prototype.indexOf=function(e){return ie(this._value,e)?0:-1},ce.prototype.lastIndexOf=function(e){return ie(this._value,e)?this.size:-1},ce.prototype.__iterate=function(e,t){for(var r=0;r1?" by "+this._step:"")+" ]"},de.prototype.get=function(e,t){return this.has(e)?this._start+x(this,e)*this._step:t},de.prototype.includes=function(e){var t=(e-this._start)/this._step;return t>=0&&t=0&&rr?{value:void 0,done:!0}:P(e,a++,i)}))},de.prototype.equals=function(e){return e instanceof de?this._start===e._start&&this._end===e._end&&this._step===e._step:se(this,e)},t(ue,r),t(pe,ue),t(fe,ue),t(me,ue),ue.Keyed=pe,ue.Indexed=fe,ue.Set=me;var he="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var r=65535&(e|=0),n=65535&(t|=0);return r*n+((e>>>16)*n+r*(t>>>16)<<16>>>0)|0};function ge(e){return e>>>1&1073741824|3221225471&e}function be(e){if(!1===e||null==e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null==e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){var r=0|e;for(r!==e&&(r^=4294967295*e);e>4294967295;)r^=e/=4294967295;return ge(r)}if("string"===t)return e.length>Se?function(e){var t=De[e];return void 0===t&&(t=ve(e),Le===Ee&&(Le=0,De={}),Le++,De[e]=t),t}(e):ve(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return function(e){var t;if(ke&&void 0!==(t=ye.get(e)))return t;if(void 0!==(t=e[Me]))return t;if(!we){if(void 0!==(t=e.propertyIsEnumerable&&e.propertyIsEnumerable[Me]))return t;if(void 0!==(t=function(e){if(e&&e.nodeType>0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}(e)))return t}if(t=++xe,1073741824&xe&&(xe=0),ke)ye.set(e,t);else{if(void 0!==_e&&!1===_e(e))throw new Error("Non-extensible objects are not allowed as keys.");if(we)Object.defineProperty(e,Me,{enumerable:!1,configurable:!1,writable:!1,value:t});else if(void 0!==e.propertyIsEnumerable&&e.propertyIsEnumerable===e.constructor.prototype.propertyIsEnumerable)e.propertyIsEnumerable=function(){return this.constructor.prototype.propertyIsEnumerable.apply(this,arguments)},e.propertyIsEnumerable[Me]=t;else{if(void 0===e.nodeType)throw new Error("Unable to set a non-enumerable property on object.");e[Me]=t}}return t}(e);if("function"==typeof e.toString)return ve(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function ve(e){for(var t=0,r=0;r>>r),s=31&(0===r?n:n>>>r);return new Ie(t,1<>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function et(e,t,r,n){var o=n?e:w(e);return o[t]=r,o}Ne[Ae]=!0,Ne.delete=Ne.remove,Ne.removeIn=Ne.deleteIn,Pe.prototype.get=function(e,t,r,n){for(var o=this.entries,a=0,i=o.length;a=tt)return function(e,t,r,n){e||(e=new _);for(var o=new Fe(e,be(r),[r,n]),a=0;a>>e)),a=this.bitmap;return 0==(a&o)?n:this.nodes[Xe(a&o-1)].get(e+5,t,r,n)},Ie.prototype.update=function(e,t,r,n,o,a,i){void 0===r&&(r=be(n));var s=31&(0===t?r:r>>>t),c=1<=rt)return function(e,t,r,n,o){for(var a=0,i=new Array(32),s=0;0!==r;s++,r>>>=1)i[s]=1&r?t[a++]:void 0;return i[n]=o,new Ye(e,a+1,i)}(e,p,l,s,m);if(d&&!m&&2===p.length&&Ke(p[1^u]))return p[1^u];if(d&&m&&1===p.length&&Ke(m))return m;var g=e&&e===this.ownerID,b=d?m?l:l^c:l|c,v=d?m?et(p,u,m,g):function(e,t,r){var n=e.length-1;if(r&&t===n)return e.pop(),e;for(var o=new Array(n),a=0,i=0;i>>e),a=this.nodes[o];return a?a.get(e+5,t,r,n):n},Ye.prototype.update=function(e,t,r,n,o,a,i){void 0===r&&(r=be(n));var s=31&(0===t?r:r>>>t),c=o===h,l=this.nodes,d=l[s];if(c&&!d)return this;var u=Ve(d,e,t+5,r,n,o,a,i);if(u===d)return this;var p=this.count;if(d){if(!u&&--p0&&n<32?ft(0,n,5,null,new ct(r.toArray())):t.withMutations((function(e){e.setSize(n),r.forEach((function(t,r){return e.set(r,t)}))})))}function at(e){return!(!e||!e[it])}t(ot,fe),ot.of=function(){return this(arguments)},ot.prototype.toString=function(){return this.__toString("List [","]")},ot.prototype.get=function(e,t){if((e=x(this,e))>=0&&e=e.size||t<0)return e.withMutations((function(e){t<0?vt(e,t).set(0,r):vt(e,0,t+1).set(t,r)}));t+=e._origin;var n=e._tail,o=e._root,a=v(b);return t>=_t(e._capacity)?n=ht(n,e.__ownerID,0,t,r,a):o=ht(o,e.__ownerID,e._level,t,r,a),a.value?e.__ownerID?(e._root=o,e._tail=n,e.__hash=void 0,e.__altered=!0,e):ft(e._origin,e._capacity,e._level,o,n):e}(this,e,t)},ot.prototype.remove=function(e){return this.has(e)?0===e?this.shift():e===this.size-1?this.pop():this.splice(e,1):this},ot.prototype.insert=function(e,t){return this.splice(e,0,t)},ot.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=this._origin=this._capacity=0,this._level=5,this._root=this._tail=null,this.__hash=void 0,this.__altered=!0,this):mt()},ot.prototype.push=function(){var e=arguments,t=this.size;return this.withMutations((function(r){vt(r,0,t+e.length);for(var n=0;n>>t&31;if(n>=this.array.length)return new ct([],e);var o,a=0===n;if(t>0){var i=this.array[n];if((o=i&&i.removeBefore(e,t-5,r))===i&&a)return this}if(a&&!o)return this;var s=gt(this,e);if(!a)for(var c=0;c>>t&31;if(o>=this.array.length)return this;if(t>0){var a=this.array[o];if((n=a&&a.removeAfter(e,t-5,r))===a&&o===this.array.length-1)return this}var i=gt(this,e);return i.array.splice(o+1),n&&(i.array[o]=n),i};var lt,dt,ut={};function pt(e,t){var r=e._origin,n=e._capacity,o=_t(n),a=e._tail;return i(e._root,e._level,0);function i(e,s,c){return 0===s?function(e,i){var s=i===o?a&&a.array:e&&e.array,c=i>r?0:r-i,l=n-i;return l>32&&(l=32),function(){if(c===l)return ut;var e=t?--l:c++;return s&&s[e]}}(e,c):function(e,o,a){var s,c=e&&e.array,l=a>r?0:r-a>>o,d=1+(n-a>>o);return d>32&&(d=32),function(){for(;;){if(s){var e=s();if(e!==ut)return e;s=null}if(l===d)return ut;var r=t?--d:l++;s=i(c&&c[r],o-5,a+(r<>>r&31,c=e&&s0){var l=e&&e.array[s],d=ht(l,t,r-5,n,o,a);return d===l?e:((i=gt(e,t)).array[s]=d,i)}return c&&e.array[s]===o?e:(y(a),i=gt(e,t),void 0===o&&s===i.array.length-1?i.array.pop():i.array[s]=o,i)}function gt(e,t){return t&&e&&t===e.ownerID?e:new ct(e?e.array.slice():[],t)}function bt(e,t){if(t>=_t(e._capacity))return e._tail;if(t<1<0;)r=r.array[t>>>n&31],n-=5;return r}}function vt(e,t,r){void 0!==t&&(t|=0),void 0!==r&&(r|=0);var n=e.__ownerID||new _,o=e._origin,a=e._capacity,i=o+t,s=void 0===r?a:r<0?a+r:o+r;if(i===o&&s===a)return e;if(i>=s)return e.clear();for(var c=e._level,l=e._root,d=0;i+d<0;)l=new ct(l&&l.array.length?[void 0,l]:[],n),d+=1<<(c+=5);d&&(i+=d,o+=d,s+=d,a+=d);for(var u=_t(a),p=_t(s);p>=1<u?new ct([],n):f;if(f&&p>u&&i5;g-=5){var b=u>>>g&31;h=h.array[b]=gt(h.array[b],n)}h.array[u>>>5&31]=f}if(s=p)i-=p,s-=p,c=5,l=null,m=m&&m.removeBefore(n,0,i);else if(i>o||p>>c&31;if(v!==p>>>c&31)break;v&&(d+=(1<o&&(l=l.removeBefore(n,c,i-d)),l&&pa&&(a=l.size),i(c)||(l=l.map((function(e){return ne(e)}))),n.push(l)}return a>e.size&&(e=e.setSize(a)),$e(e,t,n)}function _t(e){return e<32?0:e-1>>>5<<5}function wt(e){return null==e?Mt():kt(e)?e:Mt().withMutations((function(t){var r=n(e);Ce(r.size),r.forEach((function(e,r){return t.set(r,e)}))}))}function kt(e){return Te(e)&&d(e)}function xt(e,t,r,n){var o=Object.create(wt.prototype);return o.size=e?e.size:0,o._map=e,o._list=t,o.__ownerID=r,o.__hash=n,o}function Mt(){return dt||(dt=xt(We(),mt()))}function St(e,t,r){var n,o,a=e._map,i=e._list,s=a.get(t),c=void 0!==s;if(r===h){if(!c)return e;i.size>=32&&i.size>=2*a.size?(n=(o=i.filter((function(e,t){return void 0!==e&&s!==t}))).toKeyedSeq().map((function(e){return e[0]})).flip().toMap(),e.__ownerID&&(n.__ownerID=o.__ownerID=e.__ownerID)):(n=a.remove(t),o=s===i.size-1?i.pop():i.set(s,void 0))}else if(c){if(r===i.get(s)[1])return e;n=a,o=i.set(s,[t,r])}else n=a.set(t,i.size),o=i.set(i.size,[t,r]);return e.__ownerID?(e.size=n.size,e._map=n,e._list=o,e.__hash=void 0,e):xt(n,o)}function Et(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Lt(e){this._iter=e,this.size=e.size}function Dt(e){this._iter=e,this.size=e.size}function Ct(e){this._iter=e,this.size=e.size}function Ot(e){var t=Vt(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=Kt,t.__iterateUncached=function(t,r){var n=this;return e.__iterate((function(e,r){return!1!==t(r,e,n)}),r)},t.__iteratorUncached=function(t,r){if(2===t){var n=e.__iterator(t,r);return new N((function(){var e=n.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e}))}return e.__iterator(1===t?0:1,r)},t}function Tt(e,t,r){var n=Vt(e);return n.size=e.size,n.has=function(t){return e.has(t)},n.get=function(n,o){var a=e.get(n,h);return a===h?o:t.call(r,a,n,e)},n.__iterateUncached=function(n,o){var a=this;return e.__iterate((function(e,o,i){return!1!==n(t.call(r,e,o,i),o,a)}),o)},n.__iteratorUncached=function(n,o){var a=e.__iterator(2,o);return new N((function(){var o=a.next();if(o.done)return o;var i=o.value,s=i[0];return P(n,s,t.call(r,i[1],s,e),o)}))},n}function jt(e,t){var r=Vt(e);return r._iter=e,r.size=e.size,r.reverse=function(){return e},e.flip&&(r.flip=function(){var t=Ot(e);return t.reverse=function(){return e.flip()},t}),r.get=function(r,n){return e.get(t?r:-1-r,n)},r.has=function(r){return e.has(t?r:-1-r)},r.includes=function(t){return e.includes(t)},r.cacheResult=Kt,r.__iterate=function(t,r){var n=this;return e.__iterate((function(e,r){return t(e,r,n)}),!r)},r.__iterator=function(t,r){return e.__iterator(t,!r)},r}function At(e,t,r,n){var o=Vt(e);return n&&(o.has=function(n){var o=e.get(n,h);return o!==h&&!!t.call(r,o,n,e)},o.get=function(n,o){var a=e.get(n,h);return a!==h&&t.call(r,a,n,e)?a:o}),o.__iterateUncached=function(o,a){var i=this,s=0;return e.__iterate((function(e,a,c){if(t.call(r,e,a,c))return s++,o(e,n?a:s-1,i)}),a),s},o.__iteratorUncached=function(o,a){var i=e.__iterator(2,a),s=0;return new N((function(){for(;;){var a=i.next();if(a.done)return a;var c=a.value,l=c[0],d=c[1];if(t.call(r,d,l,e))return P(o,n?l:s++,d,a)}}))},o}function Nt(e,t,r,n){var o=e.size;if(void 0!==t&&(t|=0),void 0!==r&&(r|=0),S(t,r,o))return e;var a=E(t,o),i=L(r,o);if(a!=a||i!=i)return Nt(e.toSeq().cacheResult(),t,r,n);var s,c=i-a;c==c&&(s=c<0?0:c);var l=Vt(e);return l.size=0===s?s:e.size&&s||void 0,!n&&J(e)&&s>=0&&(l.get=function(t,r){return(t=x(this,t))>=0&&ts)return{value:void 0,done:!0};var e=o.next();return n||1===t?e:P(t,c-1,0===t?void 0:e.value[1],e)}))},l}function Pt(e,t,r,n){var o=Vt(e);return o.__iterateUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterate(o,a);var s=!0,c=0;return e.__iterate((function(e,a,l){if(!s||!(s=t.call(r,e,a,l)))return c++,o(e,n?a:c-1,i)})),c},o.__iteratorUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterator(o,a);var s=e.__iterator(2,a),c=!0,l=0;return new N((function(){var e,a,d;do{if((e=s.next()).done)return n||1===o?e:P(o,l++,0===o?void 0:e.value[1],e);var u=e.value;a=u[0],d=u[1],c&&(c=t.call(r,d,a,i))}while(c);return 2===o?e:P(o,a,d,e)}))},o}function It(e,t){var r=s(e),o=[e].concat(t).map((function(e){return i(e)?r&&(e=n(e)):e=r?$(e):X(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var a=o[0];if(a===e||r&&s(a)||c(e)&&c(a))return a}var l=new V(o);return r?l=l.toKeyedSeq():c(e)||(l=l.toSetSeq()),(l=l.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var r=t.size;if(void 0!==r)return e+r}}),0),l}function Yt(e,t,r){var n=Vt(e);return n.__iterateUncached=function(n,o){var a=0,s=!1;return function e(c,l){var d=this;c.__iterate((function(o,c){return(!t||l0}function Ht(e,t,n){var o=Vt(e);return o.size=new V(n).map((function(e){return e.size})).min(),o.__iterate=function(e,t){for(var r,n=this.__iterator(1,t),o=0;!(r=n.next()).done&&!1!==e(r.value,o++,this););return o},o.__iteratorUncached=function(e,o){var a=n.map((function(e){return e=r(e),F(o?e.reverse():e)})),i=0,s=!1;return new N((function(){var r;return s||(r=a.map((function(e){return e.next()})),s=r.some((function(e){return e.done}))),s?{value:void 0,done:!0}:P(e,i++,t.apply(null,r.map((function(e){return e.value}))))}))},o}function Bt(e,t){return J(e)?t:e.constructor(t)}function Ut(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function Wt(e){return Ce(e.size),k(e)}function qt(e){return s(e)?n:c(e)?o:a}function Vt(e){return Object.create((s(e)?U:c(e)?W:q).prototype)}function Kt(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):B.prototype.cacheResult.call(this)}function Gt(e,t){return e>t?1:e=0;r--)t={value:arguments[r],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):_r(e,t)},hr.prototype.pushAll=function(e){if(0===(e=o(e)).size)return this;Ce(e.size);var t=this.size,r=this._head;return e.reverse().forEach((function(e){t++,r={value:e,next:r}})),this.__ownerID?(this.size=t,this._head=r,this.__hash=void 0,this.__altered=!0,this):_r(t,r)},hr.prototype.pop=function(){return this.slice(1)},hr.prototype.unshift=function(){return this.push.apply(this,arguments)},hr.prototype.unshiftAll=function(e){return this.pushAll(e)},hr.prototype.shift=function(){return this.pop.apply(this,arguments)},hr.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):wr()},hr.prototype.slice=function(e,t){if(S(e,t,this.size))return this;var r=E(e,this.size);if(L(t,this.size)!==this.size)return fe.prototype.slice.call(this,e,t);for(var n=this.size-r,o=this._head;r--;)o=o.next;return this.__ownerID?(this.size=n,this._head=o,this.__hash=void 0,this.__altered=!0,this):_r(n,o)},hr.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?_r(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},hr.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var r=0,n=this._head;n&&!1!==e(n.value,r++,this);)n=n.next;return r},hr.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var r=0,n=this._head;return new N((function(){if(n){var t=n.value;return n=n.next,P(e,r++,t)}return{value:void 0,done:!0}}))},hr.isStack=gr;var br,vr="@@__IMMUTABLE_STACK__@@",yr=hr.prototype;function _r(e,t,r,n){var o=Object.create(yr);return o.size=e,o._head=t,o.__ownerID=r,o.__hash=n,o.__altered=!1,o}function wr(){return br||(br=_r(0))}function kr(e,t){var r=function(r){e.prototype[r]=t[r]};return Object.keys(t).forEach(r),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(r),e}yr[vr]=!0,yr.withMutations=Ne.withMutations,yr.asMutable=Ne.asMutable,yr.asImmutable=Ne.asImmutable,yr.wasAltered=Ne.wasAltered,r.Iterator=N,kr(r,{toArray:function(){Ce(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate((function(t,r){e[r]=t})),e},toIndexedSeq:function(){return new Lt(this)},toJS:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJS?e.toJS():e})).__toJS()},toJSON:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e})).__toJS()},toKeyedSeq:function(){return new Et(this,!0)},toMap:function(){return Oe(this.toKeyedSeq())},toObject:function(){Ce(this.size);var e={};return this.__iterate((function(t,r){e[r]=t})),e},toOrderedMap:function(){return wt(this.toKeyedSeq())},toOrderedSet:function(){return lr(s(this)?this.valueSeq():this)},toSet:function(){return tr(s(this)?this.valueSeq():this)},toSetSeq:function(){return new Dt(this)},toSeq:function(){return c(this)?this.toIndexedSeq():s(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return hr(s(this)?this.valueSeq():this)},toList:function(){return ot(s(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){var t=e.call(arguments,0);return Bt(this,It(this,t))},includes:function(e){return this.some((function(t){return ie(t,e)}))},entries:function(){return this.__iterator(2)},every:function(e,t){Ce(this.size);var r=!0;return this.__iterate((function(n,o,a){if(!e.call(t,n,o,a))return r=!1,!1})),r},filter:function(e,t){return Bt(this,At(this,e,t,!0))},find:function(e,t,r){var n=this.findEntry(e,t);return n?n[1]:r},findEntry:function(e,t){var r;return this.__iterate((function(n,o,a){if(e.call(t,n,o,a))return r=[o,n],!1})),r},findLastEntry:function(e,t){return this.toSeq().reverse().findEntry(e,t)},forEach:function(e,t){return Ce(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){Ce(this.size),e=void 0!==e?""+e:",";var t="",r=!0;return this.__iterate((function(n){r?r=!1:t+=e,t+=null!=n?n.toString():""})),t},keys:function(){return this.__iterator(0)},map:function(e,t){return Bt(this,Tt(this,e,t))},reduce:function(e,t,r){var n,o;return Ce(this.size),arguments.length<2?o=!0:n=t,this.__iterate((function(t,a,i){o?(o=!1,n=t):n=e.call(r,n,t,a,i)})),n},reduceRight:function(e,t,r){var n=this.toKeyedSeq().reverse();return n.reduce.apply(n,arguments)},reverse:function(){return Bt(this,jt(this,!0))},slice:function(e,t){return Bt(this,Nt(this,e,t,!0))},some:function(e,t){return!this.every(Lr(e),t)},sort:function(e){return Bt(this,zt(this,e))},values:function(){return this.__iterator(1)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(e,t){return k(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return function(e,t,r){var n=Oe().asMutable();return e.__iterate((function(o,a){n.update(t.call(r,o,a,e),0,(function(e){return e+1}))})),n.asImmutable()}(this,e,t)},equals:function(e){return se(this,e)},entrySeq:function(){var e=this;if(e._cache)return new V(e._cache);var t=e.toSeq().map(Er).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(Lr(e),t)},findLast:function(e,t,r){return this.toKeyedSeq().reverse().find(e,t,r)},first:function(){return this.find(M)},flatMap:function(e,t){return Bt(this,function(e,t,r){var n=qt(e);return e.toSeq().map((function(o,a){return n(t.call(r,o,a,e))})).flatten(!0)}(this,e,t))},flatten:function(e){return Bt(this,Yt(this,e,!0))},fromEntrySeq:function(){return new Ct(this)},get:function(e,t){return this.find((function(t,r){return ie(r,e)}),void 0,t)},getIn:function(e,t){for(var r,n=this,o=Qt(e);!(r=o.next()).done;){var a=r.value;if((n=n&&n.get?n.get(a,h):h)===h)return t}return n},groupBy:function(e,t){return function(e,t,r){var n=s(e),o=(d(e)?wt():Oe()).asMutable();e.__iterate((function(a,i){o.update(t.call(r,a,i,e),(function(e){return(e=e||[]).push(n?[i,a]:a),e}))}));var a=qt(e);return o.map((function(t){return Bt(e,a(t))}))}(this,e,t)},has:function(e){return this.get(e,h)!==h},hasIn:function(e){return this.getIn(e,h)!==h},isSubset:function(e){return e="function"==typeof e.includes?e:r(e),this.every((function(t){return e.includes(t)}))},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:r(e)).isSubset(this)},keySeq:function(){return this.toSeq().map(Sr).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},max:function(e){return Ft(this,e)},maxBy:function(e,t){return Ft(this,t,e)},min:function(e){return Ft(this,e?Dr(e):Tr)},minBy:function(e,t){return Ft(this,t?Dr(t):Tr,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return Bt(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return Bt(this,Pt(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(Lr(e),t)},sortBy:function(e,t){return Bt(this,zt(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return Bt(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return Bt(this,function(e,t,r){var n=Vt(e);return n.__iterateUncached=function(n,o){var a=this;if(o)return this.cacheResult().__iterate(n,o);var i=0;return e.__iterate((function(e,o,s){return t.call(r,e,o,s)&&++i&&n(e,o,a)})),i},n.__iteratorUncached=function(n,o){var a=this;if(o)return this.cacheResult().__iterator(n,o);var i=e.__iterator(2,o),s=!0;return new N((function(){if(!s)return{value:void 0,done:!0};var e=i.next();if(e.done)return e;var o=e.value,c=o[0],l=o[1];return t.call(r,l,c,a)?2===n?e:P(n,c,l,e):(s=!1,{value:void 0,done:!0})}))},n}(this,e,t))},takeUntil:function(e,t){return this.takeWhile(Lr(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=function(e){if(e.size===1/0)return 0;var t=d(e),r=s(e),n=t?1:0;return function(e,t){return t=he(t,3432918353),t=he(t<<15|t>>>-15,461845907),t=he(t<<13|t>>>-13,5),t=he((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=ge((t=he(t^t>>>13,3266489909))^t>>>16)}(e.__iterate(r?t?function(e,t){n=31*n+jr(be(e),be(t))|0}:function(e,t){n=n+jr(be(e),be(t))|0}:t?function(e){n=31*n+be(e)|0}:function(e){n=n+be(e)|0}),n)}(this))}});var xr=r.prototype;xr[u]=!0,xr[A]=xr.values,xr.__toJS=xr.toArray,xr.__toStringMapper=Cr,xr.inspect=xr.toSource=function(){return this.toString()},xr.chain=xr.flatMap,xr.contains=xr.includes,function(){try{Object.defineProperty(xr,"length",{get:function(){if(!r.noLengthWarning){var e;try{throw new Error}catch(t){e=t.stack}if(-1===e.indexOf("_wrapObject"))return console&&console.warn&&console.warn("iterable.length has been deprecated, use iterable.size or iterable.count(). This warning will become a silent error in a future version. "+e),this.size}}})}catch(e){}}(),kr(n,{flip:function(){return Bt(this,Ot(this))},findKey:function(e,t){var r=this.findEntry(e,t);return r&&r[0]},findLastKey:function(e,t){return this.toSeq().reverse().findKey(e,t)},keyOf:function(e){return this.findKey((function(t){return ie(t,e)}))},lastKeyOf:function(e){return this.findLastKey((function(t){return ie(t,e)}))},mapEntries:function(e,t){var r=this,n=0;return Bt(this,this.toSeq().map((function(o,a){return e.call(t,[a,o],n++,r)})).fromEntrySeq())},mapKeys:function(e,t){var r=this;return Bt(this,this.toSeq().flip().map((function(n,o){return e.call(t,n,o,r)})).flip())}});var Mr=n.prototype;function Sr(e,t){return t}function Er(e,t){return[t,e]}function Lr(e){return function(){return!e.apply(this,arguments)}}function Dr(e){return function(){return-e.apply(this,arguments)}}function Cr(e){return"string"==typeof e?JSON.stringify(e):e}function Or(){return w(arguments)}function Tr(e,t){return et?-1:0}function jr(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Mr[p]=!0,Mr[A]=xr.entries,Mr.__toJS=xr.toObject,Mr.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+Cr(e)},kr(o,{toKeyedSeq:function(){return new Et(this,!1)},filter:function(e,t){return Bt(this,At(this,e,t,!1))},findIndex:function(e,t){var r=this.findEntry(e,t);return r?r[0]:-1},indexOf:function(e){var t=this.toKeyedSeq().keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.toKeyedSeq().reverse().keyOf(e);return void 0===t?-1:t},reverse:function(){return Bt(this,jt(this,!1))},slice:function(e,t){return Bt(this,Nt(this,e,t,!1))},splice:function(e,t){var r=arguments.length;if(t=Math.max(0|t,0),0===r||2===r&&!t)return this;e=E(e,e<0?this.count():this.size);var n=this.slice(0,e);return Bt(this,1===r?n:n.concat(w(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var r=this.toKeyedSeq().findLastKey(e,t);return void 0===r?-1:r},first:function(){return this.get(0)},flatten:function(e){return Bt(this,Yt(this,e,!1))},get:function(e,t){return(e=x(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find((function(t,r){return r===e}),void 0,t)},has:function(e){return(e=x(this,e))>=0&&(void 0!==this.size?this.size===1/0||e2?r-2:0),a=2;a0)return o.getInlineStyleAt(n-1);if(o.getLength())return o.getInlineStyleAt(0);return _(e,r)}(t,r):function(e,t){var r=t.getStartKey(),n=t.getStartOffset(),o=e.getBlockForKey(r);if(n0)return o.getInlineStyleAt(n-1);return _(e,r)}(t,r)},e.getBlockTree=function(e){return this.getImmutable().getIn(["treeMap",e])},e.isSelectionAtStartOfContent=function(){var e=this.getCurrentContent().getBlockMap().first().getKey();return this.getSelection().hasEdgeWithin(e,0,0)},e.isSelectionAtEndOfContent=function(){var e=this.getCurrentContent().getBlockMap().last(),t=e.getLength();return this.getSelection().hasEdgeWithin(e.getKey(),t,t)},e.getDirectionMap=function(){return this.getImmutable().get("directionMap")},t.acceptSelection=function(e,t){return b(e,t,!1)},t.forceSelection=function(e,t){return t.getHasFocus()||(t=t.set("hasFocus",!0)),b(e,t,!0)},t.moveSelectionToEnd=function(e){var r=e.getCurrentContent().getLastBlock(),n=r.getKey(),o=r.getLength();return t.acceptSelection(e,new c({anchorKey:n,anchorOffset:o,focusKey:n,focusOffset:o,isBackward:!1}))},t.moveFocusToEnd=function(e){var r=t.moveSelectionToEnd(e);return t.forceSelection(r,r.getSelection())},t.push=function(e,r,n){var o=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];if(e.getCurrentContent()===r)return e;var a=s.getDirectionMap(r,e.getDirectionMap());if(!e.getAllowUndo())return t.set(e,{currentContent:r,directionMap:a,lastChangeType:n,selection:r.getSelectionAfter(),forceSelection:o,inlineStyleOverride:null});var i=e.getSelection(),c=e.getCurrentContent(),l=e.getUndoStack(),d=r;i!==c.getSelectionAfter()||y(e,n)?(l=l.push(c),d=d.set("selectionBefore",i)):"insert-characters"!==n&&"backspace-character"!==n&&"delete-character"!==n||(d=d.set("selectionBefore",c.getSelectionBefore()));var u=e.getInlineStyleOverride(),f=["adjust-depth","change-block-type","split-block"];-1===f.indexOf(n)&&(u=null);var m={currentContent:d,directionMap:a,undoStack:l,redoStack:p(),lastChangeType:n,selection:r.getSelectionAfter(),forceSelection:o,inlineStyleOverride:u};return t.set(e,m)},t.undo=function(e){if(!e.getAllowUndo())return e;var r=e.getUndoStack(),n=r.peek();if(!n)return e;var o=e.getCurrentContent(),a=s.getDirectionMap(n,e.getDirectionMap());return t.set(e,{currentContent:n,directionMap:a,undoStack:r.shift(),redoStack:e.getRedoStack().push(o),forceSelection:!0,inlineStyleOverride:null,lastChangeType:"undo",nativelyRenderedContent:null,selection:o.getSelectionBefore()})},t.redo=function(e){if(!e.getAllowUndo())return e;var r=e.getRedoStack(),n=r.peek();if(!n)return e;var o=e.getCurrentContent(),a=s.getDirectionMap(n,e.getDirectionMap());return t.set(e,{currentContent:n,directionMap:a,undoStack:e.getUndoStack().push(o),redoStack:r.shift(),forceSelection:!0,inlineStyleOverride:null,lastChangeType:"redo",nativelyRenderedContent:null,selection:n.getSelectionAfter()})},e.getImmutable=function(){return this._immutable},t}();function b(e,t,r){return g.set(e,{selection:t,forceSelection:r,nativelyRenderedContent:null,inlineStyleOverride:null})}function v(e,t){return e.getBlockMap().map((function(r){return a.generate(e,r,t)})).toOrderedMap()}function y(e,t){return t!==e.getLastChangeType()||"insert-characters"!==t&&"backspace-character"!==t&&"delete-character"!==t}function _(e,t){var r=e.getBlockMap().reverse().skipUntil((function(e,r){return r===t})).skip(1).skipUntil((function(e,t){return e.getLength()})).first();return r?r.getInlineStyleAt(r.getLength()-1):d()}e.exports=g},function(e,t,r){var n=r(110)("wks"),o=r(72),a=r(8).Symbol,i="function"==typeof a;(e.exports=function(e){return n[e]||(n[e]=i&&a[e]||(i?a:o)("Symbol."+e))}).store=n},function(e,t,r){var n=r(45),o=Math.min;e.exports=function(e){return e>0?o(n(e),9007199254740991):0}},function(e,t,r){e.exports=!r(10)((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}},function(e,t){e.exports=function(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}},function(e,t,r){var n=r(7),o=r(244),a=r(49),i=Object.defineProperty;t.f=r(17)?Object.defineProperty:function(e,t,r){if(n(e),t=a(t,!0),n(r),o)try{return i(e,t,r)}catch(e){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(e[t]=r.value),e}},function(e,t,r){var n=r(50);e.exports=function(e){return Object(n(e))}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){"use strict";var n=r(36),o=r(870),a=r(871),i=r(873),s=r(152),c=r(9),l=r(876),d=r(877),u=r(11),p=r(445),f=r(443),m=r(878),h=r(879),g=c.OrderedSet,b={replaceText:function(e,t,r,o,a){var i=f(e,t),s=m(i,t),c=n.create({style:o||g(),entity:a||null});return d(s,s.getSelectionAfter(),r,c)},insertText:function(e,t,r,n,o){return t.isCollapsed()||u(!1),b.replaceText(e,t,r,n,o)},moveText:function(e,t,r){var n=s(e,t),o=b.removeRange(e,t,"backward");return b.replaceWithFragment(o,r,n)},replaceWithFragment:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"REPLACE_WITH_NEW_DATA",o=f(e,t),a=m(o,t);return l(a,a.getSelectionAfter(),r,n)},removeRange:function(e,t,r){var n,o,a,s;t.getIsBackward()&&(t=t.merge({anchorKey:t.getFocusKey(),anchorOffset:t.getFocusOffset(),focusKey:t.getAnchorKey(),focusOffset:t.getAnchorOffset(),isBackward:!1})),n=t.getAnchorKey(),o=t.getFocusKey(),a=e.getBlockForKey(n),s=e.getBlockForKey(o);var c=t.getStartOffset(),l=t.getEndOffset(),d=a.getEntityAt(c),u=s.getEntityAt(l-1);if(n===o&&d&&d===u){var p=i(e.getEntityMap(),a,s,t,r);return m(e,p)}var h=f(e,t);return m(h,t)},splitBlock:function(e,t){var r=f(e,t),n=m(r,t);return h(n,n.getSelectionAfter())},applyInlineStyle:function(e,t,r){return o.add(e,t,r)},removeInlineStyle:function(e,t,r){return o.remove(e,t,r)},setBlockType:function(e,t,r){return p(e,t,(function(e){return e.merge({type:r,depth:0})}))},setBlockData:function(e,t,r){return p(e,t,(function(e){return e.merge({data:r})}))},mergeBlockData:function(e,t,r){return p(e,t,(function(e){return e.merge({data:e.getData().merge(r)})}))},applyEntity:function(e,t,r){var n=f(e,t);return a(n,t,r)}};e.exports=b},function(e,t,r){"use strict";e.exports=function(e){if(null!=e)return e;throw new Error("Got unexpected null or undefined")}},function(e,t,r){var n=r(20),o=r(71);e.exports=r(17)?function(e,t,r){return n.f(e,t,o(1,r))}:function(e,t,r){return e[t]=r,e}},function(e,t,r){var n=r(8),o=r(26),a=r(32),i=r(72)("src"),s=r(516),c=(""+s).split("toString");r(42).inspectSource=function(e){return s.call(e)},(e.exports=function(e,t,r,s){var l="function"==typeof r;l&&(a(r,"name")||o(r,"name",t)),e[t]!==r&&(l&&(a(r,i)||o(r,i,e[t]?""+e[t]:c.join(String(t)))),e===n?e[t]=r:s?e[t]?e[t]=r:o(e,t,r):(delete e[t],o(e,t,r)))})(Function.prototype,"toString",(function(){return"function"==typeof this&&this[i]||s.call(this)}))},function(e,t,r){var n=r(4),o=r(10),a=r(50),i=/"/g,s=function(e,t,r,n){var o=String(a(e)),s="<"+t;return""!==r&&(s+=" "+r+'="'+String(n).replace(i,""")+'"'),s+">"+o+""};e.exports=function(e,t){var r={};r[e]=t(s),n(n.P+n.F*o((function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3})),"String",r)}},function(e,t,r){"use strict";var n=r(889),o=r(891),a=r(892),i=r(893);function s(e,t,r,n){if(e===r)return!0;if(!r.startsWith(e))return!1;var a=r.slice(e.length);return!!t&&(a=n?n(a):a,o.contains(a,t))}function c(e){return"Windows"===n.platformName?e.replace(/^\s*NT/,""):e}var l={isBrowser:function(e){return s(n.browserName,n.browserFullVersion,e)},isBrowserArchitecture:function(e){return s(n.browserArchitecture,null,e)},isDevice:function(e){return s(n.deviceName,null,e)},isEngine:function(e){return s(n.engineName,n.engineVersion,e)},isPlatform:function(e){return s(n.platformName,n.platformFullVersion,e,c)},isPlatformArchitecture:function(e){return s(n.platformArchitecture,null,e)}};e.exports=a(l,i)},function(e,t,r){"use strict";t.__esModule=!0;var n,o=r(162),a=(n=o)&&n.__esModule?n:{default:n};t.default=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!==(void 0===t?"undefined":(0,a.default)(t))&&"function"!=typeof t?e:t}},function(e,t,r){"use strict";function n(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}r.d(t,"a",(function(){return n}))},function(e,t){var r={}.hasOwnProperty;e.exports=function(e,t){return r.call(e,t)}},function(e,t,r){var n=r(111),o=r(50);e.exports=function(e){return n(o(e))}},function(e,t,r){var n=r(112),o=r(71),a=r(33),i=r(49),s=r(32),c=r(244),l=Object.getOwnPropertyDescriptor;t.f=r(17)?l:function(e,t){if(e=a(e),t=i(t,!0),c)try{return l(e,t)}catch(e){}if(s(e,t))return o(!n.f.call(e,t),e[t])}},function(e,t,r){var n=r(32),o=r(21),a=r(174)("IE_PROTO"),i=Object.prototype;e.exports=Object.getPrototypeOf||function(e){return e=o(e),n(e,a)?e[a]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?i:null}},function(e,t,r){"use strict";var n=r(9),o=n.Map,a=n.OrderedSet,i=n.Record,s=a(),c={style:s,entity:null},l=function(e){var t,r;function n(){return e.apply(this,arguments)||this}r=e,(t=n).prototype=Object.create(r.prototype),t.prototype.constructor=t,t.__proto__=r;var i=n.prototype;return i.getStyle=function(){return this.get("style")},i.getEntity=function(){return this.get("entity")},i.hasStyle=function(e){return this.getStyle().includes(e)},n.applyStyle=function(e,t){var r=e.set("style",e.getStyle().add(t));return n.create(r)},n.removeStyle=function(e,t){var r=e.set("style",e.getStyle().remove(t));return n.create(r)},n.applyEntity=function(e,t){var r=e.getEntity()===t?e:e.set("entity",t);return n.create(r)},n.create=function(e){if(!e)return d;var t=o({style:s,entity:null}).merge(e),r=u.get(t);if(r)return r;var a=new n(t);return u=u.set(t,a),a},n.fromJS=function(e){var t=e.style,r=e.entity;return new n({style:Array.isArray(t)?a(t):t,entity:Array.isArray(r)?a(r):r})},n}(i(c)),d=new l,u=o([[o(c),d]]);l.EMPTY=d,e.exports=l},function(e,t,r){"use strict";var n=r(36),o=r(121),a=r(9),i=a.List,s=a.Map,c=a.OrderedSet,l=a.Record,d=a.Repeat,u=c(),p={parent:null,characterList:i(),data:s(),depth:0,key:"",text:"",type:"unstyled",children:i(),prevSibling:null,nextSibling:null},f=function(e,t){return e.getStyle()===t.getStyle()},m=function(e,t){return e.getEntity()===t.getEntity()},h=function(e){if(!e)return e;var t=e.characterList,r=e.text;return r&&!t&&(e.characterList=i(d(n.EMPTY,r.length))),e},g=function(e){var t,r;function n(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:p;return e.call(this,h(t))||this}r=e,(t=n).prototype=Object.create(r.prototype),t.prototype.constructor=t,t.__proto__=r;var a=n.prototype;return a.getKey=function(){return this.get("key")},a.getType=function(){return this.get("type")},a.getText=function(){return this.get("text")},a.getCharacterList=function(){return this.get("characterList")},a.getLength=function(){return this.getText().length},a.getDepth=function(){return this.get("depth")},a.getData=function(){return this.get("data")},a.getInlineStyleAt=function(e){var t=this.getCharacterList().get(e);return t?t.getStyle():u},a.getEntityAt=function(e){var t=this.getCharacterList().get(e);return t?t.getEntity():null},a.getChildKeys=function(){return this.get("children")},a.getParentKey=function(){return this.get("parent")},a.getPrevSiblingKey=function(){return this.get("prevSibling")},a.getNextSiblingKey=function(){return this.get("nextSibling")},a.findStyleRanges=function(e,t){o(this.getCharacterList(),f,e,t)},a.findEntityRanges=function(e,t){o(this.getCharacterList(),m,e,t)},n}(l(p));e.exports=g},function(e,t,r){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,r){"use strict";t.__esModule=!0;var n,o=r(435),a=(n=o)&&n.__esModule?n:{default:n};t.default=function(){function e(e,t){for(var r=0;r0?n:r)(e)}},function(e,t,r){"use strict";var n=r(10);e.exports=function(e,t){return!!e&&n((function(){t?e.call(null,(function(){}),1):e.call(null)}))}},function(e,t,r){"use strict";r.r(t),r.d(t,"default",(function(){return i}));var n=r(5),o=r(6),a=r(3);function i(e,t){Object(a.a)(1,arguments);var r=t||{},i=r.locale,s=i&&i.options&&i.options.weekStartsOn,c=null==s?0:Object(o.a)(s),l=null==r.weekStartsOn?c:Object(o.a)(r.weekStartsOn);if(!(l>=0&&l<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=Object(n.default)(e),u=d.getDay(),p=(u=0&&l<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=Object(o.default)(e),u=d.getUTCDay(),p=(uw;w++)if((p||w in v)&&(g=y(h=v[w],w,b),e))if(r)k[w]=g;else if(g)switch(e){case 3:return!0;case 5:return h;case 6:return w;case 2:k.push(h)}else if(d)return!1;return u?-1:l||d?d:k}}},function(e,t,r){"use strict";var n={},o=Math.pow(2,24);e.exports=function(){for(var e;void 0===e||n.hasOwnProperty(e)||!isNaN(+e);)e=Math.floor(Math.random()*o).toString(32);return n[e]=!0,e}},function(e,t,r){"use strict";t.__esModule=!0;var n,o=r(816),a=(n=o)&&n.__esModule?n:{default:n};t.default=a.default||function(e){for(var t=1;t0?(6e4+n(t))%6e4:n(t))}},function(e,t,r){"use strict";r.d(t,"a",(function(){return a}));var n=r(5),o=r(3);function a(e){Object(o.a)(1,arguments);var t=1,r=Object(n.default)(e),a=r.getUTCDay(),i=(ar;)o[r]=t[r++];return o},Ce=function(e,t,r){H(e,t,{get:function(){return this._d[r]}})},Oe=function(e){var t,r,n,o,a,i,s=k(e),c=arguments.length,d=c>1?arguments[1]:void 0,u=void 0!==d,p=L(s);if(null!=p&&!x(p)){for(i=p.call(s),n=[],t=0;!(a=i.next()).done;t++)n.push(a.value);s=n}for(u&&c>2&&(d=l(d,arguments[2],2)),t=0,r=h(s.length),o=Ee(this,r);r>t;t++)o[t]=u?d(s[t],t):s[t];return o},Te=function(){for(var e=0,t=arguments.length,r=Ee(this,t);t>e;)r[e]=arguments[e++];return r},je=!!q&&a((function(){fe.call(new q(1))})),Ae=function(){return fe.apply(je?ue.call(Se(this)):Se(this),arguments)},Ne={copyWithin:function(e,t){return z.call(Se(this),e,t,arguments.length>2?arguments[2]:void 0)},every:function(e){return $(Se(this),e,arguments.length>1?arguments[1]:void 0)},fill:function(e){return Y.apply(Se(this),arguments)},filter:function(e){return Le(this,J(Se(this),e,arguments.length>1?arguments[1]:void 0))},find:function(e){return X(Se(this),e,arguments.length>1?arguments[1]:void 0)},findIndex:function(e){return ee(Se(this),e,arguments.length>1?arguments[1]:void 0)},forEach:function(e){Q(Se(this),e,arguments.length>1?arguments[1]:void 0)},indexOf:function(e){return re(Se(this),e,arguments.length>1?arguments[1]:void 0)},includes:function(e){return te(Se(this),e,arguments.length>1?arguments[1]:void 0)},join:function(e){return le.apply(Se(this),arguments)},lastIndexOf:function(e){return ie.apply(Se(this),arguments)},map:function(e){return we(Se(this),e,arguments.length>1?arguments[1]:void 0)},reduce:function(e){return se.apply(Se(this),arguments)},reduceRight:function(e){return ce.apply(Se(this),arguments)},reverse:function(){for(var e,t=Se(this).length,r=Math.floor(t/2),n=0;n1?arguments[1]:void 0)},sort:function(e){return de.call(Se(this),e)},subarray:function(e,t){var r=Se(this),n=r.length,o=b(e,n);return new(j(r,r[be]))(r.buffer,r.byteOffset+o*r.BYTES_PER_ELEMENT,h((void 0===t?n:b(t,n))-o))}},Pe=function(e,t){return Le(this,ue.call(Se(this),e,t))},Ie=function(e){Se(this);var t=Me(arguments[1],1),r=this.length,n=k(e),o=h(n.length),a=0;if(o+t>r)throw U("Wrong length!");for(;a255?255:255&n),o.v[f](r*t+o.o,n,ke)}(this,r,e)},enumerable:!0})};y?(m=r((function(e,r,n,o){d(e,m,l,"_d");var a,i,s,c,u=0,f=0;if(w(r)){if(!(r instanceof K||"ArrayBuffer"==(c=_(r))||"SharedArrayBuffer"==c))return ye in r?De(m,r):Oe.call(m,r);a=r,f=Me(n,t);var b=r.byteLength;if(void 0===o){if(b%t)throw U("Wrong length!");if((i=b-f)<0)throw U("Wrong length!")}else if((i=h(o)*t)+f>b)throw U("Wrong length!");s=i/t}else s=g(r),a=new K(i=s*t);for(p(e,"_d",{b:a,o:f,l:i,e:s,v:new G(a)});udocument.F=Object<\/script>"),e.close(),c=e.F;n--;)delete c.prototype[a[n]];return c()};e.exports=Object.create||function(e,t){var r;return null!==e?(s.prototype=n(e),r=new s,s.prototype=null,r[i]=e):r=c(),void 0===t?r:o(r,t)}},function(e,t,r){var n=r(246),o=r(175).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return n(e,o)}},function(e,t,r){"use strict";var n=r(8),o=r(20),a=r(17),i=r(15)("species");e.exports=function(e){var t=n[e];a&&t&&!t[i]&&o.f(t,i,{configurable:!0,get:function(){return this}})}},function(e,t){e.exports=function(e,t,r,n){if(!(e instanceof t)||void 0!==n&&n in e)throw TypeError(r+": incorrect invocation!");return e}},function(e,t,r){var n=r(43),o=r(259),a=r(187),i=r(7),s=r(16),c=r(189),l={},d={};(t=e.exports=function(e,t,r,u,p){var f,m,h,g,b=p?function(){return e}:c(e),v=n(r,u,t?2:1),y=0;if("function"!=typeof b)throw TypeError(e+" is not iterable!");if(a(b)){for(f=s(e.length);f>y;y++)if((g=t?v(i(m=e[y])[0],m[1]):v(e[y]))===l||g===d)return g}else for(h=b.call(e);!(m=h.next()).done;)if((g=o(h,v,m.value,t))===l||g===d)return g}).BREAK=l,t.RETURN=d},function(e,t,r){var n=r(27);e.exports=function(e,t,r){for(var o in t)n(e,o,t[o],r);return e}},function(e,t,r){var n=r(13);e.exports=function(e,t){if(!n(e)||e._t!==t)throw TypeError("Incompatible receiver, "+t+" required!");return e}},function(e,t,r){var n=r(731),o=r(736);e.exports=function(e,t){var r=o(e,t);return n(r)?r:void 0}},function(e,t,r){var n=r(117),o=r(430),a=r(208),i=Object.defineProperty;t.f=r(68)?Object.defineProperty:function(e,t,r){if(n(e),t=a(t,!0),n(r),o)try{return i(e,t,r)}catch(e){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(e[t]=r.value),e}},function(e,t){var r={}.hasOwnProperty;e.exports=function(e,t){return r.call(e,t)}},function(e,t,r){"use strict";var n=r(36),o=r(121),a=r(9),i=a.List,s=a.Map,c=a.OrderedSet,l=a.Record,d=a.Repeat,u=c(),p=l({key:"",type:"unstyled",text:"",characterList:i(),depth:0,data:s()}),f=function(e){var t,r;function a(t){return e.call(this,function(e){if(!e)return e;var t=e.characterList,r=e.text;return r&&!t&&(e.characterList=i(d(n.EMPTY,r.length))),e}(t))||this}r=e,(t=a).prototype=Object.create(r.prototype),t.prototype.constructor=t,t.__proto__=r;var s=a.prototype;return s.getKey=function(){return this.get("key")},s.getType=function(){return this.get("type")},s.getText=function(){return this.get("text")},s.getCharacterList=function(){return this.get("characterList")},s.getLength=function(){return this.getText().length},s.getDepth=function(){return this.get("depth")},s.getData=function(){return this.get("data")},s.getInlineStyleAt=function(e){var t=this.getCharacterList().get(e);return t?t.getStyle():u},s.getEntityAt=function(e){var t=this.getCharacterList().get(e);return t?t.getEntity():null},s.findStyleRanges=function(e,t){o(this.getCharacterList(),m,e,t)},s.findEntityRanges=function(e,t){o(this.getCharacterList(),h,e,t)},a}(p);function m(e,t){return e.getStyle()===t.getStyle()}function h(e,t){return e.getEntity()===t.getEntity()}e.exports=f},function(e,t,r){"use strict";function n(e){return e.replace(/\//g,"-")}e.exports=function(e){return"object"==typeof e?Object.keys(e).filter((function(t){return e[t]})).map(n).join(" "):Array.prototype.map.call(arguments,n).join(" ")}},function(e,t,r){"use strict";e.exports=function(e){return!(!e||!e.ownerDocument)&&e.nodeType===Node.ELEMENT_NODE}},function(e,t,r){"use strict";var n=r(11),o=/[\uD800-\uDFFF]/;function a(e){return 55296<=e&&e<=57343}function i(e){return o.test(e)}function s(e,t){return 1+a(e.charCodeAt(t))}function c(e,t,r){if(t=t||0,r=void 0===r?1/0:r||0,!i(e))return e.substr(t,r);var n=e.length;if(n<=0||t>n||r<=0)return"";var o=0;if(t>0){for(;t>0&&o=n)return""}else if(t<0){for(o=n;t<0&&00&&a=0&&u.splice(t,1)}function b(e){var t=document.createElement("style");if(void 0===e.attrs.type&&(e.attrs.type="text/css"),void 0===e.attrs.nonce){var n=function(){0;return r.nc}();n&&(e.attrs.nonce=n)}return v(t,e.attrs),h(e,t),t}function v(e,t){Object.keys(t).forEach((function(r){e.setAttribute(r,t[r])}))}function y(e,t){var r,n,o,a;if(t.transform&&e.css){if(!(a="function"==typeof t.transform?t.transform(e.css):t.transform.default(e.css)))return function(){};e.css=a}if(t.singleton){var i=d++;r=l||(l=b(t)),n=k.bind(null,r,i,!1),o=k.bind(null,r,i,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(r=function(e){var t=document.createElement("link");return void 0===e.attrs.type&&(e.attrs.type="text/css"),e.attrs.rel="stylesheet",v(t,e.attrs),h(e,t),t}(t),n=M.bind(null,r,t),o=function(){g(r),r.href&&URL.revokeObjectURL(r.href)}):(r=b(t),n=x.bind(null,r),o=function(){g(r)});return n(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;n(e=t)}else o()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=i()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var r=m(e,t);return f(r,t),function(e){for(var n=[],o=0;o=1&&p<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var f=new Date(0);f.setUTCFullYear(s+1,0,p),f.setUTCHours(0,0,0,0);var m=Object(a.a)(f,t),h=new Date(0);h.setUTCFullYear(s,0,p),h.setUTCHours(0,0,0,0);var g=Object(a.a)(h,t);return r.getTime()>=m.getTime()?s+1:r.getTime()>=g.getTime()?s:s-1}},function(e,t,r){ +/*! + * Chart.js v2.9.3 + * https://www.chartjs.org + * (c) 2019 Chart.js Contributors + * Released under the MIT License + */ +e.exports=function(e){"use strict";e=e&&e.hasOwnProperty("default")?e.default:e;var t={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},r=function(e,t){return e(t={exports:{}},t.exports),t.exports}((function(e){var r={};for(var n in t)t.hasOwnProperty(n)&&(r[t[n]]=n);var o=e.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var a in o)if(o.hasOwnProperty(a)){if(!("channels"in o[a]))throw new Error("missing channels property: "+a);if(!("labels"in o[a]))throw new Error("missing channel labels property: "+a);if(o[a].labels.length!==o[a].channels)throw new Error("channel and label counts mismatch: "+a);var i=o[a].channels,s=o[a].labels;delete o[a].channels,delete o[a].labels,Object.defineProperty(o[a],"channels",{value:i}),Object.defineProperty(o[a],"labels",{value:s})}o.rgb.hsl=function(e){var t,r,n=e[0]/255,o=e[1]/255,a=e[2]/255,i=Math.min(n,o,a),s=Math.max(n,o,a),c=s-i;return s===i?t=0:n===s?t=(o-a)/c:o===s?t=2+(a-n)/c:a===s&&(t=4+(n-o)/c),(t=Math.min(60*t,360))<0&&(t+=360),r=(i+s)/2,[t,100*(s===i?0:r<=.5?c/(s+i):c/(2-s-i)),100*r]},o.rgb.hsv=function(e){var t,r,n,o,a,i=e[0]/255,s=e[1]/255,c=e[2]/255,l=Math.max(i,s,c),d=l-Math.min(i,s,c),u=function(e){return(l-e)/6/d+.5};return 0===d?o=a=0:(a=d/l,t=u(i),r=u(s),n=u(c),i===l?o=n-r:s===l?o=1/3+t-n:c===l&&(o=2/3+r-t),o<0?o+=1:o>1&&(o-=1)),[360*o,100*a,100*l]},o.rgb.hwb=function(e){var t=e[0],r=e[1],n=e[2];return[o.rgb.hsl(e)[0],1/255*Math.min(t,Math.min(r,n))*100,100*(n=1-1/255*Math.max(t,Math.max(r,n)))]},o.rgb.cmyk=function(e){var t,r=e[0]/255,n=e[1]/255,o=e[2]/255;return[100*((1-r-(t=Math.min(1-r,1-n,1-o)))/(1-t)||0),100*((1-n-t)/(1-t)||0),100*((1-o-t)/(1-t)||0),100*t]},o.rgb.keyword=function(e){var n=r[e];if(n)return n;var o,a,i,s=1/0;for(var c in t)if(t.hasOwnProperty(c)){var l=t[c],d=(a=e,i=l,Math.pow(a[0]-i[0],2)+Math.pow(a[1]-i[1],2)+Math.pow(a[2]-i[2],2));d.04045?Math.pow((t+.055)/1.055,2.4):t/12.92)+.3576*(r=r>.04045?Math.pow((r+.055)/1.055,2.4):r/12.92)+.1805*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)),100*(.2126*t+.7152*r+.0722*n),100*(.0193*t+.1192*r+.9505*n)]},o.rgb.lab=function(e){var t=o.rgb.xyz(e),r=t[0],n=t[1],a=t[2];return n/=100,a/=108.883,r=(r/=95.047)>.008856?Math.pow(r,1/3):7.787*r+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(r-n),200*(n-(a=a>.008856?Math.pow(a,1/3):7.787*a+16/116))]},o.hsl.rgb=function(e){var t,r,n,o,a,i=e[0]/360,s=e[1]/100,c=e[2]/100;if(0===s)return[a=255*c,a,a];t=2*c-(r=c<.5?c*(1+s):c+s-c*s),o=[0,0,0];for(var l=0;l<3;l++)(n=i+1/3*-(l-1))<0&&n++,n>1&&n--,a=6*n<1?t+6*(r-t)*n:2*n<1?r:3*n<2?t+(r-t)*(2/3-n)*6:t,o[l]=255*a;return o},o.hsl.hsv=function(e){var t=e[0],r=e[1]/100,n=e[2]/100,o=r,a=Math.max(n,.01);return r*=(n*=2)<=1?n:2-n,o*=a<=1?a:2-a,[t,100*(0===n?2*o/(a+o):2*r/(n+r)),(n+r)/2*100]},o.hsv.rgb=function(e){var t=e[0]/60,r=e[1]/100,n=e[2]/100,o=Math.floor(t)%6,a=t-Math.floor(t),i=255*n*(1-r),s=255*n*(1-r*a),c=255*n*(1-r*(1-a));switch(n*=255,o){case 0:return[n,c,i];case 1:return[s,n,i];case 2:return[i,n,c];case 3:return[i,s,n];case 4:return[c,i,n];case 5:return[n,i,s]}},o.hsv.hsl=function(e){var t,r,n,o=e[0],a=e[1]/100,i=e[2]/100,s=Math.max(i,.01);return n=(2-a)*i,r=a*s,[o,100*(r=(r/=(t=(2-a)*s)<=1?t:2-t)||0),100*(n/=2)]},o.hwb.rgb=function(e){var t,r,n,o,a,i,s,c=e[0]/360,l=e[1]/100,d=e[2]/100,u=l+d;switch(u>1&&(l/=u,d/=u),n=6*c-(t=Math.floor(6*c)),0!=(1&t)&&(n=1-n),o=l+n*((r=1-d)-l),t){default:case 6:case 0:a=r,i=o,s=l;break;case 1:a=o,i=r,s=l;break;case 2:a=l,i=r,s=o;break;case 3:a=l,i=o,s=r;break;case 4:a=o,i=l,s=r;break;case 5:a=r,i=l,s=o}return[255*a,255*i,255*s]},o.cmyk.rgb=function(e){var t=e[0]/100,r=e[1]/100,n=e[2]/100,o=e[3]/100;return[255*(1-Math.min(1,t*(1-o)+o)),255*(1-Math.min(1,r*(1-o)+o)),255*(1-Math.min(1,n*(1-o)+o))]},o.xyz.rgb=function(e){var t,r,n,o=e[0]/100,a=e[1]/100,i=e[2]/100;return r=-.9689*o+1.8758*a+.0415*i,n=.0557*o+-.204*a+1.057*i,t=(t=3.2406*o+-1.5372*a+-.4986*i)>.0031308?1.055*Math.pow(t,1/2.4)-.055:12.92*t,r=r>.0031308?1.055*Math.pow(r,1/2.4)-.055:12.92*r,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,[255*(t=Math.min(Math.max(0,t),1)),255*(r=Math.min(Math.max(0,r),1)),255*(n=Math.min(Math.max(0,n),1))]},o.xyz.lab=function(e){var t=e[0],r=e[1],n=e[2];return r/=100,n/=108.883,t=(t/=95.047)>.008856?Math.pow(t,1/3):7.787*t+16/116,[116*(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116)-16,500*(t-r),200*(r-(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116))]},o.lab.xyz=function(e){var t,r,n,o=e[0];t=e[1]/500+(r=(o+16)/116),n=r-e[2]/200;var a=Math.pow(r,3),i=Math.pow(t,3),s=Math.pow(n,3);return r=a>.008856?a:(r-16/116)/7.787,t=i>.008856?i:(t-16/116)/7.787,n=s>.008856?s:(n-16/116)/7.787,[t*=95.047,r*=100,n*=108.883]},o.lab.lch=function(e){var t,r=e[0],n=e[1],o=e[2];return(t=360*Math.atan2(o,n)/2/Math.PI)<0&&(t+=360),[r,Math.sqrt(n*n+o*o),t]},o.lch.lab=function(e){var t,r=e[0],n=e[1];return t=e[2]/360*2*Math.PI,[r,n*Math.cos(t),n*Math.sin(t)]},o.rgb.ansi16=function(e){var t=e[0],r=e[1],n=e[2],a=1 in arguments?arguments[1]:o.rgb.hsv(e)[2];if(0===(a=Math.round(a/50)))return 30;var i=30+(Math.round(n/255)<<2|Math.round(r/255)<<1|Math.round(t/255));return 2===a&&(i+=60),i},o.hsv.ansi16=function(e){return o.rgb.ansi16(o.hsv.rgb(e),e[2])},o.rgb.ansi256=function(e){var t=e[0],r=e[1],n=e[2];return t===r&&r===n?t<8?16:t>248?231:Math.round((t-8)/247*24)+232:16+36*Math.round(t/255*5)+6*Math.round(r/255*5)+Math.round(n/255*5)},o.ansi16.rgb=function(e){var t=e%10;if(0===t||7===t)return e>50&&(t+=3.5),[t=t/10.5*255,t,t];var r=.5*(1+~~(e>50));return[(1&t)*r*255,(t>>1&1)*r*255,(t>>2&1)*r*255]},o.ansi256.rgb=function(e){if(e>=232){var t=10*(e-232)+8;return[t,t,t]}var r;return e-=16,[Math.floor(e/36)/5*255,Math.floor((r=e%36)/6)/5*255,r%6/5*255]},o.rgb.hex=function(e){var t=(((255&Math.round(e[0]))<<16)+((255&Math.round(e[1]))<<8)+(255&Math.round(e[2]))).toString(16).toUpperCase();return"000000".substring(t.length)+t},o.hex.rgb=function(e){var t=e.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!t)return[0,0,0];var r=t[0];3===t[0].length&&(r=r.split("").map((function(e){return e+e})).join(""));var n=parseInt(r,16);return[n>>16&255,n>>8&255,255&n]},o.rgb.hcg=function(e){var t,r=e[0]/255,n=e[1]/255,o=e[2]/255,a=Math.max(Math.max(r,n),o),i=Math.min(Math.min(r,n),o),s=a-i;return t=s<=0?0:a===r?(n-o)/s%6:a===n?2+(o-r)/s:4+(r-n)/s+4,t/=6,[360*(t%=1),100*s,100*(s<1?i/(1-s):0)]},o.hsl.hcg=function(e){var t=e[1]/100,r=e[2]/100,n=1,o=0;return(n=r<.5?2*t*r:2*t*(1-r))<1&&(o=(r-.5*n)/(1-n)),[e[0],100*n,100*o]},o.hsv.hcg=function(e){var t=e[1]/100,r=e[2]/100,n=t*r,o=0;return n<1&&(o=(r-n)/(1-n)),[e[0],100*n,100*o]},o.hcg.rgb=function(e){var t=e[0]/360,r=e[1]/100,n=e[2]/100;if(0===r)return[255*n,255*n,255*n];var o,a=[0,0,0],i=t%1*6,s=i%1,c=1-s;switch(Math.floor(i)){case 0:a[0]=1,a[1]=s,a[2]=0;break;case 1:a[0]=c,a[1]=1,a[2]=0;break;case 2:a[0]=0,a[1]=1,a[2]=s;break;case 3:a[0]=0,a[1]=c,a[2]=1;break;case 4:a[0]=s,a[1]=0,a[2]=1;break;default:a[0]=1,a[1]=0,a[2]=c}return o=(1-r)*n,[255*(r*a[0]+o),255*(r*a[1]+o),255*(r*a[2]+o)]},o.hcg.hsv=function(e){var t=e[1]/100,r=t+e[2]/100*(1-t),n=0;return r>0&&(n=t/r),[e[0],100*n,100*r]},o.hcg.hsl=function(e){var t=e[1]/100,r=e[2]/100*(1-t)+.5*t,n=0;return r>0&&r<.5?n=t/(2*r):r>=.5&&r<1&&(n=t/(2*(1-r))),[e[0],100*n,100*r]},o.hcg.hwb=function(e){var t=e[1]/100,r=t+e[2]/100*(1-t);return[e[0],100*(r-t),100*(1-r)]},o.hwb.hcg=function(e){var t=e[1]/100,r=1-e[2]/100,n=r-t,o=0;return n<1&&(o=(r-n)/(1-n)),[e[0],100*n,100*o]},o.apple.rgb=function(e){return[e[0]/65535*255,e[1]/65535*255,e[2]/65535*255]},o.rgb.apple=function(e){return[e[0]/255*65535,e[1]/255*65535,e[2]/255*65535]},o.gray.rgb=function(e){return[e[0]/100*255,e[0]/100*255,e[0]/100*255]},o.gray.hsl=o.gray.hsv=function(e){return[0,0,e[0]]},o.gray.hwb=function(e){return[0,100,e[0]]},o.gray.cmyk=function(e){return[0,0,0,e[0]]},o.gray.lab=function(e){return[e[0],0,0]},o.gray.hex=function(e){var t=255&Math.round(e[0]/100*255),r=((t<<16)+(t<<8)+t).toString(16).toUpperCase();return"000000".substring(r.length)+r},o.rgb.gray=function(e){return[(e[0]+e[1]+e[2])/3/255*100]}}));function n(e){var t=function(){for(var e={},t=Object.keys(r),n=t.length,o=0;o1&&(t=Array.prototype.slice.call(arguments));var r=e(t);if("object"==typeof r)for(var n=r.length,o=0;o1&&(t=Array.prototype.slice.call(arguments)),e(t))};return"conversion"in e&&(t.conversion=e.conversion),t}(n)}))}));var s=i,c={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},l={getRgba:d,getHsla:u,getRgb:function(e){var t=d(e);return t&&t.slice(0,3)},getHsl:function(e){var t=u(e);return t&&t.slice(0,3)},getHwb:p,getAlpha:function(e){var t=d(e);return t||(t=u(e))||(t=p(e))?t[3]:void 0},hexString:function(e,t){return t=void 0!==t&&3===e.length?t:e[3],"#"+b(e[0])+b(e[1])+b(e[2])+(t>=0&&t<1?b(Math.round(255*t)):"")},rgbString:function(e,t){return t<1||e[3]&&e[3]<1?f(e,t):"rgb("+e[0]+", "+e[1]+", "+e[2]+")"},rgbaString:f,percentString:function(e,t){if(t<1||e[3]&&e[3]<1)return m(e,t);var r=Math.round(e[0]/255*100),n=Math.round(e[1]/255*100),o=Math.round(e[2]/255*100);return"rgb("+r+"%, "+n+"%, "+o+"%)"},percentaString:m,hslString:function(e,t){return t<1||e[3]&&e[3]<1?h(e,t):"hsl("+e[0]+", "+e[1]+"%, "+e[2]+"%)"},hslaString:h,hwbString:function(e,t){return void 0===t&&(t=void 0!==e[3]?e[3]:1),"hwb("+e[0]+", "+e[1]+"%, "+e[2]+"%"+(void 0!==t&&1!==t?", "+t:"")+")"},keyword:function(e){return v[e.slice(0,3)]}};function d(e){if(e){var t=[0,0,0],r=1,n=e.match(/^#([a-fA-F0-9]{3,4})$/i),o="";if(n){o=(n=n[1])[3];for(var a=0;ar?(t+.05)/(r+.05):(r+.05)/(t+.05)},level:function(e){var t=this.contrast(e);return t>=7.1?"AAA":t>=4.5?"AA":""},dark:function(){var e=this.values.rgb;return(299*e[0]+587*e[1]+114*e[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var e=[],t=0;t<3;t++)e[t]=255-this.values.rgb[t];return this.setValues("rgb",e),this},lighten:function(e){var t=this.values.hsl;return t[2]+=t[2]*e,this.setValues("hsl",t),this},darken:function(e){var t=this.values.hsl;return t[2]-=t[2]*e,this.setValues("hsl",t),this},saturate:function(e){var t=this.values.hsl;return t[1]+=t[1]*e,this.setValues("hsl",t),this},desaturate:function(e){var t=this.values.hsl;return t[1]-=t[1]*e,this.setValues("hsl",t),this},whiten:function(e){var t=this.values.hwb;return t[1]+=t[1]*e,this.setValues("hwb",t),this},blacken:function(e){var t=this.values.hwb;return t[2]+=t[2]*e,this.setValues("hwb",t),this},greyscale:function(){var e=this.values.rgb,t=.3*e[0]+.59*e[1]+.11*e[2];return this.setValues("rgb",[t,t,t]),this},clearer:function(e){var t=this.values.alpha;return this.setValues("alpha",t-t*e),this},opaquer:function(e){var t=this.values.alpha;return this.setValues("alpha",t+t*e),this},rotate:function(e){var t=this.values.hsl,r=(t[0]+e)%360;return t[0]=r<0?360+r:r,this.setValues("hsl",t),this},mix:function(e,t){var r=e,n=void 0===t?.5:t,o=2*n-1,a=this.alpha()-r.alpha(),i=((o*a==-1?o:(o+a)/(1+o*a))+1)/2,s=1-i;return this.rgb(i*this.red()+s*r.red(),i*this.green()+s*r.green(),i*this.blue()+s*r.blue()).alpha(this.alpha()*n+r.alpha()*(1-n))},toJSON:function(){return this.rgb()},clone:function(){var e,t,r=new _,n=this.values,o=r.values;for(var a in n)n.hasOwnProperty(a)&&(e=n[a],"[object Array]"===(t={}.toString.call(e))?o[a]=e.slice(0):"[object Number]"===t?o[a]=e:console.error("unexpected color value:",e));return r}},_.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},_.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},_.prototype.getValues=function(e){for(var t=this.values,r={},n=0;n=0;o--)t.call(r,e[o],o);else for(o=0;o=1?e:-(Math.sqrt(1-e*e)-1)},easeOutCirc:function(e){return Math.sqrt(1-(e-=1)*e)},easeInOutCirc:function(e){return(e/=.5)<1?-.5*(Math.sqrt(1-e*e)-1):.5*(Math.sqrt(1-(e-=2)*e)+1)},easeInElastic:function(e){var t=1.70158,r=0,n=1;return 0===e?0:1===e?1:(r||(r=.3),n<1?(n=1,t=r/4):t=r/(2*Math.PI)*Math.asin(1/n),-n*Math.pow(2,10*(e-=1))*Math.sin((e-t)*(2*Math.PI)/r))},easeOutElastic:function(e){var t=1.70158,r=0,n=1;return 0===e?0:1===e?1:(r||(r=.3),n<1?(n=1,t=r/4):t=r/(2*Math.PI)*Math.asin(1/n),n*Math.pow(2,-10*e)*Math.sin((e-t)*(2*Math.PI)/r)+1)},easeInOutElastic:function(e){var t=1.70158,r=0,n=1;return 0===e?0:2==(e/=.5)?1:(r||(r=.45),n<1?(n=1,t=r/4):t=r/(2*Math.PI)*Math.asin(1/n),e<1?n*Math.pow(2,10*(e-=1))*Math.sin((e-t)*(2*Math.PI)/r)*-.5:n*Math.pow(2,-10*(e-=1))*Math.sin((e-t)*(2*Math.PI)/r)*.5+1)},easeInBack:function(e){var t=1.70158;return e*e*((t+1)*e-t)},easeOutBack:function(e){var t=1.70158;return(e-=1)*e*((t+1)*e+t)+1},easeInOutBack:function(e){var t=1.70158;return(e/=.5)<1?e*e*((1+(t*=1.525))*e-t)*.5:.5*((e-=2)*e*((1+(t*=1.525))*e+t)+2)},easeInBounce:function(e){return 1-S.easeOutBounce(1-e)},easeOutBounce:function(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375},easeInOutBounce:function(e){return e<.5?.5*S.easeInBounce(2*e):.5*S.easeOutBounce(2*e-1)+.5}},E={effects:S};M.easingEffects=S;var L=Math.PI,D=L/180,C=2*L,O=L/2,T=L/4,j=2*L/3,A={clear:function(e){e.ctx.clearRect(0,0,e.width,e.height)},roundedRect:function(e,t,r,n,o,a){if(a){var i=Math.min(a,o/2,n/2),s=t+i,c=r+i,l=t+n-i,d=r+o-i;e.moveTo(t,c),st.left-1e-6&&e.xt.top-1e-6&&e.y0&&this.requestAnimationFrame()},advance:function(){for(var e,t,r,n,o=this.animations,a=0;a=r?(H.callback(e.onAnimationComplete,[e],t),t.animating=!1,o.splice(a,1)):++a}},$=H.options.resolve,X=["push","pop","shift","splice","unshift"];function ee(e,t){var r=e._chartjs;if(r){var n=r.listeners,o=n.indexOf(t);-1!==o&&n.splice(o,1),n.length>0||(X.forEach((function(t){delete e[t]})),delete e._chartjs)}}var te=function(e,t){this.initialize(e,t)};H.extend(te.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(e,t){var r=this;r.chart=e,r.index=t,r.linkScales(),r.addElements(),r._type=r.getMeta().type},updateIndex:function(e){this.index=e},linkScales:function(){var e=this.getMeta(),t=this.chart,r=t.scales,n=this.getDataset(),o=t.options.scales;null!==e.xAxisID&&e.xAxisID in r&&!n.xAxisID||(e.xAxisID=n.xAxisID||o.xAxes[0].id),null!==e.yAxisID&&e.yAxisID in r&&!n.yAxisID||(e.yAxisID=n.yAxisID||o.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(e){return this.chart.scales[e]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&ee(this._data,this)},createMetaDataset:function(){var e=this.datasetElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(e){var t=this.dataElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index,_index:e})},addElements:function(){var e,t,r=this.getMeta(),n=this.getDataset().data||[],o=r.data;for(e=0,t=n.length;er&&this.insertElements(r,n-r)},insertElements:function(e,t){for(var r=0;ro?(a=o/t.innerRadius,e.arc(i,s,t.innerRadius-o,n+a,r-a,!0)):e.arc(i,s,o,n+Math.PI/2,r-Math.PI/2),e.closePath(),e.clip()}function ae(e,t,r){var n="inner"===t.borderAlign;n?(e.lineWidth=2*t.borderWidth,e.lineJoin="round"):(e.lineWidth=t.borderWidth,e.lineJoin="bevel"),r.fullCircles&&function(e,t,r,n){var o,a=r.endAngle;for(n&&(r.endAngle=r.startAngle+ne,oe(e,r),r.endAngle=a,r.endAngle===r.startAngle&&r.fullCircles&&(r.endAngle+=ne,r.fullCircles--)),e.beginPath(),e.arc(r.x,r.y,r.innerRadius,r.startAngle+ne,r.startAngle,!0),o=0;os;)o-=ne;for(;o=i&&o<=s,l=a>=r.innerRadius&&a<=r.outerRadius;return c&&l}return!1},getCenterPoint:function(){var e=this._view,t=(e.startAngle+e.endAngle)/2,r=(e.innerRadius+e.outerRadius)/2;return{x:e.x+Math.cos(t)*r,y:e.y+Math.sin(t)*r}},getArea:function(){var e=this._view;return Math.PI*((e.endAngle-e.startAngle)/(2*Math.PI))*(Math.pow(e.outerRadius,2)-Math.pow(e.innerRadius,2))},tooltipPosition:function(){var e=this._view,t=e.startAngle+(e.endAngle-e.startAngle)/2,r=(e.outerRadius-e.innerRadius)/2+e.innerRadius;return{x:e.x+Math.cos(t)*r,y:e.y+Math.sin(t)*r}},draw:function(){var e,t=this._chart.ctx,r=this._view,n="inner"===r.borderAlign?.33:0,o={x:r.x,y:r.y,innerRadius:r.innerRadius,outerRadius:Math.max(r.outerRadius-n,0),pixelMargin:n,startAngle:r.startAngle,endAngle:r.endAngle,fullCircles:Math.floor(r.circumference/ne)};if(t.save(),t.fillStyle=r.backgroundColor,t.strokeStyle=r.borderColor,o.fullCircles){for(o.endAngle=o.startAngle+ne,t.beginPath(),t.arc(o.x,o.y,o.outerRadius,o.startAngle,o.endAngle),t.arc(o.x,o.y,o.innerRadius,o.endAngle,o.startAngle,!0),t.closePath(),e=0;ee.x&&(t=be(t,"left","right")):e.baser?r:n,r:c.right||o<0?0:o>t?t:o,b:c.bottom||a<0?0:a>r?r:a,l:c.left||i<0?0:i>t?t:i}}function ye(e,t,r){var n=null===t,o=null===r,a=!(!e||n&&o)&&ge(e);return a&&(n||t>=a.left&&t<=a.right)&&(o||r>=a.top&&r<=a.bottom)}I._set("global",{elements:{rectangle:{backgroundColor:me,borderColor:me,borderSkipped:"bottom",borderWidth:0}}});var _e=G.extend({_type:"rectangle",draw:function(){var e=this._chart.ctx,t=this._view,r=function(e){var t=ge(e),r=t.right-t.left,n=t.bottom-t.top,o=ve(e,r/2,n/2);return{outer:{x:t.left,y:t.top,w:r,h:n},inner:{x:t.left+o.l,y:t.top+o.t,w:r-o.l-o.r,h:n-o.t-o.b}}}(t),n=r.outer,o=r.inner;e.fillStyle=t.backgroundColor,e.fillRect(n.x,n.y,n.w,n.h),n.w===o.w&&n.h===o.h||(e.save(),e.beginPath(),e.rect(n.x,n.y,n.w,n.h),e.clip(),e.fillStyle=t.borderColor,e.rect(o.x,o.y,o.w,o.h),e.fill("evenodd"),e.restore())},height:function(){var e=this._view;return e.base-e.y},inRange:function(e,t){return ye(this._view,e,t)},inLabelRange:function(e,t){var r=this._view;return he(r)?ye(r,e,null):ye(r,null,t)},inXRange:function(e){return ye(this._view,e,null)},inYRange:function(e){return ye(this._view,null,e)},getCenterPoint:function(){var e,t,r=this._view;return he(r)?(e=r.x,t=(r.y+r.base)/2):(e=(r.x+r.base)/2,t=r.y),{x:e,y:t}},getArea:function(){var e=this._view;return he(e)?e.width*Math.abs(e.y-e.base):e.height*Math.abs(e.x-e.base)},tooltipPosition:function(){var e=this._view;return{x:e.x,y:e.y}}}),we={},ke=ie,xe=le,Me=fe,Se=_e;we.Arc=ke,we.Line=xe,we.Point=Me,we.Rectangle=Se;var Ee=H._deprecated,Le=H.valueOrDefault;function De(e,t,r){var n,o,a=r.barThickness,i=t.stackCount,s=t.pixels[e],c=H.isNullOrUndef(a)?function(e,t){var r,n,o,a,i=e._length;for(o=1,a=t.length;o0?Math.min(i,Math.abs(n-r)):i,r=n;return i}(t.scale,t.pixels):-1;return H.isNullOrUndef(a)?(n=c*r.categoryPercentage,o=r.barPercentage):(n=a*i,o=1),{chunk:n/i,ratio:o,start:s-n/2}}I._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),I._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var Ce=re.extend({dataElementType:we.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var e,t,r=this;re.prototype.initialize.apply(r,arguments),(e=r.getMeta()).stack=r.getDataset().stack,e.bar=!0,t=r._getIndexScale().options,Ee("bar chart",t.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),Ee("bar chart",t.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),Ee("bar chart",t.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),Ee("bar chart",r._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),Ee("bar chart",t.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(e){var t,r,n=this.getMeta().data;for(this._ruler=this.getRuler(),t=0,r=n.length;t=0&&h.min>=0?h.min:h.max,_=void 0===h.start?h.end:h.max>=0&&h.min>=0?h.max-h.min:h.min-h.max,w=m.length;if(b||void 0===b&&void 0!==v)for(n=0;n=0&&l.max>=0?l.max:l.min,(h.min<0&&a<0||h.max>=0&&a>0)&&(y+=a));return i=u.getPixelForValue(y),c=(s=u.getPixelForValue(y+_))-i,void 0!==g&&Math.abs(c)=0&&!p||_<0&&p?i-g:i+g),{size:c,base:i,head:s,center:s+c/2}},calculateBarIndexPixels:function(e,t,r,n){var o="flex"===n.barThickness?function(e,t,r){var n,o=t.pixels,a=o[e],i=e>0?o[e-1]:null,s=e=Ne?-Pe:v<-Ne?Pe:0)+g,_=Math.cos(v),w=Math.sin(v),k=Math.cos(y),x=Math.sin(y),M=v<=0&&y>=0||y>=Pe,S=v<=Ie&&y>=Ie||y>=Pe+Ie,E=v<=-Ie&&y>=-Ie||y>=Ne+Ie,L=v===-Ne||y>=Ne?-1:Math.min(_,_*h,k,k*h),D=E?-1:Math.min(w,w*h,x,x*h),C=M?1:Math.max(_,_*h,k,k*h),O=S?1:Math.max(w,w*h,x,x*h);l=(C-L)/2,d=(O-D)/2,u=-(C+L)/2,p=-(O+D)/2}for(n=0,o=m.length;n0&&!isNaN(e)?Pe*(Math.abs(e)/t):0},getMaxBorderWidth:function(e){var t,r,n,o,a,i,s,c,l=0,d=this.chart;if(!e)for(t=0,r=d.data.datasets.length;t(l=s>l?s:l)?c:l);return l},setHoverStyle:function(e){var t=e._model,r=e._options,n=H.getHoverColor;e.$previousStyle={backgroundColor:t.backgroundColor,borderColor:t.borderColor,borderWidth:t.borderWidth},t.backgroundColor=Ae(r.hoverBackgroundColor,n(r.backgroundColor)),t.borderColor=Ae(r.hoverBorderColor,n(r.borderColor)),t.borderWidth=Ae(r.hoverBorderWidth,r.borderWidth)},_getRingWeightOffset:function(e){for(var t=0,r=0;r0&&He(c[e-1]._model,s)&&(r.controlPointPreviousX=l(r.controlPointPreviousX,s.left,s.right),r.controlPointPreviousY=l(r.controlPointPreviousY,s.top,s.bottom)),e0&&(a=e.getDatasetMeta(a[0]._datasetIndex).data),a},"x-axis":function(e,t){return nt(e,t,{intersect:!1})},point:function(e,t){return et(e,$e(t,e))},nearest:function(e,t,r){var n=$e(t,e);r.axis=r.axis||"xy";var o=rt(r.axis);return tt(e,n,r.intersect,o)},x:function(e,t,r){var n=$e(t,e),o=[],a=!1;return Xe(e,(function(e){e.inXRange(n.x)&&o.push(e),e.inRange(n.x,n.y)&&(a=!0)})),r.intersect&&!a&&(o=[]),o},y:function(e,t,r){var n=$e(t,e),o=[],a=!1;return Xe(e,(function(e){e.inYRange(n.y)&&o.push(e),e.inRange(n.x,n.y)&&(a=!0)})),r.intersect&&!a&&(o=[]),o}}},at=H.extend;function it(e,t){return H.where(e,(function(e){return e.pos===t}))}function st(e,t){return e.sort((function(e,r){var n=t?r:e,o=t?e:r;return n.weight===o.weight?n.index-o.index:n.weight-o.weight}))}function ct(e,t,r,n){return Math.max(e[r],t[r])+Math.max(e[n],t[n])}function lt(e,t,r){var n,o,a=r.box,i=e.maxPadding;if(r.size&&(e[r.pos]-=r.size),r.size=r.horizontal?a.height:a.width,e[r.pos]+=r.size,a.getPadding){var s=a.getPadding();i.top=Math.max(i.top,s.top),i.left=Math.max(i.left,s.left),i.bottom=Math.max(i.bottom,s.bottom),i.right=Math.max(i.right,s.right)}if(n=t.outerWidth-ct(i,e,"left","right"),o=t.outerHeight-ct(i,e,"top","bottom"),n!==e.w||o!==e.h)return e.w=n,e.h=o,r.horizontal?n!==e.w:o!==e.h}function dt(e,t){var r=t.maxPadding;function n(e){var n={left:0,top:0,right:0,bottom:0};return e.forEach((function(e){n[e]=Math.max(t[e],r[e])})),n}return n(e?["left","right"]:["top","bottom"])}function ut(e,t,r){var n,o,a,i,s,c,l=[];for(n=0,o=e.length;n div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n"}))&&ft.default||ft,gt=["animationstart","webkitAnimationStart"],bt={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function vt(e,t){var r=H.getStyle(e,t),n=r&&r.match(/^(\d+)(\.\d+)?px$/);return n?Number(n[1]):void 0}var yt=!!function(){var e=!1;try{var t=Object.defineProperty({},"passive",{get:function(){e=!0}});window.addEventListener("e",null,t)}catch(e){}return e}()&&{passive:!0};function _t(e,t,r){e.addEventListener(t,r,yt)}function wt(e,t,r){e.removeEventListener(t,r,yt)}function kt(e,t,r,n,o){return{type:e,chart:t,native:o||null,x:void 0!==r?r:null,y:void 0!==n?n:null}}function xt(e){var t=document.createElement("div");return t.className=e||"",t}function Mt(e,t,r){var n,o,a,i,s=e.$chartjs||(e.$chartjs={}),c=s.resizer=function(e){var t=xt("chartjs-size-monitor"),r=xt("chartjs-size-monitor-expand"),n=xt("chartjs-size-monitor-shrink");r.appendChild(xt()),n.appendChild(xt()),t.appendChild(r),t.appendChild(n),t._reset=function(){r.scrollLeft=1e6,r.scrollTop=1e6,n.scrollLeft=1e6,n.scrollTop=1e6};var o=function(){t._reset(),e()};return _t(r,"scroll",o.bind(r,"expand")),_t(n,"scroll",o.bind(n,"shrink")),t}((n=function(){if(s.resizer){var n=r.options.maintainAspectRatio&&e.parentNode,o=n?n.clientWidth:0;t(kt("resize",r)),n&&n.clientWidth0){var a=e[0];a.label?r=a.label:a.xLabel?r=a.xLabel:o>0&&a.index-1?e.split("\n"):e}function It(e){var t=I.global;return{xPadding:e.xPadding,yPadding:e.yPadding,xAlign:e.xAlign,yAlign:e.yAlign,rtl:e.rtl,textDirection:e.textDirection,bodyFontColor:e.bodyFontColor,_bodyFontFamily:Tt(e.bodyFontFamily,t.defaultFontFamily),_bodyFontStyle:Tt(e.bodyFontStyle,t.defaultFontStyle),_bodyAlign:e.bodyAlign,bodyFontSize:Tt(e.bodyFontSize,t.defaultFontSize),bodySpacing:e.bodySpacing,titleFontColor:e.titleFontColor,_titleFontFamily:Tt(e.titleFontFamily,t.defaultFontFamily),_titleFontStyle:Tt(e.titleFontStyle,t.defaultFontStyle),titleFontSize:Tt(e.titleFontSize,t.defaultFontSize),_titleAlign:e.titleAlign,titleSpacing:e.titleSpacing,titleMarginBottom:e.titleMarginBottom,footerFontColor:e.footerFontColor,_footerFontFamily:Tt(e.footerFontFamily,t.defaultFontFamily),_footerFontStyle:Tt(e.footerFontStyle,t.defaultFontStyle),footerFontSize:Tt(e.footerFontSize,t.defaultFontSize),_footerAlign:e.footerAlign,footerSpacing:e.footerSpacing,footerMarginTop:e.footerMarginTop,caretSize:e.caretSize,cornerRadius:e.cornerRadius,backgroundColor:e.backgroundColor,opacity:0,legendColorBackground:e.multiKeyBackground,displayColors:e.displayColors,borderColor:e.borderColor,borderWidth:e.borderWidth}}function Yt(e,t){return"center"===t?e.x+e.width/2:"right"===t?e.x+e.width-e.xPadding:e.x+e.xPadding}function zt(e){return Nt([],Pt(e))}var Ft=G.extend({initialize:function(){this._model=It(this._options),this._lastActive=[]},getTitle:function(){var e=this,t=e._options,r=t.callbacks,n=r.beforeTitle.apply(e,arguments),o=r.title.apply(e,arguments),a=r.afterTitle.apply(e,arguments),i=[];return i=Nt(i,Pt(n)),i=Nt(i,Pt(o)),i=Nt(i,Pt(a))},getBeforeBody:function(){return zt(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(e,t){var r=this,n=r._options.callbacks,o=[];return H.each(e,(function(e){var a={before:[],lines:[],after:[]};Nt(a.before,Pt(n.beforeLabel.call(r,e,t))),Nt(a.lines,n.label.call(r,e,t)),Nt(a.after,Pt(n.afterLabel.call(r,e,t))),o.push(a)})),o},getAfterBody:function(){return zt(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var e=this,t=e._options.callbacks,r=t.beforeFooter.apply(e,arguments),n=t.footer.apply(e,arguments),o=t.afterFooter.apply(e,arguments),a=[];return a=Nt(a,Pt(r)),a=Nt(a,Pt(n)),a=Nt(a,Pt(o))},update:function(e){var t,r,n,o,a,i,s,c,l,d,u=this,p=u._options,f=u._model,m=u._model=It(p),h=u._active,g=u._data,b={xAlign:f.xAlign,yAlign:f.yAlign},v={x:f.x,y:f.y},y={width:f.width,height:f.height},_={x:f.caretX,y:f.caretY};if(h.length){m.opacity=1;var w=[],k=[];_=At[p.position].call(u,h,u._eventPosition);var x=[];for(t=0,r=h.length;tn.width&&(o=n.width-t.width),o<0&&(o=0)),"top"===d?a+=u:a-="bottom"===d?t.height+u:t.height/2,"center"===d?"left"===l?o+=u:"right"===l&&(o-=u):"left"===l?o-=p:"right"===l&&(o+=p),{x:o,y:a}}(m,y,b=function(e,t){var r,n,o,a,i,s=e._model,c=e._chart,l=e._chart.chartArea,d="center",u="center";s.yc.height-t.height&&(u="bottom");var p=(l.left+l.right)/2,f=(l.top+l.bottom)/2;"center"===u?(r=function(e){return e<=p},n=function(e){return e>p}):(r=function(e){return e<=t.width/2},n=function(e){return e>=c.width-t.width/2}),o=function(e){return e+t.width+s.caretSize+s.caretPadding>c.width},a=function(e){return e-t.width-s.caretSize-s.caretPadding<0},i=function(e){return e<=f?"top":"bottom"},r(s.x)?(d="left",o(s.x)&&(d="center",u=i(s.y))):n(s.x)&&(d="right",a(s.x)&&(d="center",u=i(s.y)));var m=e._options;return{xAlign:m.xAlign?m.xAlign:d,yAlign:m.yAlign?m.yAlign:u}}(this,y),u._chart)}else m.opacity=0;return m.xAlign=b.xAlign,m.yAlign=b.yAlign,m.x=v.x,m.y=v.y,m.width=y.width,m.height=y.height,m.caretX=_.x,m.caretY=_.y,u._model=m,e&&p.custom&&p.custom.call(u,m),u},drawCaret:function(e,t){var r=this._chart.ctx,n=this._view,o=this.getCaretPosition(e,t,n);r.lineTo(o.x1,o.y1),r.lineTo(o.x2,o.y2),r.lineTo(o.x3,o.y3)},getCaretPosition:function(e,t,r){var n,o,a,i,s,c,l=r.caretSize,d=r.cornerRadius,u=r.xAlign,p=r.yAlign,f=e.x,m=e.y,h=t.width,g=t.height;if("center"===p)s=m+g/2,"left"===u?(o=(n=f)-l,a=n,i=s+l,c=s-l):(o=(n=f+h)+l,a=n,i=s-l,c=s+l);else if("left"===u?(n=(o=f+d+l)-l,a=o+l):"right"===u?(n=(o=f+h-d-l)-l,a=o+l):(n=(o=r.caretX)-l,a=o+l),"top"===p)s=(i=m)-l,c=i;else{s=(i=m+g)+l,c=i;var b=a;a=n,n=b}return{x1:n,x2:o,x3:a,y1:i,y2:s,y3:c}},drawTitle:function(e,t,r){var n,o,a,i=t.title,s=i.length;if(s){var c=jt(t.rtl,t.x,t.width);for(e.x=Yt(t,t._titleAlign),r.textAlign=c.textAlign(t._titleAlign),r.textBaseline="middle",n=t.titleFontSize,o=t.titleSpacing,r.fillStyle=t.titleFontColor,r.font=H.fontString(n,t._titleFontStyle,t._titleFontFamily),a=0;a0&&r.stroke()},draw:function(){var e=this._chart.ctx,t=this._view;if(0!==t.opacity){var r={width:t.width,height:t.height},n={x:t.x,y:t.y},o=Math.abs(t.opacity<.001)?0:t.opacity,a=t.title.length||t.beforeBody.length||t.body.length||t.afterBody.length||t.footer.length;this._options.enabled&&a&&(e.save(),e.globalAlpha=o,this.drawBackground(n,t,e,r),n.y+=t.yPadding,H.rtl.overrideTextDirection(e,t.textDirection),this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),H.rtl.restoreTextDirection(e,t.textDirection),e.restore())}},handleEvent:function(e){var t,r=this,n=r._options;return r._lastActive=r._lastActive||[],"mouseout"===e.type?r._active=[]:(r._active=r._chart.getElementsAtEventForMode(e,n.mode,n),n.reverse&&r._active.reverse()),(t=!H.arrayEquals(r._active,r._lastActive))&&(r._lastActive=r._active,(n.enabled||n.custom)&&(r._eventPosition={x:e.x,y:e.y},r.update(!0),r.pivot())),t}}),Rt=At,Ht=Ft;Ht.positioners=Rt;var Bt=H.valueOrDefault;function Ut(){return H.merge({},[].slice.call(arguments),{merger:function(e,t,r,n){if("xAxes"===e||"yAxes"===e){var o,a,i,s=r[e].length;for(t[e]||(t[e]=[]),o=0;o=t[e].length&&t[e].push({}),!t[e][o].type||i.type&&i.type!==t[e][o].type?H.merge(t[e][o],[Ot.getScaleDefaults(a),i]):H.merge(t[e][o],i)}else H._merger(e,t,r,n)}})}function Wt(){return H.merge({},[].slice.call(arguments),{merger:function(e,t,r,n){var o=t[e]||{},a=r[e];"scales"===e?t[e]=Ut(o,a):"scale"===e?t[e]=H.merge(o,[Ot.getScaleDefaults(a.type),a]):H._merger(e,t,r,n)}})}function qt(e){var t=e.options;H.each(e.scales,(function(t){mt.removeBox(e,t)})),t=Wt(I.global,I[e.config.type],t),e.options=e.config.options=t,e.ensureScalesHaveIDs(),e.buildOrUpdateScales(),e.tooltip._options=t.tooltips,e.tooltip.initialize()}function Vt(e,t,r){var n,o=function(e){return e.id===n};do{n=t+r++}while(H.findIndex(e,o)>=0);return n}function Kt(e){return"top"===e||"bottom"===e}function Gt(e,t){return function(r,n){return r[e]===n[e]?r[t]-n[t]:r[e]-n[e]}}I._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var Qt=function(e,t){return this.construct(e,t),this};H.extend(Qt.prototype,{construct:function(e,t){var r=this;t=function(e){var t=(e=e||{}).data=e.data||{};return t.datasets=t.datasets||[],t.labels=t.labels||[],e.options=Wt(I.global,I[e.type],e.options||{}),e}(t);var n=Dt.acquireContext(e,t),o=n&&n.canvas,a=o&&o.height,i=o&&o.width;r.id=H.uid(),r.ctx=n,r.canvas=o,r.config=t,r.width=i,r.height=a,r.aspectRatio=a?i/a:null,r.options=t.options,r._bufferedRender=!1,r._layers=[],r.chart=r,r.controller=r,Qt.instances[r.id]=r,Object.defineProperty(r,"data",{get:function(){return r.config.data},set:function(e){r.config.data=e}}),n&&o?(r.initialize(),r.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var e=this;return Ct.notify(e,"beforeInit"),H.retinaScale(e,e.options.devicePixelRatio),e.bindEvents(),e.options.responsive&&e.resize(!0),e.initToolTip(),Ct.notify(e,"afterInit"),e},clear:function(){return H.canvas.clear(this),this},stop:function(){return Z.cancelAnimation(this),this},resize:function(e){var t=this,r=t.options,n=t.canvas,o=r.maintainAspectRatio&&t.aspectRatio||null,a=Math.max(0,Math.floor(H.getMaximumWidth(n))),i=Math.max(0,Math.floor(o?a/o:H.getMaximumHeight(n)));if((t.width!==a||t.height!==i)&&(n.width=t.width=a,n.height=t.height=i,n.style.width=a+"px",n.style.height=i+"px",H.retinaScale(t,r.devicePixelRatio),!e)){var s={width:a,height:i};Ct.notify(t,"resize",[s]),r.onResize&&r.onResize(t,s),t.stop(),t.update({duration:r.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var e=this.options,t=e.scales||{},r=e.scale;H.each(t.xAxes,(function(e,r){e.id||(e.id=Vt(t.xAxes,"x-axis-",r))})),H.each(t.yAxes,(function(e,r){e.id||(e.id=Vt(t.yAxes,"y-axis-",r))})),r&&(r.id=r.id||"scale")},buildOrUpdateScales:function(){var e=this,t=e.options,r=e.scales||{},n=[],o=Object.keys(r).reduce((function(e,t){return e[t]=!1,e}),{});t.scales&&(n=n.concat((t.scales.xAxes||[]).map((function(e){return{options:e,dtype:"category",dposition:"bottom"}})),(t.scales.yAxes||[]).map((function(e){return{options:e,dtype:"linear",dposition:"left"}})))),t.scale&&n.push({options:t.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),H.each(n,(function(t){var n=t.options,a=n.id,i=Bt(n.type,t.dtype);Kt(n.position)!==Kt(t.dposition)&&(n.position=t.dposition),o[a]=!0;var s=null;if(a in r&&r[a].type===i)(s=r[a]).options=n,s.ctx=e.ctx,s.chart=e;else{var c=Ot.getScaleConstructor(i);if(!c)return;s=new c({id:a,type:i,options:n,ctx:e.ctx,chart:e}),r[s.id]=s}s.mergeTicksOptions(),t.isDefault&&(e.scale=s)})),H.each(o,(function(e,t){e||delete r[t]})),e.scales=r,Ot.addScalesToLayout(this)},buildOrUpdateControllers:function(){var e,t,r=this,n=[],o=r.data.datasets;for(e=0,t=o.length;e=0;--r)this.drawDataset(t[r],e);Ct.notify(this,"afterDatasetsDraw",[e])}},drawDataset:function(e,t){var r={meta:e,index:e.index,easingValue:t};!1!==Ct.notify(this,"beforeDatasetDraw",[r])&&(e.controller.draw(t),Ct.notify(this,"afterDatasetDraw",[r]))},_drawTooltip:function(e){var t=this.tooltip,r={tooltip:t,easingValue:e};!1!==Ct.notify(this,"beforeTooltipDraw",[r])&&(t.draw(),Ct.notify(this,"afterTooltipDraw",[r]))},getElementAtEvent:function(e){return ot.modes.single(this,e)},getElementsAtEvent:function(e){return ot.modes.label(this,e,{intersect:!0})},getElementsAtXAxis:function(e){return ot.modes["x-axis"](this,e,{intersect:!0})},getElementsAtEventForMode:function(e,t,r){var n=ot.modes[t];return"function"==typeof n?n(this,e,r):[]},getDatasetAtEvent:function(e){return ot.modes.dataset(this,e,{intersect:!0})},getDatasetMeta:function(e){var t=this.data.datasets[e];t._meta||(t._meta={});var r=t._meta[this.id];return r||(r=t._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:t.order||0,index:e}),r},getVisibleDatasetCount:function(){for(var e=0,t=0,r=this.data.datasets.length;t3?r[2]-r[1]:r[1]-r[0];Math.abs(n)>1&&e!==Math.floor(e)&&(n=e-Math.floor(e));var o=H.log10(Math.abs(n)),a="";if(0!==e)if(Math.max(Math.abs(r[0]),Math.abs(r[r.length-1]))<1e-4){var i=H.log10(Math.abs(e)),s=Math.floor(i)-Math.floor(o);s=Math.max(Math.min(s,20),0),a=e.toExponential(s)}else{var c=-1*Math.floor(o);c=Math.max(Math.min(c,20),0),a=e.toFixed(c)}else a="0";return a},logarithmic:function(e,t,r){var n=e/Math.pow(10,Math.floor(H.log10(e)));return 0===e?"0":1===n||2===n||5===n||0===t||t===r.length-1?e.toExponential():""}}},tr=H.isArray,rr=H.isNullOrUndef,nr=H.valueOrDefault,or=H.valueAtIndexOrDefault;function ar(e,t,r){var n,o=e.getTicks().length,a=Math.min(t,o-1),i=e.getPixelForTick(a),s=e._startPixel,c=e._endPixel;if(!(r&&(n=1===o?Math.max(i-s,c-i):0===t?(e.getPixelForTick(1)-i)/2:(i-e.getPixelForTick(a-1))/2,(i+=ac+1e-6)))return i}function ir(e,t,r,n){var o,a,i,s,c,l,d,u,p,f,m,h,g,b=r.length,v=[],y=[],_=[];for(o=0;ot){for(r=0;r=p||d<=1||!s.isHorizontal()?s.labelRotation=u:(t=(e=s._getLabelSizes()).widest.width,r=e.highest.height-e.highest.offset,n=Math.min(s.maxWidth,s.chart.width-t),t+6>(o=c.offset?s.maxWidth/d:n/(d-1))&&(o=n/(d-(c.offset?.5:1)),a=s.maxHeight-sr(c.gridLines)-l.padding-cr(c.scaleLabel),i=Math.sqrt(t*t+r*r),f=H.toDegrees(Math.min(Math.asin(Math.min((e.highest.height+6)/o,1)),Math.asin(Math.min(a/i,1))-Math.asin(r/i))),f=Math.max(u,Math.min(p,f))),s.labelRotation=f)},afterCalculateTickRotation:function(){H.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){H.callback(this.options.beforeFit,[this])},fit:function(){var e=this,t=e.minSize={width:0,height:0},r=e.chart,n=e.options,o=n.ticks,a=n.scaleLabel,i=n.gridLines,s=e._isVisible(),c="bottom"===n.position,l=e.isHorizontal();if(l?t.width=e.maxWidth:s&&(t.width=sr(i)+cr(a)),l?s&&(t.height=sr(i)+cr(a)):t.height=e.maxHeight,o.display&&s){var d=dr(o),u=e._getLabelSizes(),p=u.first,f=u.last,m=u.widest,h=u.highest,g=.4*d.minor.lineHeight,b=o.padding;if(l){var v=0!==e.labelRotation,y=H.toRadians(e.labelRotation),_=Math.cos(y),w=Math.sin(y),k=w*m.width+_*(h.height-(v?h.offset:0))+(v?0:g);t.height=Math.min(e.maxHeight,t.height+k+b);var x,M,S=e.getPixelForTick(0)-e.left,E=e.right-e.getPixelForTick(e.getTicks().length-1);v?(x=c?_*p.width+w*p.offset:w*(p.height-p.offset),M=c?w*(f.height-f.offset):_*f.width+w*f.offset):(x=p.width/2,M=f.width/2),e.paddingLeft=Math.max((x-S)*e.width/(e.width-S),0)+3,e.paddingRight=Math.max((M-E)*e.width/(e.width-E),0)+3}else{var L=o.mirror?0:m.width+b+g;t.width=Math.min(e.maxWidth,t.width+L),e.paddingTop=p.height/2,e.paddingBottom=f.height/2}}e.handleMargins(),l?(e.width=e._length=r.width-e.margins.left-e.margins.right,e.height=t.height):(e.width=t.width,e.height=e._length=r.height-e.margins.top-e.margins.bottom)},handleMargins:function(){var e=this;e.margins&&(e.margins.left=Math.max(e.paddingLeft,e.margins.left),e.margins.top=Math.max(e.paddingTop,e.margins.top),e.margins.right=Math.max(e.paddingRight,e.margins.right),e.margins.bottom=Math.max(e.paddingBottom,e.margins.bottom))},afterFit:function(){H.callback(this.options.afterFit,[this])},isHorizontal:function(){var e=this.options.position;return"top"===e||"bottom"===e},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(e){if(rr(e))return NaN;if(("number"==typeof e||e instanceof Number)&&!isFinite(e))return NaN;if(e)if(this.isHorizontal()){if(void 0!==e.x)return this.getRightValue(e.x)}else if(void 0!==e.y)return this.getRightValue(e.y);return e},_convertTicksToLabels:function(e){var t,r,n,o=this;for(o.ticks=e.map((function(e){return e.value})),o.beforeTickToLabelConversion(),t=o.convertTicksToLabels(e)||o.ticks,o.afterTickToLabelConversion(),r=0,n=e.length;rr-1?null:this.getPixelForDecimal(e*n+(t?n/2:0))},getPixelForDecimal:function(e){return this._reversePixels&&(e=1-e),this._startPixel+e*this._length},getDecimalForPixel:function(e){var t=(e-this._startPixel)/this._length;return this._reversePixels?1-t:t},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var e=this.min,t=this.max;return this.beginAtZero?0:e<0&&t<0?t:e>0&&t>0?e:0},_autoSkip:function(e){var t,r,n,o,a=this.options.ticks,i=this._length,s=a.maxTicksLimit||i/this._tickSize()+1,c=a.major.enabled?function(e){var t,r,n=[];for(t=0,r=e.length;ts)return function(e,t,r){var n,o,a=0,i=t[0];for(r=Math.ceil(r),n=0;nl)return a;return Math.max(l,1)}(c,e,0,s),l>0){for(t=0,r=l-1;t1?(u-d)/(l-1):null,pr(e,n,H.isNullOrUndef(o)?0:d-o,d),pr(e,n,u,H.isNullOrUndef(o)?e.length:u+o),ur(e)}return pr(e,n),ur(e)},_tickSize:function(){var e=this.options.ticks,t=H.toRadians(this.labelRotation),r=Math.abs(Math.cos(t)),n=Math.abs(Math.sin(t)),o=this._getLabelSizes(),a=e.autoSkipPadding||0,i=o?o.widest.width+a:0,s=o?o.highest.height+a:0;return this.isHorizontal()?s*r>i*n?i/r:s/n:s*n=0&&(i=e),void 0!==a&&(e=r.indexOf(a))>=0&&(s=e),t.minIndex=i,t.maxIndex=s,t.min=r[i],t.max=r[s]},buildTicks:function(){var e=this._getLabels(),t=this.minIndex,r=this.maxIndex;this.ticks=0===t&&r===e.length-1?e:e.slice(t,r+1)},getLabelForIndex:function(e,t){var r=this.chart;return r.getDatasetMeta(t).controller._getValueScaleId()===this.id?this.getRightValue(r.data.datasets[t].data[e]):this._getLabels()[e]},_configure:function(){var e=this,t=e.options.offset,r=e.ticks;mr.prototype._configure.call(e),e.isHorizontal()||(e._reversePixels=!e._reversePixels),r&&(e._startValue=e.minIndex-(t?.5:0),e._valueRange=Math.max(r.length-(t?0:1),1))},getPixelForValue:function(e,t,r){var n,o,a,i=this;return hr(t)||hr(r)||(e=i.chart.data.datasets[r].data[t]),hr(e)||(n=i.isHorizontal()?e.x:e.y),(void 0!==n||void 0!==e&&isNaN(t))&&(o=i._getLabels(),e=H.valueOrDefault(n,e),t=-1!==(a=o.indexOf(e))?a:t,isNaN(t)&&(t=e)),i.getPixelForDecimal((t-i._startValue)/i._valueRange)},getPixelForTick:function(e){var t=this.ticks;return e<0||e>t.length-1?null:this.getPixelForValue(t[e],e+this.minIndex)},getValueForPixel:function(e){var t=Math.round(this._startValue+this.getDecimalForPixel(e)*this._valueRange);return Math.min(Math.max(t,0),this.ticks.length-1)},getBasePixel:function(){return this.bottom}}),br={position:"bottom"};gr._defaults=br;var vr=H.noop,yr=H.isNullOrUndef,_r=mr.extend({getRightValue:function(e){return"string"==typeof e?+e:mr.prototype.getRightValue.call(this,e)},handleTickRangeOptions:function(){var e=this,t=e.options.ticks;if(t.beginAtZero){var r=H.sign(e.min),n=H.sign(e.max);r<0&&n<0?e.max=0:r>0&&n>0&&(e.min=0)}var o=void 0!==t.min||void 0!==t.suggestedMin,a=void 0!==t.max||void 0!==t.suggestedMax;void 0!==t.min?e.min=t.min:void 0!==t.suggestedMin&&(null===e.min?e.min=t.suggestedMin:e.min=Math.min(e.min,t.suggestedMin)),void 0!==t.max?e.max=t.max:void 0!==t.suggestedMax&&(null===e.max?e.max=t.suggestedMax:e.max=Math.max(e.max,t.suggestedMax)),o!==a&&e.min>=e.max&&(o?e.max=e.min+1:e.min=e.max-1),e.min===e.max&&(e.max++,t.beginAtZero||e.min--)},getTickLimit:function(){var e,t=this.options.ticks,r=t.stepSize,n=t.maxTicksLimit;return r?e=Math.ceil(this.max/r)-Math.floor(this.min/r)+1:(e=this._computeTickLimit(),n=n||11),n&&(e=Math.min(n,e)),e},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:vr,buildTicks:function(){var e=this,t=e.options.ticks,r=e.getTickLimit(),n={maxTicks:r=Math.max(2,r),min:t.min,max:t.max,precision:t.precision,stepSize:H.valueOrDefault(t.fixedStepSize,t.stepSize)},o=e.ticks=function(e,t){var r,n,o,a,i=[],s=e.stepSize,c=s||1,l=e.maxTicks-1,d=e.min,u=e.max,p=e.precision,f=t.min,m=t.max,h=H.niceNum((m-f)/l/c)*c;if(h<1e-14&&yr(d)&&yr(u))return[f,m];(a=Math.ceil(m/h)-Math.floor(f/h))>l&&(h=H.niceNum(a*h/l/c)*c),s||yr(p)?r=Math.pow(10,H._decimalPlaces(h)):(r=Math.pow(10,p),h=Math.ceil(h*r)/r),n=Math.floor(f/h)*h,o=Math.ceil(m/h)*h,s&&(!yr(d)&&H.almostWhole(d/h,h/1e3)&&(n=d),!yr(u)&&H.almostWhole(u/h,h/1e3)&&(o=u)),a=(o-n)/h,a=H.almostEquals(a,Math.round(a),h/1e3)?Math.round(a):Math.ceil(a),n=Math.round(n*r)/r,o=Math.round(o*r)/r,i.push(yr(d)?n:d);for(var g=1;gt.length-1?null:this.getPixelForValue(t[e])}}),Sr=wr;Mr._defaults=Sr;var Er=H.valueOrDefault,Lr=H.math.log10,Dr={position:"left",ticks:{callback:er.formatters.logarithmic}};function Cr(e,t){return H.isFinite(e)&&e>=0?e:t}var Or=mr.extend({determineDataLimits:function(){var e,t,r,n,o,a,i=this,s=i.options,c=i.chart,l=c.data.datasets,d=i.isHorizontal();function u(e){return d?e.xAxisID===i.id:e.yAxisID===i.id}i.min=Number.POSITIVE_INFINITY,i.max=Number.NEGATIVE_INFINITY,i.minNotZero=Number.POSITIVE_INFINITY;var p=s.stacked;if(void 0===p)for(e=0;e0){var t=H.min(e),r=H.max(e);i.min=Math.min(i.min,t),i.max=Math.max(i.max,r)}}))}else for(e=0;e0?e.minNotZero=e.min:e.max<1?e.minNotZero=Math.pow(10,Math.floor(Lr(e.max))):e.minNotZero=1)},buildTicks:function(){var e=this,t=e.options.ticks,r=!e.isHorizontal(),n={min:Cr(t.min),max:Cr(t.max)},o=e.ticks=function(e,t){var r,n,o=[],a=Er(e.min,Math.pow(10,Math.floor(Lr(t.min)))),i=Math.floor(Lr(t.max)),s=Math.ceil(t.max/Math.pow(10,i));0===a?(r=Math.floor(Lr(t.minNotZero)),n=Math.floor(t.minNotZero/Math.pow(10,r)),o.push(a),a=n*Math.pow(10,r)):(r=Math.floor(Lr(a)),n=Math.floor(a/Math.pow(10,r)));var c=r<0?Math.pow(10,Math.abs(r)):1;do{o.push(a),10==++n&&(n=1,c=++r>=0?1:c),a=Math.round(n*Math.pow(10,r)*c)/c}while(rt.length-1?null:this.getPixelForValue(t[e])},_getFirstTickValue:function(e){var t=Math.floor(Lr(e));return Math.floor(e/Math.pow(10,t))*Math.pow(10,t)},_configure:function(){var e=this,t=e.min,r=0;mr.prototype._configure.call(e),0===t&&(t=e._getFirstTickValue(e.minNotZero),r=Er(e.options.ticks.fontSize,I.global.defaultFontSize)/e._length),e._startValue=Lr(t),e._valueOffset=r,e._valueRange=(Lr(e.max)-Lr(t))/(1-r)},getPixelForValue:function(e){var t=this,r=0;return(e=+t.getRightValue(e))>t.min&&e>0&&(r=(Lr(e)-t._startValue)/t._valueRange+t._valueOffset),t.getPixelForDecimal(r)},getValueForPixel:function(e){var t=this,r=t.getDecimalForPixel(e);return 0===r&&0===t.min?0:Math.pow(10,t._startValue+(r-t._valueOffset)*t._valueRange)}}),Tr=Dr;Or._defaults=Tr;var jr=H.valueOrDefault,Ar=H.valueAtIndexOrDefault,Nr=H.options.resolve,Pr={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:er.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(e){return e}}};function Ir(e){var t=e.ticks;return t.display&&e.display?jr(t.fontSize,I.global.defaultFontSize)+2*t.backdropPaddingY:0}function Yr(e,t,r,n,o){return e===n||e===o?{start:t-r/2,end:t+r/2}:eo?{start:t-r,end:t}:{start:t,end:t+r}}function zr(e){return 0===e||180===e?"center":e<180?"left":"right"}function Fr(e,t,r,n){var o,a,i=r.y+n/2;if(H.isArray(t))for(o=0,a=t.length;o270||e<90)&&(r.y-=t.h)}function Hr(e){return H.isNumber(e)?e:0}var Br=_r.extend({setDimensions:function(){var e=this;e.width=e.maxWidth,e.height=e.maxHeight,e.paddingTop=Ir(e.options)/2,e.xCenter=Math.floor(e.width/2),e.yCenter=Math.floor((e.height-e.paddingTop)/2),e.drawingArea=Math.min(e.height-e.paddingTop,e.width)/2},determineDataLimits:function(){var e=this,t=e.chart,r=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY;H.each(t.data.datasets,(function(o,a){if(t.isDatasetVisible(a)){var i=t.getDatasetMeta(a);H.each(o.data,(function(t,o){var a=+e.getRightValue(t);isNaN(a)||i.data[o].hidden||(r=Math.min(a,r),n=Math.max(a,n))}))}})),e.min=r===Number.POSITIVE_INFINITY?0:r,e.max=n===Number.NEGATIVE_INFINITY?0:n,e.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Ir(this.options))},convertTicksToLabels:function(){var e=this;_r.prototype.convertTicksToLabels.call(e),e.pointLabels=e.chart.data.labels.map((function(){var t=H.callback(e.options.pointLabels.callback,arguments,e);return t||0===t?t:""}))},getLabelForIndex:function(e,t){return+this.getRightValue(this.chart.data.datasets[t].data[e])},fit:function(){var e=this.options;e.display&&e.pointLabels.display?function(e){var t,r,n,o=H.options._parseFont(e.options.pointLabels),a={l:0,r:e.width,t:0,b:e.height-e.paddingTop},i={};e.ctx.font=o.string,e._pointLabelSizes=[];var s,c,l,d=e.chart.data.labels.length;for(t=0;ta.r&&(a.r=f.end,i.r=u),m.starta.b&&(a.b=m.end,i.b=u)}e.setReductions(e.drawingArea,a,i)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(e,t,r){var n=this,o=t.l/Math.sin(r.l),a=Math.max(t.r-n.width,0)/Math.sin(r.r),i=-t.t/Math.cos(r.t),s=-Math.max(t.b-(n.height-n.paddingTop),0)/Math.cos(r.b);o=Hr(o),a=Hr(a),i=Hr(i),s=Hr(s),n.drawingArea=Math.min(Math.floor(e-(o+a)/2),Math.floor(e-(i+s)/2)),n.setCenterPoint(o,a,i,s)},setCenterPoint:function(e,t,r,n){var o=this,a=o.width-t-o.drawingArea,i=e+o.drawingArea,s=r+o.drawingArea,c=o.height-o.paddingTop-n-o.drawingArea;o.xCenter=Math.floor((i+a)/2+o.left),o.yCenter=Math.floor((s+c)/2+o.top+o.paddingTop)},getIndexAngle:function(e){var t=this.chart,r=(e*(360/t.data.labels.length)+((t.options||{}).startAngle||0))%360;return(r<0?r+360:r)*Math.PI*2/360},getDistanceFromCenterForValue:function(e){var t=this;if(H.isNullOrUndef(e))return NaN;var r=t.drawingArea/(t.max-t.min);return t.options.ticks.reverse?(t.max-e)*r:(e-t.min)*r},getPointPosition:function(e,t){var r=this.getIndexAngle(e)-Math.PI/2;return{x:Math.cos(r)*t+this.xCenter,y:Math.sin(r)*t+this.yCenter}},getPointPositionForValue:function(e,t){return this.getPointPosition(e,this.getDistanceFromCenterForValue(t))},getBasePosition:function(e){var t=this.min,r=this.max;return this.getPointPositionForValue(e||0,this.beginAtZero?0:t<0&&r<0?r:t>0&&r>0?t:0)},_drawGrid:function(){var e,t,r,n=this,o=n.ctx,a=n.options,i=a.gridLines,s=a.angleLines,c=jr(s.lineWidth,i.lineWidth),l=jr(s.color,i.color);if(a.pointLabels.display&&function(e){var t=e.ctx,r=e.options,n=r.pointLabels,o=Ir(r),a=e.getDistanceFromCenterForValue(r.ticks.reverse?e.min:e.max),i=H.options._parseFont(n);t.save(),t.font=i.string,t.textBaseline="middle";for(var s=e.chart.data.labels.length-1;s>=0;s--){var c=0===s?o/2:0,l=e.getPointPosition(s,a+c+5),d=Ar(n.fontColor,s,I.global.defaultFontColor);t.fillStyle=d;var u=e.getIndexAngle(s),p=H.toDegrees(u);t.textAlign=zr(p),Rr(p,e._pointLabelSizes[s],l),Fr(t,e.pointLabels[s],l,i.lineHeight)}t.restore()}(n),i.display&&H.each(n.ticks,(function(e,r){0!==r&&(t=n.getDistanceFromCenterForValue(n.ticksAsNumbers[r]),function(e,t,r,n){var o,a=e.ctx,i=t.circular,s=e.chart.data.labels.length,c=Ar(t.color,n-1),l=Ar(t.lineWidth,n-1);if((i||s)&&c&&l){if(a.save(),a.strokeStyle=c,a.lineWidth=l,a.setLineDash&&(a.setLineDash(t.borderDash||[]),a.lineDashOffset=t.borderDashOffset||0),a.beginPath(),i)a.arc(e.xCenter,e.yCenter,r,0,2*Math.PI);else{o=e.getPointPosition(0,r),a.moveTo(o.x,o.y);for(var d=1;d=0;e--)t=n.getDistanceFromCenterForValue(a.ticks.reverse?n.min:n.max),r=n.getPointPosition(e,t),o.beginPath(),o.moveTo(n.xCenter,n.yCenter),o.lineTo(r.x,r.y),o.stroke();o.restore()}},_drawLabels:function(){var e=this,t=e.ctx,r=e.options.ticks;if(r.display){var n,o,a=e.getIndexAngle(0),i=H.options._parseFont(r),s=jr(r.fontColor,I.global.defaultFontColor);t.save(),t.font=i.string,t.translate(e.xCenter,e.yCenter),t.rotate(a),t.textAlign="center",t.textBaseline="middle",H.each(e.ticks,(function(a,c){(0!==c||r.reverse)&&(n=e.getDistanceFromCenterForValue(e.ticksAsNumbers[c]),r.showLabelBackdrop&&(o=t.measureText(a).width,t.fillStyle=r.backdropColor,t.fillRect(-o/2-r.backdropPaddingX,-n-i.size/2-r.backdropPaddingY,o+2*r.backdropPaddingX,i.size+2*r.backdropPaddingY)),t.fillStyle=s,t.fillText(a,0,-n))})),t.restore()}},_drawTitle:H.noop}),Ur=Pr;Br._defaults=Ur;var Wr=H._deprecated,qr=H.options.resolve,Vr=H.valueOrDefault,Kr=Number.MIN_SAFE_INTEGER||-9007199254740991,Gr=Number.MAX_SAFE_INTEGER||9007199254740991,Qr={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Jr=Object.keys(Qr);function Zr(e,t){return e-t}function $r(e){return H.valueOrDefault(e.time.min,e.ticks.min)}function Xr(e){return H.valueOrDefault(e.time.max,e.ticks.max)}function en(e,t,r,n){var o=function(e,t,r){for(var n,o,a,i=0,s=e.length-1;i>=0&&i<=s;){if(o=e[(n=i+s>>1)-1]||null,a=e[n],!o)return{lo:null,hi:a};if(a[t]r))return{lo:o,hi:a};s=n-1}}return{lo:a,hi:null}}(e,t,r),a=o.lo?o.hi?o.lo:e[e.length-2]:e[0],i=o.lo?o.hi?o.hi:e[e.length-1]:e[1],s=i[t]-a[t],c=s?(r-a[t])/s:0,l=(i[n]-a[n])*c;return a[n]+l}function tn(e,t){var r=e._adapter,n=e.options.time,o=n.parser,a=o||n.format,i=t;return"function"==typeof o&&(i=o(i)),H.isFinite(i)||(i="string"==typeof a?r.parse(i,a):r.parse(i)),null!==i?+i:(o||"function"!=typeof a||(i=a(t),H.isFinite(i)||(i=r.parse(i))),i)}function rn(e,t){if(H.isNullOrUndef(t))return null;var r=e.options.time,n=tn(e,e.getRightValue(t));return null===n||r.round&&(n=+e._adapter.startOf(n,r.round)),n}function nn(e,t,r,n){var o,a,i,s=Jr.length;for(o=Jr.indexOf(e);o=0&&(t[a].major=!0);return t}(e,a,i,r):a}var an=mr.extend({initialize:function(){this.mergeTicksOptions(),mr.prototype.initialize.call(this)},update:function(){var e=this,t=e.options,r=t.time||(t.time={}),n=e._adapter=new Xt._date(t.adapters.date);return Wr("time scale",r.format,"time.format","time.parser"),Wr("time scale",r.min,"time.min","ticks.min"),Wr("time scale",r.max,"time.max","ticks.max"),H.mergeIf(r.displayFormats,n.formats()),mr.prototype.update.apply(e,arguments)},getRightValue:function(e){return e&&void 0!==e.t&&(e=e.t),mr.prototype.getRightValue.call(this,e)},determineDataLimits:function(){var e,t,r,n,o,a,i,s=this,c=s.chart,l=s._adapter,d=s.options,u=d.time.unit||"day",p=Gr,f=Kr,m=[],h=[],g=[],b=s._getLabels();for(e=0,r=b.length;e1?function(e){var t,r,n,o={},a=[];for(t=0,r=e.length;t1e5*l)throw t+" and "+r+" are too far apart with stepSize of "+l+" "+c;for(o=u;o=o&&r<=a&&d.push(r);return n.min=o,n.max=a,n._unit=c.unit||(s.autoSkip?nn(c.minUnit,n.min,n.max,u):function(e,t,r,n,o){var a,i;for(a=Jr.length-1;a>=Jr.indexOf(r);a--)if(i=Jr[a],Qr[i].common&&e._adapter.diff(o,n,i)>=t-1)return i;return Jr[r?Jr.indexOf(r):0]}(n,d.length,c.minUnit,n.min,n.max)),n._majorUnit=s.major.enabled&&"year"!==n._unit?function(e){for(var t=Jr.indexOf(e)+1,r=Jr.length;tt&&s=0&&e0?s:1}}),sn={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};an._defaults=sn;var cn={category:gr,linear:Mr,logarithmic:Or,radialLinear:Br,time:an},ln={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};Xt._date.override("function"==typeof e?{_id:"moment",formats:function(){return ln},parse:function(t,r){return"string"==typeof t&&"string"==typeof r?t=e(t,r):t instanceof e||(t=e(t)),t.isValid()?t.valueOf():null},format:function(t,r){return e(t).format(r)},add:function(t,r,n){return e(t).add(r,n).valueOf()},diff:function(t,r,n){return e(t).diff(e(r),n)},startOf:function(t,r,n){return t=e(t),"isoWeek"===r?t.isoWeekday(n).valueOf():t.startOf(r).valueOf()},endOf:function(t,r){return e(t).endOf(r).valueOf()},_create:function(t){return e(t)}}:{}),I._set("global",{plugins:{filler:{propagate:!0}}});var dn={dataset:function(e){var t=e.fill,r=e.chart,n=r.getDatasetMeta(t),o=n&&r.isDatasetVisible(t)&&n.dataset._children||[],a=o.length||0;return a?function(e,t){return t=r)&&n;switch(a){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return a;default:return!1}}function pn(e){return(e.el._scale||{}).getPointPositionForValue?function(e){var t,r,n,o,a,i=e.el._scale,s=i.options,c=i.chart.data.labels.length,l=e.fill,d=[];if(!c)return null;for(t=s.ticks.reverse?i.max:i.min,r=s.ticks.reverse?i.min:i.max,n=i.getPointPositionForValue(0,t),o=0;o0;--a)H.canvas.lineTo(e,r[a],r[a-1],!0);else for(i=r[0].cx,s=r[0].cy,c=Math.sqrt(Math.pow(r[0].x-i,2)+Math.pow(r[0].y-s,2)),a=o-1;a>0;--a)e.arc(i,s,c,r[a].angle,r[a-1].angle,!0)}}function bn(e,t,r,n,o,a){var i,s,c,l,d,u,p,f,m=t.length,h=n.spanGaps,g=[],b=[],v=0,y=0;for(e.beginPath(),i=0,s=m;i=0;--r)(t=c[r].$filler)&&t.visible&&(o=(n=t.el)._view,a=n._children||[],i=t.mapper,s=o.backgroundColor||I.global.defaultColor,i&&s&&a.length&&(H.canvas.clipArea(l,e.chartArea),bn(l,a,i,o,s,n._loop),H.canvas.unclipArea(l)))}},yn=H.rtl.getRtlAdapter,_n=H.noop,wn=H.valueOrDefault;function kn(e,t){return e.usePointStyle&&e.boxWidth>t?t:e.boxWidth}I._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(e,t){var r=t.datasetIndex,n=this.chart,o=n.getDatasetMeta(r);o.hidden=null===o.hidden?!n.data.datasets[r].hidden:null,n.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(e){var t=e.data.datasets,r=e.options.legend||{},n=r.labels&&r.labels.usePointStyle;return e._getSortedDatasetMetas().map((function(r){var o=r.controller.getStyle(n?0:void 0);return{text:t[r.index].label,fillStyle:o.backgroundColor,hidden:!e.isDatasetVisible(r.index),lineCap:o.borderCapStyle,lineDash:o.borderDash,lineDashOffset:o.borderDashOffset,lineJoin:o.borderJoinStyle,lineWidth:o.borderWidth,strokeStyle:o.borderColor,pointStyle:o.pointStyle,rotation:o.rotation,datasetIndex:r.index}}),this)}}},legendCallback:function(e){var t,r,n,o=document.createElement("ul"),a=e.data.datasets;for(o.setAttribute("class",e.id+"-legend"),t=0,r=a.length;tc.width)&&(u+=i+r.padding,d[d.length-(t>0?0:1)]=0),s[t]={left:0,top:0,width:n,height:i},d[d.length-1]+=n+r.padding})),c.height+=u}else{var p=r.padding,f=e.columnWidths=[],m=e.columnHeights=[],h=r.padding,g=0,b=0;H.each(e.legendItems,(function(e,t){var n=kn(r,i)+i/2+o.measureText(e.text).width;t>0&&b+i+2*p>c.height&&(h+=g+r.padding,f.push(g),m.push(b),g=0,b=0),g=Math.max(g,n),b+=i+p,s[t]={left:0,top:0,width:n,height:i}})),h+=g,f.push(g),m.push(b),c.width+=h}e.width=c.width,e.height=c.height}else e.width=c.width=e.height=c.height=0},afterFit:_n,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var e=this,t=e.options,r=t.labels,n=I.global,o=n.defaultColor,a=n.elements.line,i=e.height,s=e.columnHeights,c=e.width,l=e.lineWidths;if(t.display){var d,u=yn(t.rtl,e.left,e.minSize.width),p=e.ctx,f=wn(r.fontColor,n.defaultFontColor),m=H.options._parseFont(r),h=m.size;p.textAlign=u.textAlign("left"),p.textBaseline="middle",p.lineWidth=.5,p.strokeStyle=f,p.fillStyle=f,p.font=m.string;var g=kn(r,h),b=e.legendHitBoxes,v=function(e,n){switch(t.align){case"start":return r.padding;case"end":return e-n;default:return(e-n+r.padding)/2}},y=e.isHorizontal();d=y?{x:e.left+v(c,l[0]),y:e.top+r.padding,line:0}:{x:e.left+r.padding,y:e.top+v(i,s[0]),line:0},H.rtl.overrideTextDirection(e.ctx,t.textDirection);var _=h+r.padding;H.each(e.legendItems,(function(t,n){var f=p.measureText(t.text).width,m=g+h/2+f,w=d.x,k=d.y;u.setWidth(e.minSize.width),y?n>0&&w+m+r.padding>e.left+e.minSize.width&&(k=d.y+=_,d.line++,w=d.x=e.left+v(c,l[d.line])):n>0&&k+_>e.top+e.minSize.height&&(w=d.x=w+e.columnWidths[d.line]+r.padding,d.line++,k=d.y=e.top+v(i,s[d.line]));var x=u.x(w);!function(e,t,n){if(!(isNaN(g)||g<=0)){p.save();var i=wn(n.lineWidth,a.borderWidth);if(p.fillStyle=wn(n.fillStyle,o),p.lineCap=wn(n.lineCap,a.borderCapStyle),p.lineDashOffset=wn(n.lineDashOffset,a.borderDashOffset),p.lineJoin=wn(n.lineJoin,a.borderJoinStyle),p.lineWidth=i,p.strokeStyle=wn(n.strokeStyle,o),p.setLineDash&&p.setLineDash(wn(n.lineDash,a.borderDash)),r&&r.usePointStyle){var s=g*Math.SQRT2/2,c=u.xPlus(e,g/2),l=t+h/2;H.canvas.drawPoint(p,n.pointStyle,s,c,l,n.rotation)}else p.fillRect(u.leftForLtr(e,g),t,g,h),0!==i&&p.strokeRect(u.leftForLtr(e,g),t,g,h);p.restore()}}(x,k,t),b[n].left=u.leftForLtr(x,b[n].width),b[n].top=k,function(e,t,r,n){var o=h/2,a=u.xPlus(e,g+o),i=t+o;p.fillText(r.text,a,i),r.hidden&&(p.beginPath(),p.lineWidth=2,p.moveTo(a,i),p.lineTo(u.xPlus(a,n),i),p.stroke())}(x,k,t,f),y?d.x+=m+r.padding:d.y+=_})),H.rtl.restoreTextDirection(e.ctx,t.textDirection)}},_getLegendItemAt:function(e,t){var r,n,o,a=this;if(e>=a.left&&e<=a.right&&t>=a.top&&t<=a.bottom)for(o=a.legendHitBoxes,r=0;r=(n=o[r]).left&&e<=n.left+n.width&&t>=n.top&&t<=n.top+n.height)return a.legendItems[r];return null},handleEvent:function(e){var t,r=this,n=r.options,o="mouseup"===e.type?"click":e.type;if("mousemove"===o){if(!n.onHover&&!n.onLeave)return}else{if("click"!==o)return;if(!n.onClick)return}t=r._getLegendItemAt(e.x,e.y),"click"===o?t&&n.onClick&&n.onClick.call(r,e.native,t):(n.onLeave&&t!==r._hoveredItem&&(r._hoveredItem&&n.onLeave.call(r,e.native,r._hoveredItem),r._hoveredItem=t),n.onHover&&t&&n.onHover.call(r,e.native,t))}});function Mn(e,t){var r=new xn({ctx:e.ctx,options:t,chart:e});mt.configure(e,r,t),mt.addBox(e,r),e.legend=r}var Sn={id:"legend",_element:xn,beforeInit:function(e){var t=e.options.legend;t&&Mn(e,t)},beforeUpdate:function(e){var t=e.options.legend,r=e.legend;t?(H.mergeIf(t,I.global.legend),r?(mt.configure(e,r,t),r.options=t):Mn(e,t)):r&&(mt.removeBox(e,r),delete e.legend)},afterEvent:function(e,t){var r=e.legend;r&&r.handleEvent(t)}},En=H.noop;I._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Ln=G.extend({initialize:function(e){H.extend(this,e),this.legendHitBoxes=[]},beforeUpdate:En,update:function(e,t,r){var n=this;return n.beforeUpdate(),n.maxWidth=e,n.maxHeight=t,n.margins=r,n.beforeSetDimensions(),n.setDimensions(),n.afterSetDimensions(),n.beforeBuildLabels(),n.buildLabels(),n.afterBuildLabels(),n.beforeFit(),n.fit(),n.afterFit(),n.afterUpdate(),n.minSize},afterUpdate:En,beforeSetDimensions:En,setDimensions:function(){var e=this;e.isHorizontal()?(e.width=e.maxWidth,e.left=0,e.right=e.width):(e.height=e.maxHeight,e.top=0,e.bottom=e.height),e.paddingLeft=0,e.paddingTop=0,e.paddingRight=0,e.paddingBottom=0,e.minSize={width:0,height:0}},afterSetDimensions:En,beforeBuildLabels:En,buildLabels:En,afterBuildLabels:En,beforeFit:En,fit:function(){var e,t=this,r=t.options,n=t.minSize={},o=t.isHorizontal();r.display?(e=(H.isArray(r.text)?r.text.length:1)*H.options._parseFont(r).lineHeight+2*r.padding,t.width=n.width=o?t.maxWidth:e,t.height=n.height=o?e:t.maxHeight):t.width=n.width=t.height=n.height=0},afterFit:En,isHorizontal:function(){var e=this.options.position;return"top"===e||"bottom"===e},draw:function(){var e=this,t=e.ctx,r=e.options;if(r.display){var n,o,a,i=H.options._parseFont(r),s=i.lineHeight,c=s/2+r.padding,l=0,d=e.top,u=e.left,p=e.bottom,f=e.right;t.fillStyle=H.valueOrDefault(r.fontColor,I.global.defaultFontColor),t.font=i.string,e.isHorizontal()?(o=u+(f-u)/2,a=d+c,n=f-u):(o="left"===r.position?u+c:f-c,a=d+(p-d)/2,n=p-d,l=Math.PI*("left"===r.position?-.5:.5)),t.save(),t.translate(o,a),t.rotate(l),t.textAlign="center",t.textBaseline="middle";var m=r.text;if(H.isArray(m))for(var h=0,g=0;g=0;n--){var o=e[n];if(t(o))return o}},H.isNumber=function(e){return!isNaN(parseFloat(e))&&isFinite(e)},H.almostEquals=function(e,t,r){return Math.abs(e-t)=e},H.max=function(e){return e.reduce((function(e,t){return isNaN(t)?e:Math.max(e,t)}),Number.NEGATIVE_INFINITY)},H.min=function(e){return e.reduce((function(e,t){return isNaN(t)?e:Math.min(e,t)}),Number.POSITIVE_INFINITY)},H.sign=Math.sign?function(e){return Math.sign(e)}:function(e){return 0==(e=+e)||isNaN(e)?e:e>0?1:-1},H.toRadians=function(e){return e*(Math.PI/180)},H.toDegrees=function(e){return e*(180/Math.PI)},H._decimalPlaces=function(e){if(H.isFinite(e)){for(var t=1,r=0;Math.round(e*t)/t!==e;)t*=10,r++;return r}},H.getAngleFromPoint=function(e,t){var r=t.x-e.x,n=t.y-e.y,o=Math.sqrt(r*r+n*n),a=Math.atan2(n,r);return a<-.5*Math.PI&&(a+=2*Math.PI),{angle:a,distance:o}},H.distanceBetweenPoints=function(e,t){return Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2))},H.aliasPixel=function(e){return e%2==0?0:.5},H._alignPixel=function(e,t,r){var n=e.currentDevicePixelRatio,o=r/2;return Math.round((t-o)*n)/n+o},H.splineCurve=function(e,t,r,n){var o=e.skip?t:e,a=t,i=r.skip?t:r,s=Math.sqrt(Math.pow(a.x-o.x,2)+Math.pow(a.y-o.y,2)),c=Math.sqrt(Math.pow(i.x-a.x,2)+Math.pow(i.y-a.y,2)),l=s/(s+c),d=c/(s+c),u=n*(l=isNaN(l)?0:l),p=n*(d=isNaN(d)?0:d);return{previous:{x:a.x-u*(i.x-o.x),y:a.y-u*(i.y-o.y)},next:{x:a.x+p*(i.x-o.x),y:a.y+p*(i.y-o.y)}}},H.EPSILON=Number.EPSILON||1e-14,H.splineCurveMonotone=function(e){var t,r,n,o,a,i,s,c,l,d=(e||[]).map((function(e){return{model:e._model,deltaK:0,mK:0}})),u=d.length;for(t=0;t0?d[t-1]:null,(o=t0?d[t-1]:null,o=t=e.length-1?e[0]:e[t+1]:t>=e.length-1?e[e.length-1]:e[t+1]},H.previousItem=function(e,t,r){return r?t<=0?e[e.length-1]:e[t-1]:t<=0?e[0]:e[t-1]},H.niceNum=function(e,t){var r=Math.floor(H.log10(e)),n=e/Math.pow(10,r);return(t?n<1.5?1:n<3?2:n<7?5:10:n<=1?1:n<=2?2:n<=5?5:10)*Math.pow(10,r)},H.requestAnimFrame="undefined"==typeof window?function(e){e()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){return window.setTimeout(e,1e3/60)},H.getRelativePosition=function(e,t){var r,n,o=e.originalEvent||e,a=e.target||e.srcElement,i=a.getBoundingClientRect(),s=o.touches;s&&s.length>0?(r=s[0].clientX,n=s[0].clientY):(r=o.clientX,n=o.clientY);var c=parseFloat(H.getStyle(a,"padding-left")),l=parseFloat(H.getStyle(a,"padding-top")),d=parseFloat(H.getStyle(a,"padding-right")),u=parseFloat(H.getStyle(a,"padding-bottom")),p=i.right-i.left-c-d,f=i.bottom-i.top-l-u;return{x:r=Math.round((r-i.left-c)/p*a.width/t.currentDevicePixelRatio),y:n=Math.round((n-i.top-l)/f*a.height/t.currentDevicePixelRatio)}},H.getConstraintWidth=function(e){return r(e,"max-width","clientWidth")},H.getConstraintHeight=function(e){return r(e,"max-height","clientHeight")},H._calculatePadding=function(e,t,r){return(t=H.getStyle(e,t)).indexOf("%")>-1?r*parseInt(t,10)/100:parseInt(t,10)},H._getParentNode=function(e){var t=e.parentNode;return t&&"[object ShadowRoot]"===t.toString()&&(t=t.host),t},H.getMaximumWidth=function(e){var t=H._getParentNode(e);if(!t)return e.clientWidth;var r=t.clientWidth,n=r-H._calculatePadding(t,"padding-left",r)-H._calculatePadding(t,"padding-right",r),o=H.getConstraintWidth(e);return isNaN(o)?n:Math.min(n,o)},H.getMaximumHeight=function(e){var t=H._getParentNode(e);if(!t)return e.clientHeight;var r=t.clientHeight,n=r-H._calculatePadding(t,"padding-top",r)-H._calculatePadding(t,"padding-bottom",r),o=H.getConstraintHeight(e);return isNaN(o)?n:Math.min(n,o)},H.getStyle=function(e,t){return e.currentStyle?e.currentStyle[t]:document.defaultView.getComputedStyle(e,null).getPropertyValue(t)},H.retinaScale=function(e,t){var r=e.currentDevicePixelRatio=t||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==r){var n=e.canvas,o=e.height,a=e.width;n.height=o*r,n.width=a*r,e.ctx.scale(r,r),n.style.height||n.style.width||(n.style.height=o+"px",n.style.width=a+"px")}},H.fontString=function(e,t,r){return t+" "+e+"px "+r},H.longestText=function(e,t,r,n){var o=(n=n||{}).data=n.data||{},a=n.garbageCollect=n.garbageCollect||[];n.font!==t&&(o=n.data={},a=n.garbageCollect=[],n.font=t),e.font=t;var i,s,c,l,d,u=0,p=r.length;for(i=0;ir.length){for(i=0;in&&(n=a),n},H.numberOfLabelLines=function(e){var t=1;return H.each(e,(function(e){H.isArray(e)&&e.length>t&&(t=e.length)})),t},H.color=k?function(e){return e instanceof CanvasGradient&&(e=I.global.defaultColor),k(e)}:function(e){return console.error("Color.js not found!"),e},H.getHoverColor=function(e){return e instanceof CanvasPattern||e instanceof CanvasGradient?e:H.color(e).saturate(.5).darken(.1).rgbString()}}(),Jt._adapters=Xt,Jt.Animation=J,Jt.animationService=Z,Jt.controllers=Ze,Jt.DatasetController=re,Jt.defaults=I,Jt.Element=G,Jt.elements=we,Jt.Interaction=ot,Jt.layouts=mt,Jt.platform=Dt,Jt.plugins=Ct,Jt.Scale=mr,Jt.scaleService=Ot,Jt.Ticks=er,Jt.Tooltip=Ht,Jt.helpers.each(cn,(function(e,t){Jt.scaleService.registerScaleType(t,e,e._defaults)})),Cn)Cn.hasOwnProperty(An)&&Jt.plugins.register(Cn[An]);Jt.platform.initialize();var Nn=Jt;return"undefined"!=typeof window&&(window.Chart=Jt),Jt.Chart=Jt,Jt.Legend=Cn.legend._element,Jt.Title=Cn.title._element,Jt.pluginService=Jt.plugins,Jt.PluginBase=Jt.Element.extend({}),Jt.canvasHelpers=Jt.helpers.canvas,Jt.layoutService=Jt.layouts,Jt.LinearScaleBase=_r,Jt.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],(function(e){Jt[e]=function(t,r){return new Jt(t,Jt.helpers.merge(r||{},{type:e.charAt(0).toLowerCase()+e.slice(1)}))}})),Nn}(function(){try{return r(2)}catch(e){}}())},function(e,t,r){var n=r(200);e.exports=function(e,t){return n(e,t)}},function(e,t,r){var n=r(42),o=r(8),a=o["__core-js_shared__"]||(o["__core-js_shared__"]={});(e.exports=function(e,t){return a[e]||(a[e]=void 0!==t?t:{})})("versions",[]).push({version:n.version,mode:r(63)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(e,t,r){var n=r(44);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==n(e)?e.split(""):Object(e)}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,r){"use strict";var n=r(7);e.exports=function(){var e=n(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t}},function(e,t,r){var n=r(7),o=r(22),a=r(15)("species");e.exports=function(e,t){var r,i=n(e).constructor;return void 0===i||null==(r=n(i)[a])?t:o(r)}},function(e,t,r){var n=r(143),o=r(732),a=r(733),i=n?n.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":i&&i in Object(e)?o(e):a(e)}},function(e,t){e.exports=function(e){return null!=e&&"object"==typeof e}},function(e,t,r){var n=r(101);e.exports=function(e){if(!n(e))throw TypeError(e+" is not an object!");return e}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,o=Object.assign||function(e){for(var t=1;t2?arguments[2]:{},a=n(t);o&&(a=i.call(a,Object.getOwnPropertySymbols(t)));for(var s=0;s=l?c:(r.setFullYear(c.getFullYear(),c.getMonth(),s),r)}},function(e,t,r){var n=r(33),o=r(16),a=r(74);e.exports=function(e){return function(t,r,i){var s,c=n(t),l=o(c.length),d=a(i,l);if(e&&r!=r){for(;l>d;)if((s=c[d++])!=s)return!0}else for(;l>d;d++)if((e||d in c)&&c[d]===r)return e||d||0;return!e&&-1}}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,r){var n=r(44);e.exports=Array.isArray||function(e){return"Array"==n(e)}},function(e,t,r){var n=r(45),o=r(50);e.exports=function(e){return function(t,r){var a,i,s=String(o(t)),c=n(r),l=s.length;return c<0||c>=l?e?"":void 0:(a=s.charCodeAt(c))<55296||a>56319||c+1===l||(i=s.charCodeAt(c+1))<56320||i>57343?e?s.charAt(c):a:e?s.slice(c,c+2):i-56320+(a-55296<<10)+65536}}},function(e,t,r){var n=r(13),o=r(44),a=r(15)("match");e.exports=function(e){var t;return n(e)&&(void 0!==(t=e[a])?!!t:"RegExp"==o(e))}},function(e,t,r){var n=r(15)("iterator"),o=!1;try{var a=[7][n]();a.return=function(){o=!0},Array.from(a,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var r=!1;try{var a=[7],i=a[n]();i.next=function(){return{done:r=!0}},a[n]=function(){return i},e(a)}catch(e){}return r}},function(e,t,r){"use strict";var n=r(96),o=RegExp.prototype.exec;e.exports=function(e,t){var r=e.exec;if("function"==typeof r){var a=r.call(e,t);if("object"!=typeof a)throw new TypeError("RegExp exec method returned something other than an Object or null");return a}if("RegExp"!==n(e))throw new TypeError("RegExp#exec called on incompatible receiver");return o.call(e,t)}},function(e,t,r){"use strict";r(263);var n=r(27),o=r(26),a=r(10),i=r(50),s=r(15),c=r(193),l=s("species"),d=!a((function(){var e=/./;return e.exec=function(){var e=[];return e.groups={a:"7"},e},"7"!=="".replace(e,"$")})),u=function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var r="ab".split(e);return 2===r.length&&"a"===r[0]&&"b"===r[1]}();e.exports=function(e,t,r){var p=s(e),f=!a((function(){var t={};return t[p]=function(){return 7},7!=""[e](t)})),m=f?!a((function(){var t=!1,r=/a/;return r.exec=function(){return t=!0,null},"split"===e&&(r.constructor={},r.constructor[l]=function(){return r}),r[p](""),!t})):void 0;if(!f||!m||"replace"===e&&!d||"split"===e&&!u){var h=/./[p],g=r(i,p,""[e],(function(e,t,r,n,o){return t.exec===c?f&&!o?{done:!0,value:h.call(t,r,n)}:{done:!0,value:e.call(r,t,n)}:{done:!1}})),b=g[0],v=g[1];n(String.prototype,e,b),o(RegExp.prototype,p,2==t?function(e,t){return v.call(e,this,t)}:function(e){return v.call(e,this)})}}},function(e,t,r){var n=r(8).navigator;e.exports=n&&n.userAgent||""},function(e,t,r){"use strict";var n=r(8),o=r(4),a=r(27),i=r(80),s=r(64),c=r(79),l=r(78),d=r(13),u=r(10),p=r(132),f=r(95),m=r(179);e.exports=function(e,t,r,h,g,b){var v=n[e],y=v,_=g?"set":"add",w=y&&y.prototype,k={},x=function(e){var t=w[e];a(w,e,"delete"==e||"has"==e?function(e){return!(b&&!d(e))&&t.call(this,0===e?0:e)}:"get"==e?function(e){return b&&!d(e)?void 0:t.call(this,0===e?0:e)}:"add"==e?function(e){return t.call(this,0===e?0:e),this}:function(e,r){return t.call(this,0===e?0:e,r),this})};if("function"==typeof y&&(b||w.forEach&&!u((function(){(new y).entries().next()})))){var M=new y,S=M[_](b?{}:-0,1)!=M,E=u((function(){M.has(1)})),L=p((function(e){new y(e)})),D=!b&&u((function(){for(var e=new y,t=5;t--;)e[_](t,t);return!e.has(-0)}));L||((y=t((function(t,r){l(t,y,e);var n=m(new v,t,y);return null!=r&&c(r,g,n[_],n),n}))).prototype=w,w.constructor=y),(E||D)&&(x("delete"),x("has"),g&&x("get")),(D||S)&&x(_),b&&w.clear&&delete w.clear}else y=h.getConstructor(t,e,g,_),i(y.prototype,r),s.NEED=!0;return f(y,e),k[e]=y,o(o.G+o.W+o.F*(y!=v),k),b||h.setStrong(y,e,g),y}},function(e,t,r){for(var n,o=r(8),a=r(26),i=r(72),s=i("typed_array"),c=i("view"),l=!(!o.ArrayBuffer||!o.DataView),d=l,u=0,p="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");u<9;)(n=o[p[u++]])?(a(n.prototype,s,!0),a(n.prototype,c,!0)):d=!1;e.exports={ABV:l,CONSTR:d,TYPED:s,VIEW:c}},function(e,t,r){"use strict";e.exports=r(63)||!r(10)((function(){var e=Math.random();__defineSetter__.call(null,e,(function(){})),delete r(8)[e]}))},function(e,t,r){"use strict";var n=r(4);e.exports=function(e){n(n.S,e,{of:function(){for(var e=arguments.length,t=new Array(e);e--;)t[e]=arguments[e];return new this(t)}})}},function(e,t,r){"use strict";var n=r(4),o=r(22),a=r(43),i=r(79);e.exports=function(e){n(n.S,e,{from:function(e){var t,r,n,s,c=arguments[1];return o(this),(t=void 0!==c)&&o(c),null==e?new this:(r=[],t?(n=0,s=a(c,arguments[2],2),i(e,!1,(function(e){r.push(s(e,n++))}))):i(e,!1,r.push,r),new this(r))}})}},function(e,t,r){var n=r(721),o=r(722),a=r(723),i=r(724),s=r(725);function c(e){var t=-1,r=null==e?0:e.length;for(this.clear();++t=s},o.isCollapsed=function(){return this.getAnchorKey()===this.getFocusKey()&&this.getAnchorOffset()===this.getFocusOffset()},o.getStartKey=function(){return this.getIsBackward()?this.getFocusKey():this.getAnchorKey()},o.getStartOffset=function(){return this.getIsBackward()?this.getFocusOffset():this.getAnchorOffset()},o.getEndKey=function(){return this.getIsBackward()?this.getAnchorKey():this.getFocusKey()},o.getEndOffset=function(){return this.getIsBackward()?this.getAnchorOffset():this.getFocusOffset()},n.createEmpty=function(e){return new n({anchorKey:e,anchorOffset:0,focusKey:e,focusOffset:0,isBackward:!1,hasFocus:!1})},n}((0,r(9).Record)({anchorKey:"",anchorOffset:0,focusKey:"",focusOffset:0,isBackward:!1,hasFocus:!1}));e.exports=n},function(e,t,r){"use strict";var n=r(11),o=null;function a(e){return"LTR"===e||"RTL"===e}function i(e){return a(e)||n(!1),"LTR"===e?"ltr":"rtl"}function s(e){o=e}var c={NEUTRAL:"NEUTRAL",LTR:"LTR",RTL:"RTL",isStrong:a,getHTMLDir:i,getHTMLDirIfDifferent:function(e,t){return a(e)||n(!1),a(t)||n(!1),e===t?null:i(e)},setGlobalDir:s,initGlobalDir:function(){s("LTR")},getGlobalDir:function(){return o||this.initGlobalDir(),o||n(!1),o}};e.exports=c},function(e,t,r){"use strict";var n=r(157),o=r(451);e.exports=function(e){for(var t=e;t&&t!==n(e).documentElement;){var r=o(t);if(null!=r)return r;t=t.parentNode}return null}},function(e,t,r){"use strict";e.exports=function(e){return e&&e.ownerDocument?e.ownerDocument:document}},function(e,t,r){"use strict";e.exports={BACKSPACE:8,TAB:9,RETURN:13,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46,COMMA:188,PERIOD:190,A:65,Z:90,ZERO:48,NUMPAD_0:96,NUMPAD_9:105}},function(e,t,r){"use strict";function n(e,t){var r=o.get(e,t);return"auto"===r||"scroll"===r}var o={get:r(903),getScrollParent:function(e){if(!e)return null;for(var t=e.ownerDocument;e&&e!==t.body;){if(n(e,"overflow")||n(e,"overflowY")||n(e,"overflowX"))return e;e=e.parentNode}return t.defaultView||t.parentWindow}};e.exports=o},function(e,t,r){"use strict";var n=r(907),o=r(908);e.exports=function(e){var t=n(e.ownerDocument||e.document);e.Window&&e instanceof e.Window&&(e=t);var r=o(e),a=e===t?e.ownerDocument.documentElement:e,i=e.scrollWidth-a.clientWidth,s=e.scrollHeight-a.clientHeight;return r.x=Math.max(0,Math.min(r.x,i)),r.y=Math.max(0,Math.min(r.y,s)),r}},function(e,t,r){"use strict";e.exports=function(e){return"handled"===e||!0===e}},function(e,t,r){"use strict";t.__esModule=!0;var n=i(r(831)),o=i(r(843)),a="function"==typeof o.default&&"symbol"==typeof n.default?function(e){return typeof e}:function(e){return e&&"function"==typeof o.default&&e.constructor===o.default&&e!==o.default.prototype?"symbol":typeof e};function i(e){return e&&e.__esModule?e:{default:e}}t.default="function"==typeof o.default&&"symbol"===a(n.default)?function(e){return void 0===e?"undefined":a(e)}:function(e){return e&&"function"==typeof o.default&&e.constructor===o.default&&e!==o.default.prototype?"symbol":void 0===e?"undefined":a(e)}},function(e,t,r){"use strict";(function(e){r.d(t,"a",(function(){return _}));var n=r(0),o=r.n(n),a=r(1),i=r.n(a),s=r(108),c=r.n(s),l=r(109),d=r.n(l),u=r(490),p=r.n(u),f=Object.assign||function(e){for(var t=1;t=0||Object.prototype.hasOwnProperty.call(e,n)&&(r[n]=e[n]);return r}function h(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function g(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function b(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var v=void 0!==e&&e.env&&"production",y=function(e){function t(){h(this,t);var r=g(this,e.call(this));return r.handleOnClick=function(e){var t=r.chartInstance,n=r.props,o=n.getDatasetAtEvent,a=n.getElementAtEvent,i=n.getElementsAtEvent,s=n.onElementsClick;o&&o(t.getDatasetAtEvent(e),e),a&&a(t.getElementAtEvent(e),e),i&&i(t.getElementsAtEvent(e),e),s&&s(t.getElementsAtEvent(e),e)},r.ref=function(e){r.element=e},r.chartInstance=void 0,r}return b(t,e),t.prototype.componentDidMount=function(){this.renderChart()},t.prototype.componentDidUpdate=function(){if(this.props.redraw)return this.destroyChart(),void this.renderChart();this.updateChart()},t.prototype.shouldComponentUpdate=function(e){var t=this.props,r=(t.redraw,t.type),n=t.options,o=t.plugins,a=t.legend,i=t.height,s=t.width;if(!0===e.redraw)return!0;if(i!==e.height||s!==e.width)return!0;if(r!==e.type)return!0;if(!d()(a,e.legend))return!0;if(!d()(n,e.options))return!0;var c=this.transformDataProp(e);return!d()(this.shadowDataProp,c)||!d()(o,e.plugins)},t.prototype.componentWillUnmount=function(){this.destroyChart()},t.prototype.transformDataProp=function(e){var t=e.data;return"function"==typeof t?t(this.element):t},t.prototype.memoizeDataProps=function(){if(this.props.data){var e=this.transformDataProp(this.props);return this.shadowDataProp=f({},e,{datasets:e.datasets&&e.datasets.map((function(e){return f({},e)}))}),this.saveCurrentDatasets(),e}},t.prototype.checkDatasets=function(e){var r="production"!==v&&"prod"!==v,n=this.props.datasetKeyProvider!==t.getLabelAsKey,o=e.length>1;if(r&&o&&!n){var a=!1;e.forEach((function(e){e.label||(a=!0)})),a&&console.error('[react-chartjs-2] Warning: Each dataset needs a unique key. By default, the "label" property on each dataset is used. Alternatively, you may provide a "datasetKeyProvider" as a prop that returns a unique key.')}},t.prototype.getCurrentDatasets=function(){return this.chartInstance&&this.chartInstance.config.data&&this.chartInstance.config.data.datasets||[]},t.prototype.saveCurrentDatasets=function(){var e=this;this.datasets=this.datasets||{},this.getCurrentDatasets().forEach((function(t){e.datasets[e.props.datasetKeyProvider(t)]=t}))},t.prototype.updateChart=function(){var e=this,t=this.props.options,r=this.memoizeDataProps(this.props);if(this.chartInstance){t&&(this.chartInstance.options=c.a.helpers.configMerge(this.chartInstance.options,t));var n=this.getCurrentDatasets(),o=r.datasets||[];this.checkDatasets(n);var a=p()(n,this.props.datasetKeyProvider);this.chartInstance.config.data.datasets=o.map((function(t){var r=a[e.props.datasetKeyProvider(t)];if(r&&r.type===t.type&&t.data){r.data.splice(t.data.length),t.data.forEach((function(e,n){r.data[n]=t.data[n]}));t.data;var n=m(t,["data"]);return f({},r,n)}return t}));r.datasets;var i=m(r,["datasets"]);this.chartInstance.config.data=f({},this.chartInstance.config.data,i),this.chartInstance.update()}},t.prototype.renderChart=function(){var e=this.props,r=e.options,n=e.legend,o=e.type,a=e.plugins,i=this.element,s=this.memoizeDataProps();void 0===n||d()(t.defaultProps.legend,n)||(r.legend=n),this.chartInstance=new c.a(i,{type:o,data:s,options:r,plugins:a})},t.prototype.destroyChart=function(){if(this.chartInstance){this.saveCurrentDatasets();var e=Object.values(this.datasets);this.chartInstance.config.data.datasets=e,this.chartInstance.destroy()}},t.prototype.render=function(){var e=this.props,t=e.height,r=e.width,n=e.id;return o.a.createElement("canvas",{ref:this.ref,height:t,width:r,id:n,onClick:this.handleOnClick})},t}(o.a.Component);y.getLabelAsKey=function(e){return e.label},y.propTypes={data:i.a.oneOfType([i.a.object,i.a.func]).isRequired,getDatasetAtEvent:i.a.func,getElementAtEvent:i.a.func,getElementsAtEvent:i.a.func,height:i.a.number,legend:i.a.object,onElementsClick:i.a.func,options:i.a.object,plugins:i.a.arrayOf(i.a.object),redraw:i.a.bool,type:function(e,t,r){if(!c.a.controllers[e[t]])return new Error("Invalid chart type `"+e[t]+"` supplied to `"+r+"`.")},width:i.a.number,datasetKeyProvider:i.a.func},y.defaultProps={legend:{display:!0,position:"bottom"},type:"doughnut",height:150,width:300,redraw:!1,options:{},datasetKeyProvider:y.getLabelAsKey};(function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"doughnut"}))}})(o.a.Component),function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"pie"}))}}(o.a.Component),function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"line"}))}}(o.a.Component);var _=function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}return b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"bar"}))},t}(o.a.Component);(function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"horizontalBar"}))}})(o.a.Component),function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"radar"}))}}(o.a.Component),function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"polarArea"}))}}(o.a.Component),function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"bubble"}))}}(o.a.Component),function(e){function t(){return h(this,t),g(this,e.apply(this,arguments))}b(t,e),t.prototype.render=function(){var e=this;return o.a.createElement(y,f({},this.props,{ref:function(t){return e.chartInstance=t&&t.chartInstance},type:"scatter"}))}}(o.a.Component),c.a.defaults}).call(this,r(413))},function(e,t,r){"use strict";r.d(t,"a",(function(){return i}));var n=r(6),o=r(106),a=r(3);function i(e,t){Object(a.a)(2,arguments);var r=Object(n.a)(t);return Object(o.a)(e,-r)}},function(e,t,r){"use strict";function n(e,t){switch(e){case"P":return t.date({width:"short"});case"PP":return t.date({width:"medium"});case"PPP":return t.date({width:"long"});case"PPPP":default:return t.date({width:"full"})}}function o(e,t){switch(e){case"p":return t.time({width:"short"});case"pp":return t.time({width:"medium"});case"ppp":return t.time({width:"long"});case"pppp":default:return t.time({width:"full"})}}var a={p:o,P:function(e,t){var r,a=e.match(/(P+)(p+)?/),i=a[1],s=a[2];if(!s)return n(e,t);switch(i){case"P":r=t.dateTime({width:"short"});break;case"PP":r=t.dateTime({width:"medium"});break;case"PPP":r=t.dateTime({width:"long"});break;case"PPPP":default:r=t.dateTime({width:"full"})}return r.replace("{{date}}",n(i,t)).replace("{{time}}",o(s,t))}};t.a=a},function(e,t,r){"use strict";r.d(t,"a",(function(){return i}));var n=r(5),o=r(56),a=r(3);function i(e){Object(a.a)(1,arguments);var t=Object(n.default)(e),r=t.getUTCFullYear(),i=new Date(0);i.setUTCFullYear(r+1,0,4),i.setUTCHours(0,0,0,0);var s=Object(o.a)(i),c=new Date(0);c.setUTCFullYear(r,0,4),c.setUTCHours(0,0,0,0);var l=Object(o.a)(c);return t.getTime()>=s.getTime()?r+1:t.getTime()>=l.getTime()?r:r-1}},function(e,t,r){"use strict";r.r(t),r.d(t,"default",(function(){return a}));var n=r(5),o=r(3);function a(e){Object(o.a)(1,arguments);var t=Object(n.default)(e),r=t.getMonth(),a=r-r%3;return t.setMonth(a,1),t.setHours(0,0,0,0),t}},function(e,t,r){"use strict";var n={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXWeeks:{one:"about 1 week",other:"about {{count}} weeks"},xWeeks:{one:"1 week",other:"{{count}} weeks"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}};function o(e){return function(t){var r=t||{},n=r.width?String(r.width):e.defaultWidth;return e.formats[n]||e.formats[e.defaultWidth]}}var a={date:o({formats:{full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},defaultWidth:"full"}),time:o({formats:{full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},defaultWidth:"full"}),dateTime:o({formats:{full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},defaultWidth:"full"})},i={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"};function s(e){return function(t,r){var n,o=r||{};if("formatting"===(o.context?String(o.context):"standalone")&&e.formattingValues){var a=e.defaultFormattingWidth||e.defaultWidth,i=o.width?String(o.width):a;n=e.formattingValues[i]||e.formattingValues[a]}else{var s=e.defaultWidth,c=o.width?String(o.width):e.defaultWidth;n=e.values[c]||e.values[s]}return n[e.argumentCallback?e.argumentCallback(t):t]}}function c(e){return function(t,r){var n=String(t),o=r||{},a=o.width,i=a&&e.matchPatterns[a]||e.matchPatterns[e.defaultMatchWidth],s=n.match(i);if(!s)return null;var c,l=s[0],d=a&&e.parsePatterns[a]||e.parsePatterns[e.defaultParseWidth];return c="[object Array]"===Object.prototype.toString.call(d)?function(e,t){for(var r=0;r0?"in "+o:o+" ago":o},formatLong:a,formatRelative:function(e,t,r,n){return i[e]},localize:{ordinalNumber:function(e,t){var r=Number(e),n=r%100;if(n>20||n<10)switch(n%10){case 1:return r+"st";case 2:return r+"nd";case 3:return r+"rd"}return r+"th"},era:s({values:{narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},defaultWidth:"wide"}),quarter:s({values:{narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter","2nd quarter","3rd quarter","4th quarter"]},defaultWidth:"wide",argumentCallback:function(e){return Number(e)-1}}),month:s({values:{narrow:["J","F","M","A","M","J","J","A","S","O","N","D"],abbreviated:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],wide:["January","February","March","April","May","June","July","August","September","October","November","December"]},defaultWidth:"wide"}),day:s({values:{narrow:["S","M","T","W","T","F","S"],short:["Su","Mo","Tu","We","Th","Fr","Sa"],abbreviated:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],wide:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},defaultWidth:"wide"}),dayPeriod:s({values:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"}},defaultWidth:"wide",formattingValues:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"}},defaultFormattingWidth:"wide"})},match:{ordinalNumber:(l={matchPattern:/^(\d+)(th|st|nd|rd)?/i,parsePattern:/\d+/i,valueCallback:function(e){return parseInt(e,10)}},function(e,t){var r=String(e),n=t||{},o=r.match(l.matchPattern);if(!o)return null;var a=o[0],i=r.match(l.parsePattern);if(!i)return null;var s=l.valueCallback?l.valueCallback(i[0]):i[0];return{value:s=n.valueCallback?n.valueCallback(s):s,rest:r.slice(a.length)}}),era:c({matchPatterns:{narrow:/^(b|a)/i,abbreviated:/^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,wide:/^(before christ|before common era|anno domini|common era)/i},defaultMatchWidth:"wide",parsePatterns:{any:[/^b/i,/^(a|c)/i]},defaultParseWidth:"any"}),quarter:c({matchPatterns:{narrow:/^[1234]/i,abbreviated:/^q[1234]/i,wide:/^[1234](th|st|nd|rd)? quarter/i},defaultMatchWidth:"wide",parsePatterns:{any:[/1/i,/2/i,/3/i,/4/i]},defaultParseWidth:"any",valueCallback:function(e){return e+1}}),month:c({matchPatterns:{narrow:/^[jfmasond]/i,abbreviated:/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,wide:/^(january|february|march|april|may|june|july|august|september|october|november|december)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^j/i,/^f/i,/^m/i,/^a/i,/^m/i,/^j/i,/^j/i,/^a/i,/^s/i,/^o/i,/^n/i,/^d/i],any:[/^ja/i,/^f/i,/^mar/i,/^ap/i,/^may/i,/^jun/i,/^jul/i,/^au/i,/^s/i,/^o/i,/^n/i,/^d/i]},defaultParseWidth:"any"}),day:c({matchPatterns:{narrow:/^[smtwf]/i,short:/^(su|mo|tu|we|th|fr|sa)/i,abbreviated:/^(sun|mon|tue|wed|thu|fri|sat)/i,wide:/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^s/i,/^m/i,/^t/i,/^w/i,/^t/i,/^f/i,/^s/i],any:[/^su/i,/^m/i,/^tu/i,/^w/i,/^th/i,/^f/i,/^sa/i]},defaultParseWidth:"any"}),dayPeriod:c({matchPatterns:{narrow:/^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,any:/^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i},defaultMatchWidth:"any",parsePatterns:{any:{am:/^a/i,pm:/^p/i,midnight:/^mi/i,noon:/^no/i,morning:/morning/i,afternoon:/afternoon/i,evening:/evening/i,night:/night/i}},defaultParseWidth:"any"})},options:{weekStartsOn:0,firstWeekContainsDate:1}};t.a=d},function(e,t,r){"use strict";r.d(t,"a",(function(){return l}));var n=r(5),o=r(48),a=r(6),i=r(107),s=r(3);function c(e,t){Object(s.a)(1,arguments);var r=t||{},n=r.locale,c=n&&n.options&&n.options.firstWeekContainsDate,l=null==c?1:Object(a.a)(c),d=null==r.firstWeekContainsDate?l:Object(a.a)(r.firstWeekContainsDate),u=Object(i.a)(e,t),p=new Date(0);p.setUTCFullYear(u,0,d),p.setUTCHours(0,0,0,0);var f=Object(o.a)(p,t);return f}function l(e,t){Object(s.a)(1,arguments);var r=Object(n.default)(e),a=Object(o.a)(r,t).getTime()-c(r,t).getTime();return Math.round(a/6048e5)+1}},function(e,t,r){"use strict";r.d(t,"a",(function(){return c}));var n=r(5),o=r(56),a=r(166),i=r(3);function s(e){Object(i.a)(1,arguments);var t=Object(a.a)(e),r=new Date(0);r.setUTCFullYear(t,0,4),r.setUTCHours(0,0,0,0);var n=Object(o.a)(r);return n}function c(e){Object(i.a)(1,arguments);var t=Object(n.default)(e),r=Object(o.a)(t).getTime()-s(t).getTime();return Math.round(r/6048e5)+1}},,function(e,t,r){var n=r(13),o=r(8).document,a=n(o)&&n(o.createElement);e.exports=function(e){return a?o.createElement(e):{}}},function(e,t,r){var n=r(8),o=r(42),a=r(63),i=r(245),s=r(20).f;e.exports=function(e){var t=o.Symbol||(o.Symbol=a?{}:n.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:i.f(e)})}},function(e,t,r){var n=r(110)("keys"),o=r(72);e.exports=function(e){return n[e]||(n[e]=o(e))}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,r){var n=r(8).document;e.exports=n&&n.documentElement},function(e,t,r){var n=r(13),o=r(7),a=function(e,t){if(o(e),!n(t)&&null!==t)throw TypeError(t+": can't set as prototype!")};e.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(e,t,n){try{(n=r(43)(Function.call,r(34).f(Object.prototype,"__proto__").set,2))(e,[]),t=!(e instanceof Array)}catch(e){t=!0}return function(e,r){return a(e,r),t?e.__proto__=r:n(e,r),e}}({},!1):void 0),check:a}},function(e,t){e.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(e,t,r){var n=r(13),o=r(177).set;e.exports=function(e,t,r){var a,i=t.constructor;return i!==r&&"function"==typeof i&&(a=i.prototype)!==r.prototype&&n(a)&&o&&o(e,a),e}},function(e,t,r){"use strict";var n=r(45),o=r(50);e.exports=function(e){var t=String(o(this)),r="",a=n(e);if(a<0||a==1/0)throw RangeError("Count can't be negative");for(;a>0;(a>>>=1)&&(t+=t))1&a&&(r+=t);return r}},function(e,t){e.exports=Math.sign||function(e){return 0==(e=+e)||e!=e?e:e<0?-1:1}},function(e,t){var r=Math.expm1;e.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(e){return 0==(e=+e)?e:e>-1e-6&&e<1e-6?e+e*e/2:Math.exp(e)-1}:r},function(e,t,r){"use strict";var n=r(63),o=r(4),a=r(27),i=r(26),s=r(98),c=r(184),l=r(95),d=r(35),u=r(15)("iterator"),p=!([].keys&&"next"in[].keys()),f=function(){return this};e.exports=function(e,t,r,m,h,g,b){c(r,t,m);var v,y,_,w=function(e){if(!p&&e in S)return S[e];switch(e){case"keys":case"values":return function(){return new r(this,e)}}return function(){return new r(this,e)}},k=t+" Iterator",x="values"==h,M=!1,S=e.prototype,E=S[u]||S["@@iterator"]||h&&S[h],L=E||w(h),D=h?x?w("entries"):L:void 0,C="Array"==t&&S.entries||E;if(C&&(_=d(C.call(new e)))!==Object.prototype&&_.next&&(l(_,k,!0),n||"function"==typeof _[u]||i(_,u,f)),x&&E&&"values"!==E.name&&(M=!0,L=function(){return E.call(this)}),n&&!b||!p&&!M&&S[u]||i(S,u,L),s[t]=L,s[k]=f,h)if(v={values:x?L:w("values"),keys:g?L:w("keys"),entries:D},b)for(y in v)y in S||a(S,y,v[y]);else o(o.P+o.F*(p||M),t,v);return v}},function(e,t,r){"use strict";var n=r(75),o=r(71),a=r(95),i={};r(26)(i,r(15)("iterator"),(function(){return this})),e.exports=function(e,t,r){e.prototype=n(i,{next:o(1,r)}),a(e,t+" Iterator")}},function(e,t,r){var n=r(131),o=r(50);e.exports=function(e,t,r){if(n(t))throw TypeError("String#"+r+" doesn't accept regex!");return String(o(e))}},function(e,t,r){var n=r(15)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(r){try{return t[n]=!1,!"/./"[e](t)}catch(e){}}return!0}},function(e,t,r){var n=r(98),o=r(15)("iterator"),a=Array.prototype;e.exports=function(e){return void 0!==e&&(n.Array===e||a[o]===e)}},function(e,t,r){"use strict";var n=r(20),o=r(71);e.exports=function(e,t,r){t in e?n.f(e,t,o(0,r)):e[t]=r}},function(e,t,r){var n=r(96),o=r(15)("iterator"),a=r(98);e.exports=r(42).getIteratorMethod=function(e){if(null!=e)return e[o]||e["@@iterator"]||a[n(e)]}},function(e,t,r){var n=r(605);e.exports=function(e,t){return new(n(e))(t)}},function(e,t,r){"use strict";var n=r(21),o=r(74),a=r(16);e.exports=function(e){for(var t=n(this),r=a(t.length),i=arguments.length,s=o(i>1?arguments[1]:void 0,r),c=i>2?arguments[2]:void 0,l=void 0===c?r:o(c,r);l>s;)t[s++]=e;return t}},function(e,t,r){"use strict";var n=r(65),o=r(262),a=r(98),i=r(33);e.exports=r(183)(Array,"Array",(function(e,t){this._t=i(e),this._i=0,this._k=t}),(function(){var e=this._t,t=this._k,r=this._i++;return!e||r>=e.length?(this._t=void 0,o(1)):o(0,"keys"==t?r:"values"==t?e[r]:[r,e[r]])}),"values"),a.Arguments=a.Array,n("keys"),n("values"),n("entries")},function(e,t,r){"use strict";var n,o,a=r(113),i=RegExp.prototype.exec,s=String.prototype.replace,c=i,l=(n=/a/,o=/b*/g,i.call(n,"a"),i.call(o,"a"),0!==n.lastIndex||0!==o.lastIndex),d=void 0!==/()??/.exec("")[1];(l||d)&&(c=function(e){var t,r,n,o,c=this;return d&&(r=new RegExp("^"+c.source+"$(?!\\s)",a.call(c))),l&&(t=c.lastIndex),n=i.call(c,e),l&&n&&(c.lastIndex=c.global?n.index+n[0].length:t),d&&n&&n.length>1&&s.call(n[0],r,(function(){for(o=1;or;)t.push(arguments[r++]);return b[++g]=function(){s("function"==typeof e?e:Function(e),t)},n(g),g},f=function(e){delete b[e]},"process"==r(44)(u)?n=function(e){u.nextTick(i(v,e,1))}:h&&h.now?n=function(e){h.now(i(v,e,1))}:m?(a=(o=new m).port2,o.port1.onmessage=y,n=i(a.postMessage,a,1)):d.addEventListener&&"function"==typeof postMessage&&!d.importScripts?(n=function(e){d.postMessage(e+"","*")},d.addEventListener("message",y,!1)):n="onreadystatechange"in l("script")?function(e){c.appendChild(l("script")).onreadystatechange=function(){c.removeChild(this),v.call(e)}}:function(e){setTimeout(i(v,e,1),0)}),e.exports={set:p,clear:f}},function(e,t,r){var n=r(8),o=r(195).set,a=n.MutationObserver||n.WebKitMutationObserver,i=n.process,s=n.Promise,c="process"==r(44)(i);e.exports=function(){var e,t,r,l=function(){var n,o;for(c&&(n=i.domain)&&n.exit();e;){o=e.fn,e=e.next;try{o()}catch(n){throw e?r():t=void 0,n}}t=void 0,n&&n.enter()};if(c)r=function(){i.nextTick(l)};else if(!a||n.navigator&&n.navigator.standalone)if(s&&s.resolve){var d=s.resolve(void 0);r=function(){d.then(l)}}else r=function(){o.call(n,l)};else{var u=!0,p=document.createTextNode("");new a(l).observe(p,{characterData:!0}),r=function(){p.data=u=!u}}return function(n){var o={fn:n,next:void 0};t&&(t.next=o),e||(e=o,r()),t=o}}},function(e,t,r){"use strict";var n=r(22);function o(e){var t,r;this.promise=new e((function(e,n){if(void 0!==t||void 0!==r)throw TypeError("Bad Promise constructor");t=e,r=n})),this.resolve=n(t),this.reject=n(r)}e.exports.f=function(e){return new o(e)}},function(e,t,r){"use strict";var n=r(8),o=r(17),a=r(63),i=r(137),s=r(26),c=r(80),l=r(10),d=r(78),u=r(45),p=r(16),f=r(272),m=r(76).f,h=r(20).f,g=r(191),b=r(95),v=n.ArrayBuffer,y=n.DataView,_=n.Math,w=n.RangeError,k=n.Infinity,x=v,M=_.abs,S=_.pow,E=_.floor,L=_.log,D=_.LN2,C=o?"_b":"buffer",O=o?"_l":"byteLength",T=o?"_o":"byteOffset";function j(e,t,r){var n,o,a,i=new Array(r),s=8*r-t-1,c=(1<>1,d=23===t?S(2,-24)-S(2,-77):0,u=0,p=e<0||0===e&&1/e<0?1:0;for((e=M(e))!=e||e===k?(o=e!=e?1:0,n=c):(n=E(L(e)/D),e*(a=S(2,-n))<1&&(n--,a*=2),(e+=n+l>=1?d/a:d*S(2,1-l))*a>=2&&(n++,a/=2),n+l>=c?(o=0,n=c):n+l>=1?(o=(e*a-1)*S(2,t),n+=l):(o=e*S(2,l-1)*S(2,t),n=0));t>=8;i[u++]=255&o,o/=256,t-=8);for(n=n<0;i[u++]=255&n,n/=256,s-=8);return i[--u]|=128*p,i}function A(e,t,r){var n,o=8*r-t-1,a=(1<>1,s=o-7,c=r-1,l=e[c--],d=127&l;for(l>>=7;s>0;d=256*d+e[c],c--,s-=8);for(n=d&(1<<-s)-1,d>>=-s,s+=t;s>0;n=256*n+e[c],c--,s-=8);if(0===d)d=1-i;else{if(d===a)return n?NaN:l?-k:k;n+=S(2,t),d-=i}return(l?-1:1)*n*S(2,d-t)}function N(e){return e[3]<<24|e[2]<<16|e[1]<<8|e[0]}function P(e){return[255&e]}function I(e){return[255&e,e>>8&255]}function Y(e){return[255&e,e>>8&255,e>>16&255,e>>24&255]}function z(e){return j(e,52,8)}function F(e){return j(e,23,4)}function R(e,t,r){h(e.prototype,t,{get:function(){return this[r]}})}function H(e,t,r,n){var o=f(+r);if(o+t>e[O])throw w("Wrong index!");var a=e[C]._b,i=o+e[T],s=a.slice(i,i+t);return n?s:s.reverse()}function B(e,t,r,n,o,a){var i=f(+r);if(i+t>e[O])throw w("Wrong index!");for(var s=e[C]._b,c=i+e[T],l=n(+o),d=0;dV;)(U=q[V++])in v||s(v,U,x[U]);a||(W.constructor=v)}var K=new y(new v(2)),G=y.prototype.setInt8;K.setInt8(0,2147483648),K.setInt8(1,2147483649),!K.getInt8(0)&&K.getInt8(1)||c(y.prototype,{setInt8:function(e,t){G.call(this,e,t<<24>>24)},setUint8:function(e,t){G.call(this,e,t<<24>>24)}},!0)}else v=function(e){d(this,v,"ArrayBuffer");var t=f(e);this._b=g.call(new Array(t),0),this[O]=t},y=function(e,t,r){d(this,y,"DataView"),d(e,v,"DataView");var n=e[O],o=u(t);if(o<0||o>n)throw w("Wrong offset!");if(o+(r=void 0===r?n-o:p(r))>n)throw w("Wrong length!");this[C]=e,this[T]=o,this[O]=r},o&&(R(v,"byteLength","_l"),R(y,"buffer","_b"),R(y,"byteLength","_l"),R(y,"byteOffset","_o")),c(y.prototype,{getInt8:function(e){return H(this,1,e)[0]<<24>>24},getUint8:function(e){return H(this,1,e)[0]},getInt16:function(e){var t=H(this,2,e,arguments[1]);return(t[1]<<8|t[0])<<16>>16},getUint16:function(e){var t=H(this,2,e,arguments[1]);return t[1]<<8|t[0]},getInt32:function(e){return N(H(this,4,e,arguments[1]))},getUint32:function(e){return N(H(this,4,e,arguments[1]))>>>0},getFloat32:function(e){return A(H(this,4,e,arguments[1]),23,4)},getFloat64:function(e){return A(H(this,8,e,arguments[1]),52,8)},setInt8:function(e,t){B(this,1,e,P,t)},setUint8:function(e,t){B(this,1,e,P,t)},setInt16:function(e,t){B(this,2,e,I,t,arguments[2])},setUint16:function(e,t){B(this,2,e,I,t,arguments[2])},setInt32:function(e,t){B(this,4,e,Y,t,arguments[2])},setUint32:function(e,t){B(this,4,e,Y,t,arguments[2])},setFloat32:function(e,t){B(this,4,e,F,t,arguments[2])},setFloat64:function(e,t){B(this,8,e,z,t,arguments[2])}});b(v,"ArrayBuffer"),b(y,"DataView"),s(y.prototype,i.VIEW,!0),t.ArrayBuffer=v,t.DataView=y},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t,r){var n=r(720),o=r(116);e.exports=function e(t,r,a,i,s){return t===r||(null==t||null==r||!o(t)&&!o(r)?t!=t&&r!=r:n(t,r,a,i,e,s))}},function(e,t,r){var n=r(82)(r(60),"Map");e.exports=n},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t,r){var n=r(737),o=r(744),a=r(746),i=r(747),s=r(748);function c(e){var t=-1,r=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e<=9007199254740991}},function(e,t,r){var n=r(61),o=r(207),a=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,i=/^\w*$/;e.exports=function(e,t){if(n(e))return!1;var r=typeof e;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=e&&!o(e))||(i.test(e)||!a.test(e)||null!=t&&e in Object(t))}},function(e,t,r){var n=r(115),o=r(116);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==n(e)}},function(e,t,r){var n=r(101);e.exports=function(e,t){if(!n(e))return e;var r,o;if(t&&"function"==typeof(r=e.toString)&&!n(o=r.call(e)))return o;if("function"==typeof(r=e.valueOf)&&!n(o=r.call(e)))return o;if(!t&&"function"==typeof(r=e.toString)&&!n(o=r.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},function(e,t){e.exports=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e}},function(e,t){var r=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:r)(e)}},function(e,t,r){var n=r(212)("keys"),o=r(150);e.exports=function(e){return n[e]||(n[e]=o(e))}},function(e,t,r){var n=r(67),o=r(66),a=o["__core-js_shared__"]||(o["__core-js_shared__"]={});(e.exports=function(e,t){return a[e]||(a[e]=void 0!==t?t:{})})("versions",[]).push({version:n.version,mode:r(149)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,r){var n=r(209);e.exports=function(e){return Object(n(e))}},function(e,t){e.exports={}},function(e,t,r){var n=r(117),o=r(836),a=r(213),i=r(211)("IE_PROTO"),s=function(){},c=function(){var e,t=r(431)("iframe"),n=a.length;for(t.style.display="none",r(837).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" + + +
    +
    +
    +
    +

    Congratulations!

    +

    Your Web-Base Installation is now ready to use!

    +
    +

    + You can now login into your Administrator Dashboard to adjust your settings + and add routes & pages. + You can add new documents and views by adding classes in the corresponding + directories and link to them, by creating rules in the Administrator Dashboard. +

    +
    +
    +
    +
    + \ No newline at end of file diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index aab7ea0..0000000 --- a/test/.gitignore +++ /dev/null @@ -1,110 +0,0 @@ - -# Created by https://www.gitignore.io/api/python -# Edit at https://www.gitignore.io/?templates=python - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# End of https://www.gitignore.io/api/python diff --git a/test/.htaccess b/test/.htaccess deleted file mode 100644 index d3223d4..0000000 --- a/test/.htaccess +++ /dev/null @@ -1 +0,0 @@ -DENY FROM ALL diff --git a/test/README.md b/test/README.md deleted file mode 100644 index c63fb28..0000000 --- a/test/README.md +++ /dev/null @@ -1,39 +0,0 @@ -## Web-Base Test Suite - -### Introduction - -This script performs database and API tests on a clean local environment. It assumes, the web-base is running on http://localhost/. The test tool can either -use an existing database or create a temporary database (recommended). - -### Usage - -To use this tool, some requirements must be installed. This can be done using: `pip install -r requirements.txt` - -``` -usage: test.py [-h] [--username USERNAME] [--password PASSWORD] [--host HOST] - [--port PORT] [--database DATABASE] [--force] - DBMS - -Web-Base database test suite - -positional arguments: - DBMS the dbms to setup, must be one of: mysql, postgres, - oracle - -optional arguments: - -h, --help show this help message and exit - --username USERNAME, -u USERNAME - the username used for connecting to the dbms, default: - root - --password PASSWORD, -p PASSWORD - the password used for connecting to the dbms, default: - (empty) - --host HOST, -H HOST the host where the dbms is running on, default: - localhost - --port PORT, -P PORT the port where the dbms is running on, default: - (depends on dbms) - --database DATABASE, -d DATABASE - the name of the database for the test suite, default: - randomly chosen and created - --force Delete existing configuration files -``` diff --git a/test/apiTest.py b/test/apiTest.py deleted file mode 100644 index fc324a0..0000000 --- a/test/apiTest.py +++ /dev/null @@ -1,72 +0,0 @@ -from phpTest import PhpTest - -class ApiTestCase(PhpTest): - - def __init__(self): - super().__init__({ - "Testing login…": self.test_login, - "Testing already logged in…": self.test_already_logged_in, - - # ApiKeys - "Testing get api keys empty…": self.test_get_api_keys_empty, - "Testing create api key…": self.test_create_api_key, - "Testing referesh api key…": self.test_refresh_api_key, - "Testing revoke api key…": self.test_revoke_api_key, - - # Notifications - "Testing fetch notifications…": self.test_fetch_notifications, - - "Testing logout…": self.test_logout, - }) - - def api(self, method): - return "/api/%s" % method - - def getApiKeys(self): - obj = self.httpPost(self.api("apiKey/fetch")) - self.assertEquals(True, obj["success"], obj["msg"]) - return obj - - def test_login(self): - obj = self.httpPost(self.api("user/login"), data={ "username": PhpTest.ADMIN_USERNAME, "password": PhpTest.ADMIN_PASSWORD }) - self.assertEquals(True, obj["success"], obj["msg"]) - return obj - - def test_already_logged_in(self): - obj = self.test_login() - self.assertEquals("You are already logged in", obj["msg"]) - - def test_get_api_keys_empty(self): - obj = self.getApiKeys() - self.assertEquals([], obj["api_keys"]) - - def test_create_api_key(self): - obj = self.httpPost(self.api("apiKey/create")) - self.assertEquals(True, obj["success"], obj["msg"]) - self.assertTrue("api_key" in obj) - self.apiKey = obj["api_key"] - - obj = self.getApiKeys() - self.assertEquals(1, len(obj["api_keys"])) - self.assertDictEqual(self.apiKey, obj["api_keys"][0]) - - def test_refresh_api_key(self): - obj = self.httpPost(self.api("apiKey/refresh"), data={"id": self.apiKey["uid"]}) - self.assertEquals(True, obj["success"], obj["msg"]) - self.assertTrue("valid_until" in obj) - self.assertTrue(obj["valid_until"] >= self.apiKey["valid_until"]) - - def test_revoke_api_key(self): - obj = self.httpPost(self.api("apiKey/revoke"), data={"id": self.apiKey["uid"]}) - self.assertEquals(True, obj["success"], obj["msg"]) - self.test_get_api_keys_empty() - - def test_fetch_notifications(self): - obj = self.httpPost(self.api("notifications/fetch")) - self.assertEquals(True, obj["success"], obj["msg"]) - - def test_logout(self): - obj = self.httpPost(self.api("user/logout")) - self.assertEquals(True, obj["success"], obj["msg"]) - obj = self.httpPost(self.api("user/logout")) - self.assertEquals(False, obj["success"]) diff --git a/test/installTest.py b/test/installTest.py deleted file mode 100644 index 80ce7da..0000000 --- a/test/installTest.py +++ /dev/null @@ -1,53 +0,0 @@ -from phpTest import PhpTest - -import sys - -class InstallTestCase(PhpTest): - - def __init__(self, args): - super().__init__({ - "Testing connection…": self.test_connection, - "Testing database setup…": self.test_database_setup, - "Testing invalid usernames…": self.test_invalid_usernames, - "Testing invalid password…": self.test_invalid_password, - "Testing not matching password…": self.test_not_matching_passwords, - "Testing user creation…": self.test_create_user, - "Testing skip mail configuration…": self.test_skil_mail_config, - "Testing complete setup…": self.test_complete_setup, - }) - self.args = args - - def test_connection(self): - self.httpGet() - - def test_database_setup(self): - obj = self.httpPost(data=vars(self.args)) - self.assertEquals(True, obj["success"], obj["msg"]) - - def test_invalid_usernames(self): - for username in ["a", "a"*33]: - obj = self.httpPost(data={ "username": username, "password": "123456", "confirmPassword": "123456" }) - self.assertEquals(False, obj["success"]) - self.assertEquals("The username should be between 5 and 32 characters long", obj["msg"]) - - def test_invalid_password(self): - obj = self.httpPost(data={ "username": PhpTest.ADMIN_USERNAME, "password": "1", "confirmPassword": "1" }) - self.assertEquals(False, obj["success"]) - self.assertEquals("The password should be at least 6 characters long", obj["msg"]) - - def test_not_matching_passwords(self): - obj = self.httpPost(data={ "username": PhpTest.ADMIN_USERNAME, "password": "1", "confirmPassword": "2" }) - self.assertEquals(False, obj["success"]) - self.assertEquals("The given passwords do not match", obj["msg"]) - - def test_create_user(self): - obj = self.httpPost(data={ "username": PhpTest.ADMIN_USERNAME, "password": PhpTest.ADMIN_PASSWORD, "confirmPassword": PhpTest.ADMIN_PASSWORD }) - self.assertEquals(True, obj["success"], obj["msg"]) - - def test_skil_mail_config(self): - obj = self.httpPost(data={ "skip": "true" }) - self.assertEquals(True, obj["success"], obj["msg"]) - - def test_complete_setup(self): - res = self.httpGet() - self.assertTrue("Installation finished" in res.text) diff --git a/test/phpTest.py b/test/phpTest.py deleted file mode 100644 index 19ca157..0000000 --- a/test/phpTest.py +++ /dev/null @@ -1,67 +0,0 @@ -import unittest -import string -import random -import requests -import re -import json -import sys - -class PhpTest(unittest.TestCase): - - def randomString(length): - letters = string.ascii_lowercase + string.ascii_uppercase + string.digits - return ''.join(random.choice(letters) for i in range(length)) - - ADMIN_USERNAME = "Administrator" - ADMIN_PASSWORD = randomString(16) - - def __init__(self, methods): - super().__init__("test_methods") - keywords = ["Fatal error", "Warning", "Notice", "Parse error", "Deprecated"] - self.methods = methods - self.phpPattern = re.compile("(%s):" % ("|".join(keywords))) - self.url = "http://localhost" - self.session = requests.Session() - - def httpError(self, res): - return "Server returned: %d %s" % (res.status_code, res.reason) - - def getPhpErrors(self, res): - return [line for line in res.text.split("\n") if self.phpPattern.search(line)] - - def httpGet(self, target="/"): - url = self.url + target - res = self.session.get(url) - self.assertEquals(200, res.status_code, self.httpError(res)) - self.assertEquals([], self.getPhpErrors(res)) - return res - - def httpPost(self, target="/", data={}): - url = self.url + target - res = self.session.post(url, data=data) - self.assertEquals(200, res.status_code, self.httpError(res)) - self.assertEquals([], self.getPhpErrors(res)) - obj = self.getJson(res) - return obj - - def getJson(self, res): - obj = None - try: - obj = json.loads(res.text) - except: - pass - finally: - self.assertTrue(isinstance(obj, dict), res.text) - return obj - - def test_methods(self): - print() - print("Running Tests in %s" % self.__class__.__name__) - for msg, method in self.methods.items(): - self.test_method(msg, method) - - def test_method(self, msg, method): - sys.stdout.write("[ ] %s" % msg) - method() - sys.stdout.write("\r[+] %s\n" % msg) - sys.stdout.flush() diff --git a/test/requirements.txt b/test/requirements.txt deleted file mode 100644 index 2134b16..0000000 --- a/test/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -requests==2.23.0 -psycopg2==2.8.4 -mysql_connector_repackaged==0.3.1 diff --git a/test/test.py b/test/test.py deleted file mode 100644 index eb63682..0000000 --- a/test/test.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -import argparse -import random -import string -import unittest - -import mysql.connector -import psycopg2 - -from installTest import InstallTestCase -from apiTest import ApiTestCase - -CONFIG_FILES = ["../core/Configuration/Database.class.php","../core/Configuration/JWT.class.php","../core/Configuration/Mail.class.php"] - -def randomName(length): - letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(length)) - -def performTest(args): - suite = unittest.TestSuite() - suite.addTest(InstallTestCase(args)) - suite.addTest(ApiTestCase()) - runner = unittest.TextTestRunner() - runner.run(suite) - -def testMysql(args): - - # Create a temporary database - if args.database is None: - args.database = "webbase_test_%s" % randomName(6) - config = { - "host": args.host, - "user": args.username, - "passwd": args.password, - "port": args.port - } - - print("[ ] Connecting to dbms…") - connection = mysql.connector.connect(**config) - print("[+] Success") - cursor = connection.cursor() - print("[ ] Creating temporary databse %s" % args.database) - cursor.execute("CREATE DATABASE %s" % args.database) - print("[+] Success") - - # perform test - try: - args.type = "mysql" - performTest(args) - finally: - if cursor is not None: - print("[ ] Deleting temporary database") - cursor.execute("DROP DATABASE %s" % args.database) - cursor.close() - print("[+] Success") - - if connection is not None: - print("[ ] Closing connection…") - connection.close() - -def testPostgres(args): - - # Create a temporary database - if args.database is None: - args.database = "webbase_test_%s" % randomName(6) - connection_string = "host=%s port=%d user=%s password=%s" % (args.host, args.port, args.username, args.password) - - print("[ ] Connecting to dbms…") - connection = psycopg2.connect(connection_string) - connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) - print("[+] Success") - cursor = connection.cursor() - print("[ ] Creating temporary databse %s" % args.database) - cursor.execute("CREATE DATABASE %s" % args.database) - print("[+] Success") - - # perform test - try: - args.type = "postgres" - performTest(args) - finally: - if cursor is not None: - print("[ ] Deleting temporary database") - cursor.execute("DROP DATABASE %s" % args.database) - cursor.close() - print("[+] Success") - - if connection is not None: - print("[ ] Closing connection…") - connection.close() - -if __name__ == "__main__": - - supportedDbms = { - "mysql": 3306, - "postgres": 5432, - "oracle": 1521 - } - - parser = argparse.ArgumentParser(description='Web-Base database test suite') - parser.add_argument('dbms', metavar='DBMS', type=str, help='the dbms to setup, must be one of: %s' % ", ".join(supportedDbms.keys())) - parser.add_argument('--username', '-u', type=str, help='the username used for connecting to the dbms, default: root', default='root') - parser.add_argument('--password', '-p', type=str, help='the password used for connecting to the dbms, default: (empty)', default='') - parser.add_argument('--host', '-H', type=str, help='the host where the dbms is running on, default: localhost', default='localhost') - parser.add_argument('--port', '-P', type=int, help='the port where the dbms is running on, default: (depends on dbms)') - parser.add_argument('--database', '-d', type=str, help='the name of the database for the test suite, default: randomly chosen and created') - parser.add_argument('--force', action='store_const', help='Delete existing configuration files', const=True) - - args = parser.parse_args() - if args.dbms not in supportedDbms: - print("Unsupported dbms. Supported values: %s" % ", ".join(supportedDbms.keys())) - exit(1) - - for f in CONFIG_FILES: - if os.path.isfile(f): - if not args.force: - print("File %s exists. The testsuite is required to perform tests on a clean environment. Specify --force to delete those files" % f) - exit(1) - else: - os.remove(f) - - if args.port is None: - args.port = supportedDbms[args.dbms] - - if args.dbms == "mysql": - testMysql(args) - elif args.dbms == "postgres": - testPostgres(args) - - for f in CONFIG_FILES: - if os.path.isfile(f): - os.remove(f)