diff --git a/api.php b/api.php index b45877d..870c44f 100644 --- a/api.php +++ b/api.php @@ -4,4 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -require __DIR__ . "/api/index.php"; \ No newline at end of file + +// Load in new API from legacy location (a.k.a. here) +require __DIR__ . "/api/index.php"; diff --git a/api/actions/listapps.php b/api/actions/listapps.php index 2947fb3..094713b 100644 --- a/api/actions/listapps.php +++ b/api/actions/listapps.php @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -$apps = EXTERNAL_APPS; +$apps = $SETTINGS['apps']; // Format paths as absolute URLs foreach ($apps as $k => $v) { if (strpos($apps[$k]['url'], "http") === FALSE) { diff --git a/api/actions/mobileenabled.php b/api/actions/mobileenabled.php index ffae16b..a530d86 100644 --- a/api/actions/mobileenabled.php +++ b/api/actions/mobileenabled.php @@ -6,4 +6,4 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -exitWithJson(["status" => "OK", "mobile" => MOBILE_ENABLED]); \ No newline at end of file +exitWithJson(["status" => "OK", "mobile" => $SETTINGS['mobile_enabled']]); \ No newline at end of file diff --git a/app.php b/app.php index 55a8421..15b740c 100644 --- a/app.php +++ b/app.php @@ -1,5 +1,4 @@ ; rel=preload; as=script", fals - <?php echo SITE_TITLE; ?> + <?php echo $SETTINGS['site_title']; ?> @@ -66,28 +65,35 @@ header("Link: ; rel=preload; as=script", fals get(MESSAGES[$_GET['msg']]['string'], false); + if (!empty($_GET['msg'])) { + if (array_key_exists($_GET['msg'], MESSAGES)) { + // optional string generation argument + if (empty($_GET['arg'])) { + $alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false); + } else { + $alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false); + } + $alerttype = MESSAGES[$_GET['msg']]['type']; + $alerticon = "square-o"; + switch (MESSAGES[$_GET['msg']]['type']) { + case "danger": + $alerticon = "times"; + break; + case "warning": + $alerticon = "exclamation-triangle"; + break; + case "info": + $alerticon = "info-circle"; + break; + case "success": + $alerticon = "check"; + break; + } } else { - $alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false); - } - $alerttype = MESSAGES[$_GET['msg']]['type']; - $alerticon = "square-o"; - switch (MESSAGES[$_GET['msg']]['type']) { - case "danger": - $alerticon = "times"; - break; - case "warning": - $alerticon = "exclamation-triangle"; - break; - case "info": - $alerticon = "info-circle"; - break; - case "success": - $alerticon = "check"; - break; + // We don't have a message for this, so just assume an error and escape stuff. + $alertmsg = htmlentities($Strings->get($_GET['msg'], false)); + $alerticon = "times"; + $alerttype = "danger"; } echo << @@ -121,7 +127,7 @@ END; - + @@ -194,4 +200,4 @@ END; } ?> - + \ No newline at end of file diff --git a/feed.php b/feed.php index 65d5dd3..ea2f5ee 100644 --- a/feed.php +++ b/feed.php @@ -54,12 +54,12 @@ switch ($_GET['type']) { die("400 Bad Request: feed parameter must have a value of \"rss\", \"rss1\", \"rss2\" or \"atom\"."); } -$feed->setTitle($Strings->build("Notifications from server for user", ['server' => SITE_TITLE, 'user' => $user->getName()], false)); +$feed->setTitle($Strings->build("Notifications from server for user", ['server' => $SETTINGS['site_title'], 'user' => $user->getName()], false)); -if (strpos(URL, "http") === 0) { - $url = URL; +if (strpos($SETTINGS['url'], "http") === 0) { + $url = $SETTINGS['url']; } else { - $url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . URL; + $url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url']; } $feed->setLink($url); diff --git a/index.php b/index.php index d1fceae..414accd 100644 --- a/index.php +++ b/index.php @@ -20,7 +20,7 @@ if (empty($VARS['progress'])) { // Easy way to remove "undefined" warnings. } else if ($VARS['progress'] == "1") { engageRateLimit(); - if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && Login::verifyCaptcha($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) { + if (!$SETTINGS['captcha']['enabled'] || ($SETTINGS['captcha']['enabled'] && Login::verifyCaptcha($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], $SETTINGS['captcha']['server'] . "/api.php"))) { $autherror = ""; $user = User::byUsername($VARS['username']); if ($user->exists()) { @@ -44,7 +44,7 @@ if (empty($VARS['progress'])) { break; case "ALERT_ON_ACCESS": $mail_resp = $user->sendAlertEmail(); - if (DEBUG) { + if ($SETTINGS['debug']) { var_dump($mail_resp); } $username_ok = true; @@ -143,16 +143,16 @@ header("Link: ; rel=preload; as=script", fals - <?php echo SITE_TITLE; ?> + <?php echo $SETTINGS['site_title']; ?> - - - + + +
@@ -197,7 +197,7 @@ header("Link: ; rel=preload; as=script", fals ?> " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus />
" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
- +

@@ -206,7 +206,7 @@ header("Link: ; rel=preload; as=script", fals } else if ($multiauth) { ?>
- get("2fa prompt"); ?> + get("2fa prompt"); ?>
" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus />
@@ -222,18 +222,17 @@ header("Link: ; rel=preload; as=script", fals } ?>
- - - - + + + \ No newline at end of file diff --git a/lib/FormBuilder.lib.php b/lib/FormBuilder.lib.php new file mode 100644 index 0000000..fddbff2 --- /dev/null +++ b/lib/FormBuilder.lib.php @@ -0,0 +1,257 @@ +title = $title; + $this->icon = $icon; + $this->action = $action; + $this->method = $method; + } + + /** + * Set the title of the form. + * @param string $title + */ + public function setTitle(string $title) { + $this->title = $title; + } + + /** + * Set the icon for the form. + * @param string $icon FontAwesome icon (example: "fas fa-toilet-paper") + */ + public function setIcon(string $icon) { + $this->icon = $icon; + } + + /** + * Set the URL the form will submit to. + * @param string $action + */ + public function setAction(string $action) { + $this->action = $action; + } + + /** + * Set the form submission method (GET, POST, etc) + * @param string $method + */ + public function setMethod(string $method = "POST") { + $this->method = $method; + } + + /** + * Set the form ID. + * @param string $id + */ + public function setID(string $id = "editform") { + $this->id = $id; + } + + /** + * Add an input to the form. + * + * @param string $name Element name + * @param string $value Element value + * @param string $type Input type (text, number, date, select, tel...) + * @param bool $required If the element is required for form submission. + * @param string $id Element ID + * @param array $options Array of [value => text] pairs for a select element + * @param string $label Text label to display near the input + * @param string $icon FontAwesome icon (example: "fas fa-toilet-paper") + * @param int $width Bootstrap column width for the input, out of 12. + * @param int $minlength Minimum number of characters for the input. + * @param int $maxlength Maximum number of characters for the input. + * @param string $pattern Regex pattern for custom client-side validation. + * @param string $error Message to show if the input doesn't validate. + */ + public function addInput(string $name, string $value = "", string $type = "text", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") { + $item = [ + "name" => $name, + "value" => $value, + "type" => $type, + "required" => $required, + "label" => $label, + "icon" => $icon, + "width" => $width, + "minlength" => $minlength, + "maxlength" => $maxlength + ]; + if (!empty($id)) { + $item["id"] = $id; + } + if (!empty($options) && $type == "select") { + $item["options"] = $options; + } + if (!empty($pattern)) { + $item["pattern"] = $pattern; + } + if (!empty($error)) { + $item["error"] = $error; + } + $this->items[] = $item; + } + + /** + * Add a button to the form. + * + * @param string $text Text string to show on the button. + * @param string $icon FontAwesome icon to show next to the text. + * @param string $href If not null, the button will actually be a hyperlink. + * @param string $type Usually "button" or "submit". Ignored if $href is set. + * @param string $id The element ID. + * @param string $name The element name for the button. + * @param string $value The form value for the button. Ignored if $name is null. + * @param string $class The CSS classes for the button, if a standard success-colored one isn't right. + */ + public function addButton(string $text, string $icon = "", string $href = null, string $type = "button", string $id = null, string $name = null, string $value = "", string $class = "btn btn-success") { + $button = [ + "text" => $text, + "icon" => $icon, + "class" => $class, + "type" => $type, + "id" => $id, + "href" => $href, + "name" => $name, + "value" => $value + ]; + $this->buttons[] = $button; + } + + /** + * Add a hidden input. + * @param string $name + * @param string $value + */ + public function addHiddenInput(string $name, string $value) { + $this->hiddenitems[$name] = $value; + } + + /** + * Generate the form HTML. + * @param bool $echo If false, returns HTML string instead of outputting it. + */ + public function generate(bool $echo = true) { + $html = << +
+

+
+ $this->title +
+

+ +
+
+HTMLTOP; + + foreach ($this->items as $item) { + $required = $item["required"] ? "required" : ""; + $id = empty($item["id"]) ? "" : "id=\"$item[id]\""; + $pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\""; + + $itemhtml = ""; + $itemhtml .= << +
+ +
+
+ +
+ITEMTOP; + if (empty($item['type']) || $item['type'] != "select") { + $itemhtml .= << +INPUT; + } else { + $itemhtml .= <<"; + } + + if (!empty($item["error"])) { + $itemhtml .= << + $item[error] +
+ERROR; + } + $itemhtml .= << +
+
\n +ITEMBOTTOM; + $html .= $itemhtml; + } + + $html .= << +
+HTMLBOTTOM; + + if (!empty($this->buttons)) { + $html .= "\n
"; + foreach ($this->buttons as $btn) { + $btnhtml = ""; + $inner = " $btn[text]"; + $id = empty($btn['id']) ? "" : "id=\"$btn[id]\""; + if (!empty($btn['href'])) { + $btnhtml = "$inner"; + } else { + $name = empty($btn['name']) ? "" : "name=\"$btn[name]\""; + $value = (!empty($btn['name']) && !empty($btn['value'])) ? "value=\"$btn[value]\"" : ""; + $btnhtml = ""; + } + $html .= "\n $btnhtml"; + } + $html .= "\n
"; + } + + $html .= "\n
"; + foreach ($this->hiddenitems as $name => $value) { + $value = htmlentities($value); + $html .= "\n "; + } + $html .= "\n\n"; + + if ($echo) { + echo $html; + } + return $html; + } + +} diff --git a/lib/User.lib.php b/lib/User.lib.php index 0949b1d..edc3127 100644 --- a/lib/User.lib.php +++ b/lib/User.lib.php @@ -119,7 +119,7 @@ class User { * @throws WeakPasswordException */ function changePassword(string $old, string $new, string $new2) { - global $database; + global $database, $SETTINGS; if ($old == $new) { throw new PasswordMatchException(); } @@ -137,7 +137,7 @@ class User { if ($passrank !== FALSE) { throw new WeakPasswordException(); } - if (strlen($new) < MIN_PASSWORD_LENGTH) { + if (strlen($new) < $SETTINGS['min_password_length']) { throw new WeakPasswordException(); } @@ -171,10 +171,11 @@ class User { * @return string OTP provisioning URI (for generating a QR code) */ function generate2fa(): string { + global $SETTINGS; $secret = random_bytes(20); $encoded_secret = Base32::encode($secret); $totp = new TOTP((empty($this->email) ? $this->realname : $this->email), $encoded_secret); - $totp->setIssuer(SYSTEM_NAME); + $totp->setIssuer($SETTINGS['system_name']); return $totp->getProvisioningUri(); } @@ -214,7 +215,11 @@ class User { return new AccountStatus($statuscode); } - function sendAlertEmail(string $appname = SITE_TITLE) { + function sendAlertEmail(string $appname = null) { + global $SETTINGS; + if (is_null($appname)) { + $appname = $SETTINGS['site_title']; + } if (empty(ADMIN_EMAIL) || filter_var(ADMIN_EMAIL, FILTER_VALIDATE_EMAIL) === FALSE) { return "invalid_to_email"; } @@ -224,19 +229,19 @@ class User { $mail = new PHPMailer; - if (DEBUG) { + if ($SETTINGS['debug']) { $mail->SMTPDebug = 2; } - if (USE_SMTP) { + if ($SETTINGS['email']['use_smtp']) { $mail->isSMTP(); - $mail->Host = SMTP_HOST; - $mail->SMTPAuth = SMTP_AUTH; - $mail->Username = SMTP_USER; - $mail->Password = SMTP_PASS; - $mail->SMTPSecure = SMTP_SECURE; - $mail->Port = SMTP_PORT; - if (SMTP_ALLOW_INVALID_CERTIFICATE) { + $mail->Host = $SETTINGS['email']['host']; + $mail->SMTPAuth = $SETTINGS['email']['auth']; + $mail->Username = $SETTINGS['email']['user']; + $mail->Password = $SETTINGS['email']['password']; + $mail->SMTPSecure = $SETTINGS['email']['secure']; + $mail->Port = $SETTINGS['email']['port']; + if ($SETTINGS['email']['allow_invalid_certificate']) { $mail->SMTPOptions = array( 'ssl' => array( 'verify_peer' => false, diff --git a/mobile/index.php b/mobile/index.php index 6985e68..7f8433a 100644 --- a/mobile/index.php +++ b/mobile/index.php @@ -18,7 +18,7 @@ if ($VARS['action'] == "ping") { exit(json_encode(["status" => "OK"])); } -if (MOBILE_ENABLED !== TRUE) { +if ($SETTINGS['mobile_enabled'] !== TRUE) { exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)])); } @@ -106,7 +106,7 @@ switch ($VARS['action']) { Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key); exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)])); case "listapps": - $apps = EXTERNAL_APPS; + $apps = $SETTINGS['apps']; // Format paths as absolute URLs foreach ($apps as $k => $v) { if (strpos($apps[$k]['url'], "http") === FALSE) { diff --git a/pages.php b/pages.php index a198cd3..4bec262 100644 --- a/pages.php +++ b/pages.php @@ -7,7 +7,7 @@ // List of pages and metadata define("PAGES", [ "home" => [ - "title" => "home", + "title" => "Home", "navbar" => true, "icon" => "fas fa-home", "styles" => [ diff --git a/pages/home.php b/pages/home.php index b0e1686..593d185 100644 --- a/pages/home.php +++ b/pages/home.php @@ -6,36 +6,9 @@ */ ?> - - -
-

- - "/> - - -

-
- - - */ -?> -
diff --git a/pages/security.php b/pages/security.php index 0a7ee74..414d661 100644 --- a/pages/security.php +++ b/pages/security.php @@ -30,7 +30,7 @@ $user = new User($_SESSION['uid']);
@@ -71,8 +71,8 @@ $user = new User($_SESSION['uid']); generate2fa(); - $label = SYSTEM_NAME . ":" . is_null($user->getEmail()) ? $user->getName() : $user->getEmail(); - $issuer = SYSTEM_NAME; + $label = $SETTINGS['system_name'] . ":" . is_null($user->getEmail()) ? $user->getName() : $user->getEmail(); + $issuer = $SETTINGS['system_name']; $qrCode = new QrCode($codeuri); $qrCode->setWriterByName('svg'); $qrCode->setSize(550); diff --git a/pages/sync.php b/pages/sync.php index 0bd1ed6..883277b 100644 --- a/pages/sync.php +++ b/pages/sync.php @@ -25,10 +25,10 @@ if (!empty($_GET['delsynccode'])) { $code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20)); $desc = htmlspecialchars($_POST['desc']); $database->insert('mobile_codes', ['uid' => $_SESSION['uid'], 'code' => $code, 'description' => $desc]); - if (strpos(URL, "http") === 0) { - $url = URL . "mobile/index.php"; + if (strpos($SETTINGS['url'], "http") === 0) { + $url = $SETTINGS['url'] . "mobile/index.php"; } else { - $url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . URL . "mobile/index.php"; + $url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url'] . "mobile/index.php"; } $encodedurl = str_replace("/", "\\", $url); $codeuri = "bizsync://" . $encodedurl . "/" . $_SESSION['username'] . "/" . $code; @@ -68,7 +68,7 @@ if (!empty($_GET['delsynccode'])) { $activecodes = $database->select("mobile_codes", ["codeid", "code", "description"], ["uid" => $_SESSION['uid']]); ?>

- build("sync explained", ["site_name" => SITE_TITLE]); ?> + build("sync explained", ["site_name" => $SETTINGS['site_title']]); ?>

" required /> @@ -142,10 +142,10 @@ if (!empty($_GET['delsynccode'])) { $database->insert('userkeys', ['uid' => $_SESSION['uid'], 'typeid' => 1, 'created' => date('Y-m-d H:i:s'), 'key' => $key]); } - if (strpos(URL, "http") === 0) { - $url = URL; + if (strpos($SETTINGS['url'], "http") === 0) { + $url = $SETTINGS['url']; } else { - $url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . URL; + $url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url']; } $url = $url . "feed.php?key=$key"; ?> diff --git a/required.php b/required.php index 7a168d6..20d83fb 100644 --- a/required.php +++ b/required.php @@ -33,7 +33,7 @@ session_start(); // stick some cookies in it // renew session cookie setcookie(session_name(), session_id(), time() + $session_length, "/", false, false); -$captcha_server = (CAPTCHA_ENABLED === true ? preg_replace("/http(s)?:\/\//", "", CAPTCHA_SERVER) : ""); +$captcha_server = ($SETTINGS['captcha']['enabled'] === true ? preg_replace("/http(s)?:\/\//", "", $SETTINGS['captcha']['server']) : ""); if ($_SESSION['mobile'] === TRUE) { header("Content-Security-Policy: " . "default-src 'self';" @@ -70,7 +70,7 @@ foreach ($libs as $lib) { require_once $lib; } -$Strings = new Strings(LANGUAGE); +$Strings = new Strings($SETTINGS['language']); /** * Kill off the running process and spit out an error message @@ -94,7 +94,7 @@ function sendError($error) { . "

" . htmlspecialchars($error) . "

"); } -date_default_timezone_set(TIMEZONE); +date_default_timezone_set($SETTINGS['timezone']); // Database settings // Also inits database and stuff @@ -103,12 +103,12 @@ use Medoo\Medoo; $database; try { $database = new Medoo([ - 'database_type' => DB_TYPE, - 'database_name' => DB_NAME, - 'server' => DB_SERVER, - 'username' => DB_USER, - 'password' => DB_PASS, - 'charset' => DB_CHARSET + 'database_type' => $SETTINGS['database']['type'], + 'database_name' => $SETTINGS['database']['name'], + 'server' => $SETTINGS['database']['server'], + 'username' => $SETTINGS['database']['user'], + 'password' => $SETTINGS['database']['password'], + 'charset' => $SETTINGS['database']['charset'] ]); } catch (Exception $ex) { //header('HTTP/1.1 500 Internal Server Error'); @@ -116,7 +116,7 @@ try { } -if (!DEBUG) { +if (!$SETTINGS['debug']) { error_reporting(0); } else { error_reporting(E_ALL); @@ -157,10 +157,9 @@ function checkDBError($specials = []) { } } - function redirectIfNotLoggedIn() { if ($_SESSION['loggedin'] !== TRUE) { - header('Location: ' . URL . '/login.php'); + header('Location: ' . $SETTINGS['url'] . '/index.php'); die(); } } @@ -186,4 +185,4 @@ function engageRateLimit() { // Add a record for the IP address $database->insert('rate_limit', ["ipaddr" => IPUtils::getClientIP(), "lastaction" => date("Y-m-d H:i:s")]); } -} \ No newline at end of file +} diff --git a/settings.template.php b/settings.template.php index f919db8..f27e355 100644 --- a/settings.template.php +++ b/settings.template.php @@ -1,172 +1,164 @@ [ - "url" => "/accounthub", - "mobileapi" => "/mobile/index.php", - "icon" => "/static/img/logo.svg", - "title" => SITE_TITLE + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// Settings for the app. +// Copy to settings.php and customize. + +$SETTINGS = [ + // Whether to output debugging info like PHP notices, warnings, + // and stacktraces. + // Turning this on in production is a security risk and can sometimes break + // things, such as JSON output where extra content is not expected. + "debug" => false, + // Database connection settings + // See http://medoo.in/api/new for info + "database" => [ + "type" => "mysql", + "name" => "accounthub", + "server" => "localhost", + "user" => "accounthub", + "password" => "", + "charset" => "utf8" ], - "qwikclock" => [ - "url" => "/qwikclock", - "mobileapi" => "/mobile/index.php", - "icon" => "/static/img/logo.svg", - "title" => "QwikClock", - "station_features" => [ - "qwikclock_punchinout", - "qwikclock_myshifts", - "qwikclock_jobs" + // Name of the app. + "site_title" => "AccountHub", + // Used to identify the system in OTP and other places + "system_name" => "Netsyms AccountHub", + // Allow login from the Netsyms mobile app + "mobile_enabled" => true, + // For supported values, see http://php.net/manual/en/timezones.php + "timezone" => "America/Denver", + // List of external apps connected to this system. + // This list is used for generating the dashboard cards and in the + // mobile app. + "apps" => [ + "accounthub" => [ + "url" => "/accounthub", + "mobileapi" => "/mobile/index.php", + "icon" => "/static/img/logo.svg", + "title" => $SETTINGS['site_title'] ], - "card" => [ - "color" => "blue", - "string" => "Punch in and check work schedule" - ] - ], - "binstack" => [ - "url" => "/binstack", - "mobileapi" => "/mobile/index.php", - "icon" => "/static/img/logo.svg", - "title" => "BinStack", - "card" => [ - "color" => "green", - "string" => "Manage physical items" - ] - ], - "newspen" => [ - "url" => "/newspen", - "mobileapi" => "/mobile/index.php", - "icon" => "/static/img/logo.svg", - "title" => "NewsPen", - "card" => [ - "color" => "purple", - "string" => "Create and publish e-newsletters" - ] - ], - "managepanel" => [ - "url" => "/managepanel", - "mobileapi" => "/mobile/index.php", - "icon" => "/static/img/logo.svg", - "title" => "ManagePanel", - "card" => [ - "color" => "brown", - "string" => "Manage users, permissions, and security" - ] - ], - "nickelbox" => [ - "url" => "/nickelbox", - "mobileapi" => "/mobile/index.php", - "icon" => "/static/img/logo.svg", - "title" => "NickelBox", - "card" => [ - "color" => "light-green", - "text" => "dark", - "string" => "Checkout customers and manage online orders" + "qwikclock" => [ + "url" => "/qwikclock", + "mobileapi" => "/mobile/index.php", + "icon" => "/static/img/logo.svg", + "title" => "QwikClock", + "station_features" => [ + "qwikclock_punchinout", + "qwikclock_myshifts", + "qwikclock_jobs" + ], + "card" => [ + "color" => "blue", + "string" => "Punch in and check work schedule" + ] + ], + "binstack" => [ + "url" => "/binstack", + "mobileapi" => "/mobile/index.php", + "icon" => "/static/img/logo.svg", + "title" => "BinStack", + "card" => [ + "color" => "green", + "string" => "Manage physical items" + ] + ], + "newspen" => [ + "url" => "/newspen", + "mobileapi" => "/mobile/index.php", + "icon" => "/static/img/logo.svg", + "title" => "NewsPen", + "card" => [ + "color" => "purple", + "string" => "Create and publish e-newsletters" + ] + ], + "managepanel" => [ + "url" => "/managepanel", + "mobileapi" => "/mobile/index.php", + "icon" => "/static/img/logo.svg", + "title" => "ManagePanel", + "card" => [ + "color" => "brown", + "string" => "Manage users, permissions, and security" + ] + ], + "nickelbox" => [ + "url" => "/nickelbox", + "mobileapi" => "/mobile/index.php", + "icon" => "/static/img/logo.svg", + "title" => "NickelBox", + "card" => [ + "color" => "light-green", + "text" => "dark", + "string" => "Checkout customers and manage online orders" + ] + ], + "sitewriter" => [ + "url" => "/sitewriter", + "mobileapi" => "/mobile/index.php", + "icon" => "/static/img/logo.svg", + "title" => "SiteWriter", + "card" => [ + "color" => "light-blue", + "string" => "Build websites and manage contact form messages" + ] + ], + "taskfloor" => [ + "url" => "/taskfloor", + "mobileapi" => "/mobile/index.php", + "icon" => "/static/img/logo.svg", + "title" => "TaskFloor", + "station_features" => [ + "taskfloor_viewtasks", + "taskfloor_viewmessages" + ], + "card" => [ + "color" => "blue-grey", + "string" => "Track jobs and assigned tasks" + ] ] ], - "sitewriter" => [ - "url" => "/sitewriter", - "mobileapi" => "/mobile/index.php", - "icon" => "/static/img/logo.svg", - "title" => "SiteWriter", - "card" => [ - "color" => "light-blue", - "string" => "Build websites and manage contact form messages" - ] + // Settings for sending emails. + "email" => [ + // If false, will use PHP mail() instead of a server + "use_smtp" => true, + // Admin email for alerts + "admin_email" => "", + "from" => "alert-noreply@example.com", + "host" => "", + "auth" => true, + "port" => 587, + "secure" => "tls", + "user" => "", + "password" => "", + "allow_invalid_certificate" => true ], - "taskfloor" => [ - "url" => "/taskfloor", - "mobileapi" => "/mobile/index.php", - "icon" => "/static/img/logo.svg", - "title" => "TaskFloor", - "station_features" => [ - "taskfloor_viewtasks", - "taskfloor_viewmessages" - ], - "card" => [ - "color" => "blue-grey", - "string" => "Track jobs and assigned tasks" - ] + "min_password_length" => 8, + // Show or hide the Station PIN setup option. + "station_kiosk" => true, + // Used for notification timestamp display. + "datetime_format" => "M j, g:i a", + "time_format" => "g:i", + // Use Captcheck on login screen to slow down bots + // https://captcheck.netsyms.com + "captcha" => [ + "enabled" => false, + "server" => "https://captcheck.netsyms.com" ], -]); - -// Show or hide the Station PIN setup option. -define("STATION_KIOSK", true); - -// Used for notification timestamp display. -define("DATETIME_FORMAT", "M j, g:i a"); -define("TIME_FORMAT", "g:i"); - - -// Email settings for receiving admin alerts. -define("USE_SMTP", TRUE); // if FALSE, will use PHP's mail() instead -define("ADMIN_EMAIL", ""); -define("FROM_EMAIL", "alert-noreply@apps.biz.netsyms.com"); -define("SMTP_HOST", ""); -define("SMTP_AUTH", true); -define("SMTP_PORT", 587); -define("SMTP_SECURE", 'tls'); -define("SMTP_USER", ""); -define("SMTP_PASS", ""); -define("SMTP_ALLOW_INVALID_CERTIFICATE", TRUE); - -// Minimum length for new passwords -// The system checks new passwords against the 500 worst passwords and rejects -// any matches. -// If you want to have additional password requirements, go edit action.php. -// However, all that does is encourage people to use the infamous -// "post-it password manager". See also https://xkcd.com/936/ and -// http://stackoverflow.com/a/34166252 for reasons why forcing passwords -// like CaPs45$% is not actually a great idea. -// Encourage users to use 2-factor auth whenever possible. -define("MIN_PASSWORD_LENGTH", 8); - -// Maximum number of rows to get in a query. -define("QUERY_LIMIT", 1000); - - - -define("FOOTER_TEXT", ""); -define("COPYRIGHT_NAME", "Netsyms Technologies"); -////////////////////////////////////////////////////////////// + // Language to use for localization. See langs folder to add a language. + "language" => "en", + // Shown in the footer of all the pages. + "footer_text" => "", + // Also shown in the footer, but with "Copyright " in front. + "copyright" => "Netsyms Technologies", + // Base URL for building links relative to the location of the app. + // Only used when there's no good context for the path. + // The default is almost definitely fine. + "url" => "." +]; diff --git a/static/css/svg-with-js.min.css b/static/css/svg-with-js.min.css index d303435..21ad29b 100644 --- a/static/css/svg-with-js.min.css +++ b/static/css/svg-with-js.min.css @@ -1,5 +1,5 @@ /*! - * Font Awesome Free 5.3.1 by @fontawesome - https://fontawesome.com + * Font Awesome Free 5.6.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) */ -.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;transform:scale(.25);transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;transform:scale(.25);transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;transform:scale(.25);transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1em}.svg-inline--fa.fa-stack-2x{height:2em;width:2em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto} \ No newline at end of file +.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;transform:scale(.25);transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;transform:scale(.25);transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;transform:scale(.25);transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto} \ No newline at end of file diff --git a/static/js/fontawesome-all.min.js b/static/js/fontawesome-all.min.js index b08e9a6..498e4a0 100644 --- a/static/js/fontawesome-all.min.js +++ b/static/js/fontawesome-all.min.js @@ -1,5 +1,5 @@ /*! - * Font Awesome Free 5.3.1 by @fontawesome - https://fontawesome.com + * Font Awesome Free 5.6.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) */ -!function(){"use strict";var c={};try{"undefined"!=typeof window&&(c=window)}catch(c){}var l=(c.navigator||{}).userAgent,h=void 0===l?"":l,z=c,v=(~h.indexOf("MSIE")||h.indexOf("Trident/"),"___FONT_AWESOME___"),m=function(){try{return!0}catch(c){return!1}}(),s=[1,2,3,4,5,6,7,8,9,10],e=s.concat([11,12,13,14,15,16,17,18,19,20]);["xs","sm","lg","fw","ul","li","border","pull-left","pull-right","spin","pulse","rotate-90","rotate-180","rotate-270","flip-horizontal","flip-vertical","stack","stack-1x","stack-2x","inverse","layers","layers-text","layers-counter"].concat(s.map(function(c){return c+"x"})).concat(e.map(function(c){return"w-"+c}));var a=z||{};a[v]||(a[v]={}),a[v].styles||(a[v].styles={}),a[v].hooks||(a[v].hooks={}),a[v].shims||(a[v].shims=[]);var t=a[v],M=Object.assign||function(c){for(var l=1;l>>0;h--;)l[h]=c[h];return l}function U(c){return c.classList?X(c.classList):(c.getAttribute("class")||"").split(" ").filter(function(c){return c})}function K(c,l){var h,z=l.split("-"),v=z[0],m=z.slice(1).join("-");return v!==c||""===m||(h=m,~w.indexOf(h))?null:m}function G(c){return(""+c).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function J(h){return Object.keys(h||{}).reduce(function(c,l){return c+(l+": ")+h[l]+";"},"")}function Q(c){return c.size!==W.size||c.x!==W.x||c.y!==W.y||c.rotate!==W.rotate||c.flipX||c.flipY}function Z(c){var l=c.transform,h=c.containerWidth,z=c.iconWidth;return{outer:{transform:"translate("+h/2+" 256)"},inner:{transform:"translate("+32*l.x+", "+32*l.y+") "+" "+("scale("+l.size/16*(l.flipX?-1:1)+", "+l.size/16*(l.flipY?-1:1)+") ")+" "+("rotate("+l.rotate+" 0 0)")},path:{transform:"translate("+z/2*-1+" -256)"}}}var $={x:0,y:0,width:"100%",height:"100%"},cc=function(c){var l=c.children,h=c.attributes,z=c.main,v=c.mask,m=c.transform,s=z.width,e=z.icon,a=v.width,t=v.icon,M=Z({transform:m,containerWidth:a,iconWidth:s}),f={tag:"rect",attributes:A({},$,{fill:"white"})},r={tag:"g",attributes:A({},M.inner),children:[{tag:"path",attributes:A({},e.attributes,M.path,{fill:"black"})}]},H={tag:"g",attributes:A({},M.outer),children:[r]},i="mask-"+D(),n="clip-"+D(),V={tag:"defs",children:[{tag:"clipPath",attributes:{id:n},children:[t]},{tag:"mask",attributes:A({},$,{id:i,maskUnits:"userSpaceOnUse",maskContentUnits:"userSpaceOnUse"}),children:[f,H]}]};return l.push(V,{tag:"rect",attributes:A({fill:"currentColor","clip-path":"url(#"+n+")",mask:"url(#"+i+")"},$)}),{children:l,attributes:h}},lc=function(c){var l=c.children,h=c.attributes,z=c.main,v=c.transform,m=J(c.styles);if(0"+s.map(bc).join("")+""}var gc=function(){};function Sc(c){return"string"==typeof(c.getAttribute?c.getAttribute(g):null)}var yc={replace:function(c){var l=c[0],h=c[1].map(function(c){return bc(c)}).join("\n");if(l.parentNode&&l.outerHTML)l.outerHTML=h+(E.keepOriginalSource&&"svg"!==l.tagName.toLowerCase()?"\x3c!-- "+l.outerHTML+" --\x3e":"");else if(l.parentNode){var z=document.createElement("span");l.parentNode.replaceChild(z,l),z.outerHTML=h}},nest:function(c){var l=c[0],h=c[1];if(~U(l).indexOf(E.replacementClass))return yc.replace(c);var z=new RegExp(E.familyPrefix+"-.*");delete h[0].attributes.style;var v=h[0].attributes.class.split(" ").reduce(function(c,l){return l===E.replacementClass||l.match(z)?c.toSvg.push(l):c.toNode.push(l),c},{toNode:[],toSvg:[]});h[0].attributes.class=v.toSvg.join(" ");var m=h.map(function(c){return bc(c)}).join("\n");l.setAttribute("class",v.toNode.join(" ")),l.setAttribute(g,""),l.innerHTML=m}};function wc(h,c){var z="function"==typeof c?c:gc;0===h.length?z():(r.requestAnimationFrame||function(c){return c()})(function(){var c=!0===E.autoReplaceSvg?yc.replace:yc[E.autoReplaceSvg]||yc.replace,l=Mc.begin("mutate");h.map(c),l(),z()})}var kc=!1;var xc=null;function Ac(c){if(e&&E.observeMutations){var v=c.treeCallback,m=c.nodeCallback,s=c.pseudoElementsCallback,l=c.observeMutationsRoot,h=void 0===l?H.body:l;xc=new e(function(c){kc||X(c).forEach(function(c){if("childList"===c.type&&0li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);transform:scale(1,-1)}.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1,-1);transform:scale(-1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1em}.svg-inline--fa.fa-stack-2x{height:2em;width:2em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}";if("fa"!==l||h!==c){var v=new RegExp("\\.fa\\-","g"),m=new RegExp("\\."+c,"g");z=z.replace(v,"."+l+"-").replace(m,"."+h)}return z};function zl(c){return{found:!0,width:c[0],height:c[1],icon:{tag:"path",attributes:{fill:"currentColor",d:c.slice(4)[0]}}}}function vl(){E.autoAddCss&&!tl&&(Y(hl()),tl=!0)}function ml(l,c){return Object.defineProperty(l,"abstract",{get:c}),Object.defineProperty(l,"html",{get:function(){return l.abstract.map(function(c){return bc(c)})}}),Object.defineProperty(l,"node",{get:function(){if(M){var c=H.createElement("div");return c.innerHTML=l.html,c.children}}}),l}function sl(c){var l=c.prefix,h=void 0===l?"fa":l,z=c.iconName;if(z)return pc(al.definitions,h,z)||pc(T.styles,h,z)}var el,al=new(function(){function c(){k(this,c),this.definitions={}}return x(c,[{key:"add",value:function(){for(var l=this,c=arguments.length,h=Array(c),z=0;z>>0;h--;)l[h]=c[h];return l}function W(c){return c.classList?D(c.classList):(c.getAttribute("class")||"").split(" ").filter(function(c){return c})}function Y(c,l){var h,z=l.split("-"),v=z[0],m=z.slice(1).join("-");return v!==c||""===m||(h=m,~x.indexOf(h))?null:m}function G(c){return"".concat(c).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function J(h){return Object.keys(h||{}).reduce(function(c,l){return c+"".concat(l,": ").concat(h[l],";")},"")}function Q(c){return c.size!==R.size||c.x!==R.x||c.y!==R.y||c.rotate!==R.rotate||c.flipX||c.flipY}function Z(c){var l=c.transform,h=c.containerWidth,z=c.iconWidth,v={transform:"translate(".concat(h/2," 256)")},m="translate(".concat(32*l.x,", ").concat(32*l.y,") "),s="scale(".concat(l.size/16*(l.flipX?-1:1),", ").concat(l.size/16*(l.flipY?-1:1),") "),e="rotate(".concat(l.rotate," 0 0)");return{outer:v,inner:{transform:"".concat(m," ").concat(s," ").concat(e)},path:{transform:"translate(".concat(z/2*-1," -256)")}}}var $={x:0,y:0,width:"100%",height:"100%"};function cc(c){var l=c.icons,h=l.main,z=l.mask,v=c.prefix,m=c.iconName,s=c.transform,e=c.symbol,a=c.title,M=c.extra,t=c.watchable,f=void 0!==t&&t,r=z.found?z:h,H=r.width,n=r.height,V="fa-w-".concat(Math.ceil(H/n*16)),i=[U.replacementClass,m?"".concat(U.familyPrefix,"-").concat(m):"",V].filter(function(c){return-1===M.classes.indexOf(c)}).concat(M.classes).join(" "),o={children:[],attributes:B({},M.attributes,{"data-prefix":v,"data-icon":m,class:i,role:"img",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 ".concat(H," ").concat(n)})};f&&(o.attributes[X]=""),a&&o.children.push({tag:"title",attributes:{id:o.attributes["aria-labelledby"]||"title-".concat(K())},children:[a]});var C,L,d,u,p,b,g,S,y,w,k,A,x,q,O,j,E,N,P,T,_,R,F,I=B({},o,{prefix:v,iconName:m,main:h,mask:z,transform:s,symbol:e,styles:M.styles}),D=z.found&&h.found?(L=(C=I).children,d=C.attributes,u=C.main,p=C.mask,b=C.transform,g=u.width,S=u.icon,y=p.width,w=p.icon,k=Z({transform:b,containerWidth:y,iconWidth:g}),A={tag:"rect",attributes:B({},$,{fill:"white"})},x={tag:"g",attributes:B({},k.inner),children:[{tag:"path",attributes:B({},S.attributes,k.path,{fill:"black"})}]},q={tag:"g",attributes:B({},k.outer),children:[x]},O="mask-".concat(K()),j="clip-".concat(K()),E={tag:"defs",children:[{tag:"clipPath",attributes:{id:j},children:[w]},{tag:"mask",attributes:B({},$,{id:O,maskUnits:"userSpaceOnUse",maskContentUnits:"userSpaceOnUse"}),children:[A,q]}]},L.push(E,{tag:"rect",attributes:B({fill:"currentColor","clip-path":"url(#".concat(j,")"),mask:"url(#".concat(O,")")},$)}),{children:L,attributes:d}):function(c){var l=c.children,h=c.attributes,z=c.main,v=c.transform,m=J(c.styles);if(0").concat(s.map(Lc).join(""),"")}var dc=function(){};function uc(c){return"string"==typeof(c.getAttribute?c.getAttribute(X):null)}var pc={replace:function(c){var l=c[0],h=c[1].map(function(c){return Lc(c)}).join("\n");if(l.parentNode&&l.outerHTML)l.outerHTML=h+(U.keepOriginalSource&&"svg"!==l.tagName.toLowerCase()?"\x3c!-- ".concat(l.outerHTML," --\x3e"):"");else if(l.parentNode){var z=document.createElement("span");l.parentNode.replaceChild(z,l),z.outerHTML=h}},nest:function(c){var l=c[0],h=c[1];if(~W(l).indexOf(U.replacementClass))return pc.replace(c);var z=new RegExp("".concat(U.familyPrefix,"-.*"));delete h[0].attributes.style;var v=h[0].attributes.class.split(" ").reduce(function(c,l){return l===U.replacementClass||l.match(z)?c.toSvg.push(l):c.toNode.push(l),c},{toNode:[],toSvg:[]});h[0].attributes.class=v.toSvg.join(" ");var m=h.map(function(c){return Lc(c)}).join("\n");l.setAttribute("class",v.toNode.join(" ")),l.setAttribute(X,""),l.innerHTML=m}};function bc(h,c){var z="function"==typeof c?c:dc;0===h.length?z():(H.requestAnimationFrame||function(c){return c()})(function(){var c=!0===U.autoReplaceSvg?pc.replace:pc[U.autoReplaceSvg]||pc.replace,l=sc.begin("mutate");h.map(c),l(),z()})}var gc=!1;var Sc=null;function yc(c){if(M&&U.observeMutations){var v=c.treeCallback,m=c.nodeCallback,s=c.pseudoElementsCallback,l=c.observeMutationsRoot,h=void 0===l?n.body:l;Sc=new M(function(c){gc||D(c).forEach(function(c){if("childList"===c.type&&0