From deca0d330dcd75f50154a4f18ff46d697da3896d Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Mon, 9 Jul 2018 23:12:19 -0600 Subject: [PATCH] Remove home screen widgets, build new i18n system, close #4 --- apps/account_security.php | 24 -------- apps/inventory_link.php | 24 -------- apps/qwikclock_inout.php | 61 ------------------- apps/sample_app.php | 34 ----------- apps/sync_mobile.php | 13 ---- apps/taskfloor_messages.php | 67 -------------------- apps/taskfloor_tasks.php | 56 ----------------- langs/en/2fa.json | 16 +++++ langs/en/api.json | 3 + langs/en/core.json | 26 ++++++++ langs/en/ldap.json | 4 ++ langs/en/password.json | 13 ++++ langs/en/pin.json | 9 +++ langs/en/security.json | 7 +++ langs/en/sync.json | 12 ++++ langs/en/titles.json | 8 +++ lib/Strings.php | 118 ++++++++++++++++++++++++++++++++++++ pages.php | 8 +-- required.php | 39 +++--------- 19 files changed, 224 insertions(+), 318 deletions(-) delete mode 100644 apps/account_security.php delete mode 100644 apps/inventory_link.php delete mode 100644 apps/qwikclock_inout.php delete mode 100644 apps/sample_app.php delete mode 100644 apps/taskfloor_messages.php delete mode 100644 apps/taskfloor_tasks.php create mode 100644 langs/en/2fa.json create mode 100644 langs/en/api.json create mode 100644 langs/en/core.json create mode 100644 langs/en/ldap.json create mode 100644 langs/en/password.json create mode 100644 langs/en/pin.json create mode 100644 langs/en/security.json create mode 100644 langs/en/sync.json create mode 100644 langs/en/titles.json create mode 100644 lib/Strings.php diff --git a/apps/account_security.php b/apps/account_security.php deleted file mode 100644 index 7fa94b6..0000000 --- a/apps/account_security.php +++ /dev/null @@ -1,24 +0,0 @@ - [ - "manage account security" => "Manage account security", - "manage security description" => "Review security features or change your password." - ] -]); -$APPS["account_security"]["i18n"] = TRUE; -$APPS["account_security"]["title"] = "account security"; -$APPS["account_security"]["icon"] = "lock"; -$APPS["account_security"]["type"] = "brown"; -$content = "

" - . lang("manage security description", false) - . '

' - . '' - . lang("manage account security", false) - . ''; -$APPS["account_security"]["content"] = $content; -?> \ No newline at end of file diff --git a/apps/inventory_link.php b/apps/inventory_link.php deleted file mode 100644 index 37d1862..0000000 --- a/apps/inventory_link.php +++ /dev/null @@ -1,24 +0,0 @@ - [ - "inventory" => "Inventory", - "open inventory system" => "Open the inventory system" - ] -]); -$APPS["inventory_link"]["i18n"] = TRUE; -$APPS["inventory_link"]["title"] = "inventory"; -$APPS["inventory_link"]["icon"] = "cubes"; -$APPS["inventory_link"]["type"] = "teal"; -$content = "

" . lang("open inventory system", false) . '

' . lang("open app", false) . '  '; -$APPS["inventory_link"]["content"] = $content; - -require_once __DIR__ . "/../lib/login.php"; -if (account_has_permission($_SESSION['username'], "INV_VIEW") !== true) { - unset($APPS['inventory_link']); -} -?> \ No newline at end of file diff --git a/apps/qwikclock_inout.php b/apps/qwikclock_inout.php deleted file mode 100644 index 789ddbb..0000000 --- a/apps/qwikclock_inout.php +++ /dev/null @@ -1,61 +0,0 @@ - [ - "qwikclock" => "QwikClock", - "punch in" => "Punch in", - "punch out" => "Punch out", - "permission denied" => "You do not have permission to do that." - ] -]); -$APPS["qwikclock_inout"]["i18n"] = TRUE; -$APPS["qwikclock_inout"]["title"] = "qwikclock"; -$APPS["qwikclock_inout"]["icon"] = "clock-o"; -$APPS["qwikclock_inout"]["type"] = "blue"; -$content = ""; - -use GuzzleHttp\Exception\ClientException; - -if (!is_empty($_GET['qwikclock']) && ($_GET['qwikclock'] === "punchin" || $_GET['qwikclock'] === "punchout")) { - try { - $client = new GuzzleHttp\Client(); - - $response = $client->request('POST', QWIKCLOCK_API, ['form_params' => [ - 'action' => $_GET['qwikclock'], - 'username' => $_SESSION['username'], - 'password' => $_SESSION['password'] - ]]); - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - $content = "
" . $resp['msg'] . "
"; - } else { - $content = "
" . $resp['msg'] . "
"; - } - } catch (ClientException $e) { - if ($e->getResponse()->getStatusCode() == 403) { - $content = "
" . lang("permission denied", false) . "
"; - } - } catch (Exception $e) { - $content = "
" . lang("error loading widget", false) . " " . $e->getMessage() . "
"; - } -} -$lang_punchin = lang("punch in", false); -$lang_punchout = lang("punch out", false); -$content .= << $lang_punchin - $lang_punchout -END; -$content .= '
' . lang("open app", false) . '  '; -$APPS["qwikclock_inout"]["content"] = $content; - - -if (account_has_permission($_SESSION['username'], "QWIKCLOCK") !== true) { - unset($APPS['qwikclock_inout']); -} -?> \ No newline at end of file diff --git a/apps/sample_app.php b/apps/sample_app.php deleted file mode 100644 index 386965f..0000000 --- a/apps/sample_app.php +++ /dev/null @@ -1,34 +0,0 @@ - [ - "sample app" => "Sample Application", - ] -]); -// Set to true to automatically parse the app title as a language string. -$APPS["sample_app"]["i18n"] = TRUE; -// App title. -$APPS["sample_app"]["title"] = "sample app"; -// App icon, from FontAwesome. -$APPS["sample_app"]["icon"] = "rocket"; -// App content. -$APPS["sample_app"]["content"] = <<<'CONTENTEND' -
-
- Item 1 -
-
- Item 2 -
-
- Item 3 -
-
-CONTENTEND; -?> \ No newline at end of file diff --git a/apps/sync_mobile.php b/apps/sync_mobile.php index 4e9867c..d68693b 100644 --- a/apps/sync_mobile.php +++ b/apps/sync_mobile.php @@ -10,19 +10,6 @@ use Endroid\QrCode\ErrorCorrectionLevel; use Endroid\QrCode\QrCode; if (MOBILE_ENABLED) { - addMultiLangStrings(["en_us" => [ - "sync mobile" => "Sync Mobile App", - "scan sync qrcode" => "Scan this code with the mobile app or enter the code manually.", - "sync explained" => "Access your account and apps on the go. Use a sync code to securely connect your phone or tablet to AccountHub with the Netsyms Business mobile app.", - "generate sync" => "Create new sync code", - "active sync codes" => "Active codes", - "no active codes" => "No active codes.", - "done adding sync code" => "Done adding code", - "manual setup" => "Manual Setup:", - "sync key" => "Sync key:", - "url" => "URL:", - ] - ]); $APPS["sync_mobile"]["title"] = lang("sync mobile", false); $APPS["sync_mobile"]["icon"] = "mobile"; diff --git a/apps/taskfloor_messages.php b/apps/taskfloor_messages.php deleted file mode 100644 index 51a252c..0000000 --- a/apps/taskfloor_messages.php +++ /dev/null @@ -1,67 +0,0 @@ - [ - "messages" => "Messages", - "no messages" => "No messages found." - ] -]); -$APPS["taskfloor_messages"]["i18n"] = TRUE; -$APPS["taskfloor_messages"]["title"] = "messages"; -$APPS["taskfloor_messages"]["icon"] = "comments"; -$APPS["taskfloor_messages"]["type"] = "deep-purple"; -$content = ""; - -use GuzzleHttp\Exception\ClientException; -try { - $client = new GuzzleHttp\Client(); - - $response = $client->request('POST', TASKFLOOR_API, ['form_params' => [ - 'action' => "getmsgs", - 'username' => $_SESSION['username'], - 'password' => $_SESSION['password'], - 'max' => 5 - ]]); - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - if (count($resp['messages']) > 0) { - $content = '
'; - foreach ($resp['messages'] as $msg) { - $content .= '
'; - $content .= $msg['text']; - $fromuser = $msg['from']['username']; - $fromname = $msg['from']['name']; - $touser = $msg['to']['username']; - $toname = $msg['to']['name']; - $content .= << - - $fromname - - $toname - -END; - $content .= '
'; - } - $content .= "
"; - } else { - $content = "
" . lang("no messages", false) . "
"; - } - } - $content .= '' . lang("open app", false) . '  '; - $APPS["taskfloor_messages"]["content"] = $content; -} catch (ClientException $e) { - if ($e->getResponse()->getStatusCode() == 403) { - unset($APPS['taskfloor_messages']); - } -} catch (Exception $e) { - $content = "
" . lang("error loading widget", false) . " " . $e->getMessage() . "
"; - $content .= '' . lang("open app", false) . '  '; - $APPS["taskfloor_messages"]["content"] = $content; -} -?> \ No newline at end of file diff --git a/apps/taskfloor_tasks.php b/apps/taskfloor_tasks.php deleted file mode 100644 index f53a31c..0000000 --- a/apps/taskfloor_tasks.php +++ /dev/null @@ -1,56 +0,0 @@ - [ - "tasks" => "Tasks", - "no tasks found" => "No tasks found." - ] -]); -$APPS["taskfloor_tasks"]["i18n"] = TRUE; -$APPS["taskfloor_tasks"]["title"] = "tasks"; -$APPS["taskfloor_tasks"]["icon"] = "tasks"; -$APPS["taskfloor_tasks"]["type"] = "blue-grey"; -$content = ""; - -use GuzzleHttp\Exception\ClientException; - -try { - $client = new GuzzleHttp\Client(); - - $response = $client->request('POST', TASKFLOOR_API, ['form_params' => [ - 'action' => "gettasks", - 'username' => $_SESSION['username'], - 'password' => $_SESSION['password'], - 'max' => 5 - ]]); - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - if (count($resp['tasks']) > 0) { - $content = '
'; - foreach ($resp['tasks'] as $task) { - $content .= '
'; - $content .= ' ' . $task['title'] . ''; - $content .= '
'; - } - $content .= "
"; - } else { - $content = "
" . lang("no tasks found", false) . "
"; - } - } - $content .= '' . lang("open app", false) . '  '; - $APPS["taskfloor_tasks"]["content"] = $content; -} catch (ClientException $e) { - if ($e->getResponse()->getStatusCode() == 403) { - unset($APPS['taskfloor_tasks']); - } -} catch (Exception $e) { - $content = "
" . lang("error loading widget", false) . " " . $e->getMessage() . "
"; - $content .= '' . lang("open app", false) . '  '; - $APPS["taskfloor_tasks"]["content"] = $content; -} -?> \ No newline at end of file diff --git a/langs/en/2fa.json b/langs/en/2fa.json new file mode 100644 index 0000000..4241e35 --- /dev/null +++ b/langs/en/2fa.json @@ -0,0 +1,16 @@ +{ + "setup 2fa": "Setup 2-factor authentication", + "2fa removed": "2-factor authentication disabled.", + "2fa enabled": "2-factor authentication activated.", + "remove 2fa": "Disable 2-factor authentication", + "2fa explained": "2-factor authentication adds more security to your account. You can use the Auth Keys (key icon) feature of the Netsyms Business Mobile app, or another TOTP-enabled app (Authy, FreeOTP, etc) on your smartphone. When you have the app installed, you can enable 2-factor authentication by clicking the button below and scanning a QR code with the app. Whenever you sign in in the future, you'll need to input a six-digit code from your phone into the login page when prompted. You can disable 2-factor authentication from this page if you change your mind.", + "2fa active": "2-factor authentication is active on your account. To remove 2fa, reset your authentication secret, or change to a new security device, click the button below.", + "enable 2fa": "Enable 2-factor authentication", + "scan 2fa qrcode": "Scan the QR Code with the authenticator app, or enter the information manually. Then type in the six-digit code the app gives you and press Finish Setup.", + "confirm 2fa": "Finish setup", + "enter otp code": "Enter 6-digit code", + "secret key": "Secret key", + "label": "Label", + "issuer": "Issuer", + "no such code or code expired": "That code is incorrect or expired." +} diff --git a/langs/en/api.json b/langs/en/api.json new file mode 100644 index 0000000..cd7f081 --- /dev/null +++ b/langs/en/api.json @@ -0,0 +1,3 @@ +{ + "user does not exist": "User does not exist." +} diff --git a/langs/en/core.json b/langs/en/core.json new file mode 100644 index 0000000..28afcee --- /dev/null +++ b/langs/en/core.json @@ -0,0 +1,26 @@ +{ + "sign in": "Sign In", + "username": "Username", + "password": "Password", + "continue": "Continue", + "authcode": "Authentication code", + "2fa prompt": "Enter the six-digit code from your mobile authenticator app.", + "2fa incorrect": "Authentication code incorrect.", + "login incorrect": "Login incorrect.", + "login server unavailable": "Login server unavailable. Try again later or contact technical support.", + "account locked": "This account has been disabled. Contact technical support.", + "password expired": "You must change your password before continuing.", + "account terminated": "Account terminated. Access denied.", + "account state error": "Your account state is not stable. Log out, restart your browser, and try again.", + "welcome user": "Welcome, {user}!", + "sign out": "Sign out", + "404 error": "404 Error", + "page not found": "Page not found.", + "invalid parameters": "Invalid request parameters.", + "login server error": "The login server returned an error: {arg}", + "login server user data error": "The login server refused to provide account information. Try again or contact technical support.", + "captcha error": "There was a problem with the CAPTCHA (robot test). Try again.", + "no access permission": "You do not have permission to access this system.", + "generic op error": "An unknown error occurred. Try again later.", + "home": "Home" +} diff --git a/langs/en/ldap.json b/langs/en/ldap.json new file mode 100644 index 0000000..ed7eabd --- /dev/null +++ b/langs/en/ldap.json @@ -0,0 +1,4 @@ +{ + "ldap server error": "The LDAP server returned an error: {arg}", + "ldap error": "LDAP error: {error}" +} diff --git a/langs/en/password.json b/langs/en/password.json new file mode 100644 index 0000000..3a42198 --- /dev/null +++ b/langs/en/password.json @@ -0,0 +1,13 @@ +{ + "password on 500 list": "The given password is ranked number {arg} out of the 500 most common passwords. Try a different one.", + "change password": "Change password", + "current password incorrect": "The current password is incorrect. Try again.", + "new password mismatch": "The new passwords did not match. Try again.", + "weak password": "Password does not meet requirements.", + "password updated": "Password updated successfully.", + "current password": "Current password", + "new password": "New password", + "confirm password": "New password (again)", + "password complexity insufficent": "The new password does not meet the minumum requirements defined by your system administrator.", + "old and new passwords match": "Your current and new passwords are the same." +} diff --git a/langs/en/pin.json b/langs/en/pin.json new file mode 100644 index 0000000..29496ce --- /dev/null +++ b/langs/en/pin.json @@ -0,0 +1,9 @@ +{ + "pin explanation": "Change or set a login PIN for the Station kiosk Quick Access. PIN codes must be between one and eight digits.", + "change pin": "Change PIN", + "new pin": "New PIN", + "confirm pin": "New PIN (again)", + "pin updated": "PIN updated.", + "new pin mismatch": "The new PINs don't match each other.", + "invalid pin format": "PIN codes must be numeric and between one and eight digits in length." +} diff --git a/langs/en/security.json b/langs/en/security.json new file mode 100644 index 0000000..cdde52d --- /dev/null +++ b/langs/en/security.json @@ -0,0 +1,7 @@ +{ + "sign in again": "Please sign in again to continue.", + "login failed try on web": "There is a problem with your account. Visit AccountHub via a web browser for more information.", + "mobile login disabled": "Mobile login has been disabled by your system administrator. Contact technical support for more information.", + "admin alert email subject": "Alert: User login notification", + "admin alert email message": "You (or another administrator) requested to be notified when user \"{username}\" logged in, an event which happened just now.\r\n\r\nUsername: \t{username}\r\nApplication: \t{appname}\r\nDate\/Time: \t{datetime}\r\nIP address: \t{ipaddr}\r\n\r\nThese notifications can be disabled by editing the user in ManagePanel." +} diff --git a/langs/en/sync.json b/langs/en/sync.json new file mode 100644 index 0000000..be71be0 --- /dev/null +++ b/langs/en/sync.json @@ -0,0 +1,12 @@ +{ + "sync mobile": "Sync Mobile App", + "scan sync qrcode": "Scan this code with the mobile app or enter the code manually.", + "sync explained": "Access your account and apps on the go. Use a sync code to securely connect your phone or tablet to AccountHub with the Netsyms Business mobile app.", + "generate sync": "Create new sync code", + "active sync codes": "Active codes", + "no active codes": "No active codes.", + "done adding sync code": "Done adding code", + "manual setup": "Manual Setup:", + "sync key": "Sync key:", + "url": "URL:" +} diff --git a/langs/en/titles.json b/langs/en/titles.json new file mode 100644 index 0000000..ea261ca --- /dev/null +++ b/langs/en/titles.json @@ -0,0 +1,8 @@ +{ + "account security": "Account security", + "security options": "Security options", + "account options": "Account options", + "sync": "Sync settings", + "settings": "Settings", + "account": "Account" +} diff --git a/lib/Strings.php b/lib/Strings.php new file mode 100644 index 0000000..c99a665 --- /dev/null +++ b/lib/Strings.php @@ -0,0 +1,118 @@ +load("en"); + + if (file_exists(__DIR__ . "/../langs/$language/")) { + $this->language = $language; + $this->load($language); + } else { + trigger_error("Language $language could not be found.", E_USER_WARNING); + } + } + + /** + * Load all JSON files for the specified language. + * @param string $language + */ + private function load(string $language) { + $files = glob(__DIR__ . "/../langs/$language/*.json"); + foreach ($files as $file) { + $strings = json_decode(file_get_contents($file), true); + foreach ($strings as $key => $val) { + if (array_key_exists($key, $this->strings)) { + trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING); + } + $this->strings[$key] = $val; + } + } + } + + /** + * Add language strings dynamically. + * @param array $strings ["key" => "value", ...] + */ + function addStrings(array $strings) { + foreach ($strings as $key => $val) { + $this->strings[$key] = $val; + } + } + + /** + * I18N string getter. If the key isn't found, it outputs the key itself. + * @param string $key + * @param bool $echo True to echo the result, false to return it. Default is true. + * @return string + */ + function get(string $key, bool $echo = true): string { + $str = $key; + if (array_key_exists($key, $this->strings)) { + $str = $this->strings[$key]; + } else { + trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING); + } + + if ($echo) { + echo $str; + } + return $str; + } + + /** + * I18N string getter (with builder). If the key doesn't exist, outputs the key itself. + * @param string $key + * @param array $replace key-value array of replacements. + * If the string value is "hello {abc}" and you give ["abc" => "123"], the + * result will be "hello 123". + * @param bool $echo True to echo the result, false to return it. Default is true. + * @return string + */ + function build(string $key, array $replace, bool $echo = true): string { + $str = $key; + if (array_key_exists($key, $this->strings)) { + $str = $this->strings[$key]; + } else { + trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING); + } + + foreach ($replace as $find => $repl) { + $str = str_replace("{" . $find . "}", $repl, $str); + } + + if ($echo) { + echo $str; + } + return $str; + } + + /** + * Builds and returns a JSON key:value string for the supplied array of keys. + * @param array $keys ["key1", "key2", ...] + */ + function getJSON(array $keys): string { + $strings = []; + foreach ($keys as $k) { + $strings[$k] = $this->get($k, false); + } + return json_encode($strings); + } + +} diff --git a/pages.php b/pages.php index a3fb11b..4a63a8a 100644 --- a/pages.php +++ b/pages.php @@ -30,13 +30,7 @@ define("PAGES", [ // Which apps to load on a given page define("APPS", [ - "home" => [ - "taskfloor_tasks", - "qwikclock_inout", - "taskfloor_messages", - "inventory_link", - "account_security" - ], + "home" => [], "security" => [ "change_password", "change_pin", diff --git a/required.php b/required.php index 1801afb..e79d7c7 100644 --- a/required.php +++ b/required.php @@ -62,7 +62,8 @@ if ($_SESSION['mobile'] === TRUE) { // List of alert messages require __DIR__ . '/lang/messages.php'; // text strings (i18n) -require __DIR__ . '/lang/' . LANGUAGE . ".php"; +require __DIR__ . '/lib/Strings.php'; +$Strings = new Strings(LANGUAGE); function sendError($error) { global $SECURE_NONCE; @@ -136,18 +137,8 @@ function is_empty($str) { * @param boolean $echo whether to echo the result or return it (default echo) */ function lang($key, $echo = true) { - if (array_key_exists($key, $GLOBALS['STRINGS'])) { - $str = $GLOBALS['STRINGS'][$key]; - } else { - trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING); - $str = $key; - } - - if ($echo) { - echo $str; - } else { - return $str; - } + global $Strings; + return $Strings->get($key, $echo); } /** @@ -159,22 +150,8 @@ function lang($key, $echo = true) { * @param boolean $echo whether to echo the result or return it (default echo) */ function lang2($key, $replace, $echo = true) { - if (array_key_exists($key, $GLOBALS['STRINGS'])) { - $str = $GLOBALS['STRINGS'][$key]; - } else { - trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING); - $str = $key; - } - - foreach ($replace as $find => $repl) { - $str = str_replace("{" . $find . "}", $repl, $str); - } - - if ($echo) { - echo $str; - } else { - return $str; - } + global $Strings; + return $Strings->build($key, $replace, $echo); } /** @@ -191,9 +168,7 @@ function addLangStrings($strings) { * @param array $strings ['en_us' => ['key' => 'value']] */ function addMultiLangStrings($strings) { - if (!is_empty($strings[LANGUAGE])) { - $GLOBALS['STRINGS'] = array_merge($GLOBALS['STRINGS'], $strings[LANGUAGE]); - } + throw new Exception("Calling broken function addMultiLangStrings()"); } /**