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 = "$title ";
+ $button = "$title ";
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>$tag>";
+ else
+ return "<$tag>" . implode(" ", $items) . " $tag>";
+ }
+
+ 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, ">$tag>", $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>$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 " ";
- }
-
- public function createParagraph($title, $id, $content) {
- $id = replaceCssSelector($id);
- $iconId = urlencode("$id-icon");
- return "
- ";
- }
-
- public function createSimpleParagraph($content, $class="") {
- if($class) $class = " class=\"$class\"";
- return "$content
";
- }
-
- private function createList($items, $tag) {
- if(count($items) === 0)
- return "<$tag>$tag>";
- else
- return "<$tag>" . implode(" ", $items) . " $tag>";
- }
-
- 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
+
+
+
+
+
";
+
+ $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.
+ ";
+ }
+ }
+}
\ 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
+
+
+
+ ";
+
+ 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",
- " "
+ " "
);
}
- } 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
-
-
-
";
-
- 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+""+t+">"};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)