diff --git a/LICENSE.md b/LICENSE.md
index 63a11e3..56351c0 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,19 +1,7 @@
-Copyright (c) 2018 Netsyms Technologies.
+Copyright (c) 2017-2019 Netsyms Technologies. Some rights reserved.
-If you modify and redistribute this project, you must replace the branding
-assets with your own.
-
-The branding assets include:
- * the application icon
- * the Netsyms N punchcard logo
- * the Netsyms for Business graph logo
-
-If you are unsure if your usage is allowed, please contact us:
-https://netsyms.com/contact
-legal@netsyms.com
-
-All other portions of this application,
-unless otherwise noted (in comments, headers, etc), are licensed as follows:
+Licensed under the Mozilla Public License Version 2.0. Files without MPL header
+comments, including third party code, may be under a different license.
Mozilla Public License Version 2.0
==================================
diff --git a/action.php b/action.php
index e282a7c..0c0fd90 100644
--- a/action.php
+++ b/action.php
@@ -36,7 +36,7 @@ if ($VARS['action'] != "signout" && !(new User($_SESSION['uid']))->hasPermission
switch ($VARS['action']) {
case "edititem":
$insert = true;
- if (is_empty($VARS['itemid'])) {
+ if (empty($VARS['itemid'])) {
$insert = true;
} else {
if ($database->has('items', ['itemid' => $VARS['itemid']])) {
@@ -45,42 +45,42 @@ switch ($VARS['action']) {
returnToSender("invalid_itemid");
}
}
- if (is_empty($VARS['name'])) {
+ if (empty($VARS['name'])) {
returnToSender('missing_name');
}
- if (!is_empty($VARS['catstr']) && is_empty($VARS['cat'])) {
+ if (!empty($VARS['catstr']) && empty($VARS['cat'])) {
if ($database->count("categories", ["catname" => $VARS['catstr']]) == 1) {
$VARS['cat'] = $database->get("categories", 'catid', ["catname" => $VARS['catstr']]);
} else {
returnToSender('use_the_drop_luke');
}
}
- if (!is_empty($VARS['locstr']) && is_empty($VARS['loc'])) {
+ if (!empty($VARS['locstr']) && empty($VARS['loc'])) {
if ($database->count("locations", ["locname" => $VARS['locstr']]) == 1) {
$VARS['loc'] = $database->get("locations", 'locid', ["locname" => $VARS['locstr']]);
} else {
returnToSender('use_the_drop_luke');
}
}
- if (is_empty($VARS['cat']) || is_empty($VARS['loc'])) {
+ if (empty($VARS['cat']) || empty($VARS['loc'])) {
returnToSender('invalid_parameters');
}
- if (is_empty($VARS['qty'])) {
+ if (empty($VARS['qty'])) {
$VARS['qty'] = 1;
} else if (!is_numeric($VARS['qty'])) {
returnToSender('field_nan');
}
- if (is_empty($VARS['want'])) {
+ if (empty($VARS['want'])) {
$VARS['want'] = 0;
} else if (!is_numeric($VARS['want'])) {
returnToSender('field_nan');
}
- if (is_empty($VARS['cost'])) {
+ if (empty($VARS['cost'])) {
$VARS['cost'] = null;
} else if (!is_numeric($VARS['cost'])) {
returnToSender('field_nan');
}
- if (is_empty($VARS['price'])) {
+ if (empty($VARS['price'])) {
$VARS['price'] = null;
} else if (!is_numeric($VARS['price'])) {
returnToSender('field_nan');
@@ -128,7 +128,7 @@ switch ($VARS['action']) {
returnToSender("item_saved");
case "editcat":
$insert = true;
- if (is_empty($VARS['catid'])) {
+ if (empty($VARS['catid'])) {
$insert = true;
} else {
if ($database->has('categories', ['catid' => $VARS['catid']])) {
@@ -137,7 +137,7 @@ switch ($VARS['action']) {
returnToSender("invalid_catid");
}
}
- if (is_empty($VARS['name'])) {
+ if (empty($VARS['name'])) {
returnToSender('invalid_parameters');
}
@@ -154,7 +154,7 @@ switch ($VARS['action']) {
returnToSender("category_saved");
case "editloc":
$insert = true;
- if (is_empty($VARS['locid'])) {
+ if (empty($VARS['locid'])) {
$insert = true;
} else {
if ($database->has('locations', ['locid' => $VARS['locid']])) {
@@ -163,7 +163,7 @@ switch ($VARS['action']) {
returnToSender("invalid_locid");
}
}
- if (is_empty($VARS['name'])) {
+ if (empty($VARS['name'])) {
returnToSender('invalid_parameters');
}
@@ -217,9 +217,9 @@ switch ($VARS['action']) {
$client = new GuzzleHttp\Client();
$response = $client
- ->request('POST', PORTAL_API, [
+ ->request('POST', $SETTINGS['accounthub']['api'], [
'form_params' => [
- 'key' => PORTAL_KEY,
+ 'key' => $SETTINGS['accounthub']['key'],
'action' => "usersearch",
'search' => $VARS['q']
]
@@ -237,7 +237,7 @@ switch ($VARS['action']) {
}
break;
case "imageupload":
- $destpath = FILE_UPLOAD_PATH;
+ $destpath = $SETTINGS['file_upload_path'];
if (!is_writable($destpath)) {
returnToSender("unwritable_folder", "&id=$VARS[itemid]");
}
@@ -274,7 +274,7 @@ switch ($VARS['action']) {
default:
$err = "could not be uploaded.";
}
- $errors[] = htmlspecialchars($f['name']) . " $err";
+ $errors[] = htmlentities($f['name']) . " $err";
continue;
}
@@ -296,7 +296,7 @@ switch ($VARS['action']) {
}
if (!$imagevalid) {
- $errors[] = htmlspecialchars($f['name']) . " is not a supported image type (JPEG, GIF, PNG, WEBP).";
+ $errors[] = htmlentities($f['name']) . " is not a supported image type (JPEG, GIF, PNG, WEBP).";
continue;
}
@@ -319,7 +319,7 @@ switch ($VARS['action']) {
}
$database->insert('images', ['itemid' => $VARS['itemid'], 'imagename' => $filename, 'primary' => $primary]);
} else {
- $errors[] = htmlspecialchars($f['name']) . " could not be uploaded.";
+ $errors[] = htmlentities($f['name']) . " could not be uploaded.";
}
}
@@ -350,7 +350,7 @@ switch ($VARS['action']) {
$imagename = $database->get('images', 'imagename', ['imageid' => $VARS['imageid']]);
if ($database->count('images', ['imagename' => $imagename]) <= 1) {
- unlink(FILE_UPLOAD_PATH . "/" . $imagename);
+ unlink($SETTINGS['file_upload_path'] . "/" . $imagename);
}
$database->delete('images', ['AND' => ['itemid' => $VARS['itemid'], 'imageid' => $VARS['imageid']]]);
@@ -361,6 +361,6 @@ switch ($VARS['action']) {
returnToSender("image_deleted", "&id=$VARS[itemid]");
case "signout":
session_destroy();
- header('Location: index.php');
+ header('Location: index.php?logout=1');
die("Logged out.");
}
diff --git a/api.php b/api.php
index 931929f..870c44f 100644
--- a/api.php
+++ b/api.php
@@ -4,35 +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/. */
-/**
- * Simple JSON API to allow other apps to access data from this app.
- *
- * Requests can be sent via either GET or POST requests. POST is recommended
- * as it has a lower chance of being logged on the server, exposing unencrypted
- * user passwords.
- */
-require __DIR__ . '/required.php';
-header("Content-Type: application/json");
-$username = $VARS['username'];
-$password = $VARS['password'];
-$user = User::byUsername($username);
-if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) {
- header("HTTP/1.1 403 Unauthorized");
- die("\"403 Unauthorized\"");
-}
-
-// query max results
-$max = 20;
-if (preg_match("/^[0-9]+$/", $VARS['max']) === 1 && $VARS['max'] <= 1000) {
- $max = (int) $VARS['max'];
-}
-
-switch ($VARS['action']) {
- case "ping":
- $out = ["status" => "OK", "maxresults" => $max, "pong" => true];
- exit(json_encode($out));
- default:
- header("HTTP/1.1 400 Bad Request");
- die("\"400 Bad Request\"");
-}
+// Load in new API from legacy location (a.k.a. here)
+require __DIR__ . "/api/index.php";
diff --git a/api/.htaccess b/api/.htaccess
new file mode 100644
index 0000000..9a4efe4
--- /dev/null
+++ b/api/.htaccess
@@ -0,0 +1,5 @@
+# Rewrite for Nextcloud Notes API
+
+ RewriteEngine on
+ RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT]
+
\ No newline at end of file
diff --git a/api/actions/ping.php b/api/actions/ping.php
new file mode 100644
index 0000000..c764967
--- /dev/null
+++ b/api/actions/ping.php
@@ -0,0 +1,9 @@
+ [
+ "load" => "ping.php",
+ "vars" => [
+ ]
+ ]
+];
diff --git a/api/functions.php b/api/functions.php
new file mode 100644
index 0000000..b0e6d09
--- /dev/null
+++ b/api/functions.php
@@ -0,0 +1,144 @@
+ 5) {
+ for ($i = 2; $i < strlen($key) - 2; $i++) {
+ $resp[$i] = "*";
+ }
+ }
+ return $resp;
+}
+
+/**
+ * Check if the request is allowed
+ * @global array $VARS
+ * @return bool true if the request should continue, false if the request is bad
+ */
+function authenticate(): bool {
+ global $VARS;
+ // HTTP basic auth
+ if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
+ $user = User::byUsername($_SERVER['PHP_AUTH_USER']);
+ if (!$user->checkPassword($_SERVER['PHP_AUTH_PW'])) {
+ return false;
+ }
+ return true;
+ }
+ // Form auth
+ if (empty($VARS['username']) || empty($VARS['password'])) {
+ return false;
+ } else {
+ $username = $VARS['username'];
+ $password = $VARS['password'];
+ $user = User::byUsername($username);
+ if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Get the User whose credentials were used to make the request.
+ */
+function getRequestUser(): User {
+ global $VARS;
+ if (!empty($_SERVER['PHP_AUTH_USER'])) {
+ return User::byUsername($_SERVER['PHP_AUTH_USER']);
+ } else {
+ return User::byUsername($VARS['username']);
+ }
+}
+
+function checkVars($vars, $or = false) {
+ global $VARS;
+ $ok = [];
+ foreach ($vars as $key => $val) {
+ if (strpos($key, "OR") === 0) {
+ checkVars($vars[$key], true);
+ continue;
+ }
+
+ // Only check type of optional variables if they're set, and don't
+ // mark them as bad if they're not set
+ if (strpos($key, " (optional)") !== false) {
+ $key = str_replace(" (optional)", "", $key);
+ if (empty($VARS[$key])) {
+ continue;
+ }
+ } else {
+ if (empty($VARS[$key])) {
+ $ok[$key] = false;
+ continue;
+ }
+ }
+
+ if (strpos($val, "/") === 0) {
+ // regex
+ $ok[$key] = preg_match($val, $VARS[$key]) === 1;
+ } else {
+ $checkmethod = "is_$val";
+ $ok[$key] = !($checkmethod($VARS[$key]) !== true);
+ }
+ }
+ if ($or) {
+ $success = false;
+ $bad = "";
+ foreach ($ok as $k => $v) {
+ if ($v) {
+ $success = true;
+ break;
+ } else {
+ $bad = $k;
+ }
+ }
+ if (!$success) {
+ http_response_code(400);
+ die("400 Bad request: variable $bad is missing or invalid");
+ }
+ } else {
+ foreach ($ok as $key => $bool) {
+ if (!$bool) {
+ http_response_code(400);
+ die("400 Bad request: variable $key is missing or invalid");
+ }
+ }
+ }
+}
diff --git a/api/index.php b/api/index.php
new file mode 100644
index 0000000..8875860
--- /dev/null
+++ b/api/index.php
@@ -0,0 +1,79 @@
+= 1) {
+ $VARS["action"] = $route[0];
+ }
+ if (count($route) >= 2 && strpos($route[1], "?") !== 0) {
+ for ($i = 1; $i < count($route); $i++) {
+ if (empty($route[$i]) || strpos($route[$i], "=") === false) {
+ continue;
+ }
+ $key = explode("=", $route[$i], 2)[0];
+ $val = explode("=", $route[$i], 2)[1];
+ $VARS[$key] = $val;
+ }
+ }
+
+ if (strpos($route[count($route) - 1], "?") === 0) {
+ $morevars = explode("&", substr($route[count($route) - 1], 1));
+ foreach ($morevars as $var) {
+ $key = explode("=", $var, 2)[0];
+ $val = explode("=", $var, 2)[1];
+ $VARS[$key] = $val;
+ }
+ }
+}
+
+if (!authenticate()) {
+ header('WWW-Authenticate: Basic realm="' . $SETTINGS['site_title'] . '"');
+ header('HTTP/1.1 401 Unauthorized');
+ die("401 Unauthorized: you need to supply valid credentials.");
+}
+
+if (empty($VARS['action'])) {
+ http_response_code(404);
+ die("404 No action specified");
+}
+
+if (!isset($APIS[$VARS['action']])) {
+ http_response_code(404);
+ die("404 Action not defined");
+}
+
+$APIACTION = $APIS[$VARS["action"]];
+
+if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) {
+ http_response_code(404);
+ die("404 Action not found");
+}
+
+if (!empty($APIACTION["vars"])) {
+ checkVars($APIACTION["vars"]);
+}
+
+require_once __DIR__ . "/actions/" . $APIACTION["load"];
diff --git a/app.php b/app.php
index 2325ffa..a09ff4b 100644
--- a/app.php
+++ b/app.php
@@ -1,5 +1,4 @@
; rel=preload; as=script", fals
-
+
@@ -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;
-
+
@@ -157,7 +163,7 @@ END;
-
+
@@ -177,8 +183,8 @@ END;
?>
diff --git a/image.php b/image.php
index dc2341d..4b1a7bf 100644
--- a/image.php
+++ b/image.php
@@ -8,7 +8,7 @@
require_once __DIR__ . "/required.php";
-$base = FILE_UPLOAD_PATH . "/";
+$base = $SETTINGS['file_upload_path'] . "/";
if (isset($_GET['i'])) {
$file = $_GET['i'];
$filepath = $base . $file;
@@ -16,7 +16,7 @@ if (isset($_GET['i'])) {
http_response_code(404);
die("404 File Not Found");
}
- if (strpos(realpath($filepath), FILE_UPLOAD_PATH) !== 0) {
+ if (strpos(realpath($filepath), $SETTINGS['file_upload_path']) !== 0) {
http_response_code(404);
die("404 File Not Found");
}
diff --git a/index.php b/index.php
index 78c8423..4a2ce49 100644
--- a/index.php
+++ b/index.php
@@ -1,175 +1,131 @@
get("no access permission", false);
+/**
+ * Show a simple HTML page with a line of text and a button. Matches the UI of
+ * the AccountHub login flow.
+ *
+ * @global type $SETTINGS
+ * @global type $SECURE_NONCE
+ * @global type $Strings
+ * @param string $title Text to show, passed through i18n
+ * @param string $button Button text, passed through i18n
+ * @param string $url URL for the button
+ */
+function showHTML(string $title, string $button, string $url) {
+ global $SETTINGS, $SECURE_NONCE, $Strings;
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
get($title); ?>
+
+
+
+
+
+ exists()) {
- $status = $user->getStatus()->getString();
- switch ($status) {
- case "LOCKED_OR_DISABLED":
- $alert = $Strings->get("account locked", false);
- break;
- case "TERMINATED":
- $alert = $Strings->get("account terminated", false);
- break;
- case "CHANGE_PASSWORD":
- $alert = $Strings->get("password expired", false);
- break;
- case "NORMAL":
- $username_ok = true;
- break;
- case "ALERT_ON_ACCESS":
- $mail_resp = $user->sendAlertEmail();
- if (DEBUG) {
- var_dump($mail_resp);
- }
- $username_ok = true;
- break;
- default:
- if (!is_empty($error)) {
- $alert = $error;
- } else {
- $alert = $Strings->get("login error", false);
- }
- break;
- }
- if ($username_ok) {
- if ($user->checkPassword($VARS['password'])) {
- $_SESSION['passok'] = true; // stop logins using only username and authcode
- if ($user->has2fa()) {
- $multiauth = true;
- } else {
- Session::start($user);
- header('Location: app.php');
- die("Logged in, go to app.php");
- }
- } else {
- $alert = $Strings->get("login incorrect", false);
- }
+if (!empty($_GET['logout'])) {
+ showHTML("You have been logged out.", "Log in again", "./index.php");
+ die();
+}
+if (empty($_SESSION["login_code"])) {
+ $redirecttologin = true;
+} else {
+ try {
+ $uidinfo = AccountHubApi::get("checkloginkey", ["code" => $_SESSION["login_code"]]);
+ if ($uidinfo["status"] == "ERROR") {
+ throw new Exception();
+ }
+ if (is_numeric($uidinfo['uid'])) {
+ $user = new User($uidinfo['uid'] * 1);
+ foreach ($SETTINGS['permissions'] as $perm) {
+ if (!$user->hasPermission($perm)) {
+ showHTML("no access permission", "sign out", "./action.php?action=signout");
+ die();
}
- } else { // User does not exist anywhere
- $alert = $Strings->get("login incorrect", false);
}
- } else {
- $alert = $Strings->get("captcha error", false);
- }
- } else if ($VARS['progress'] == "2") {
- $user = User::byUsername($VARS['username']);
- if ($_SESSION['passok'] !== true) {
- // stop logins using only username and authcode
- sendError("Password integrity check failed!");
- }
- if ($user->check2fa($VARS['authcode'])) {
Session::start($user);
+ $_SESSION["login_code"] = null;
header('Location: app.php');
- die("Logged in, go to app.php");
+ showHTML("Logged in", "Continue", "./app.php");
+ die();
} else {
- $alert = $Strings->get("2fa incorrect", false);
+ throw new Exception();
}
+ } catch (Exception $ex) {
+ $redirecttologin = true;
+ }
+}
+
+if ($redirecttologin) {
+ try {
+ $urlbase = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "");
+ $iconurl = $urlbase . str_replace("index.php", "", $_SERVER["REQUEST_URI"]) . "static/img/logo.svg";
+ $codedata = AccountHubApi::get("getloginkey", ["appname" => $SETTINGS["site_title"], "appicon" => $iconurl]);
+
+ if ($codedata['status'] != "OK") {
+ throw new Exception($Strings->get("login server unavailable", false));
+ }
+
+ $redirecturl = $urlbase . $_SERVER['REQUEST_URI'];
+
+ $_SESSION["login_code"] = $codedata["code"];
+
+ $locationurl = $codedata["loginurl"] . "?code=" . htmlentities($codedata["code"]) . "&redirect=" . htmlentities($redirecturl);
+ header("Location: $locationurl");
+ showHTML("Continue", "Continue", $locationurl);
+ die();
+ } catch (Exception $ex) {
+ sendError($ex->getMessage());
}
-} else {
- $alert = $Strings->get("login server unavailable", false);
}
-header("Link: ; rel=preload; as=style", false);
-header("Link: ; rel=preload; as=style", false);
-header("Link: ; rel=preload; as=style", false);
-header("Link: ; rel=preload; as=style", false);
-header("Link: ; rel=preload; as=script", false);
-header("Link: ; rel=preload; as=script", false);
-?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lang/en_us.php b/lang/en_us.php
deleted file mode 100644
index a405785..0000000
--- a/lang/en_us.php
+++ /dev/null
@@ -1,114 +0,0 @@
- "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}!",
- "no permission" => "You do not have permission to access this system.",
- "sign out" => "Sign out",
- "settings" => "Settings",
- "options" => "Options",
- "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 edit permission" => "You do not have permission to modify records.",
- "no access permission" => "You do not have permission to access this system.",
- "home" => "Home",
- "more" => "More",
- "invalid itemid" => "The item ID is invalid.",
- "invalid category" => "The category is invalid.",
- "invalid location" => "The location does not exist.",
- "item saved" => "Item saved.",
- "item deleted" => "Item deleted.",
- "total items" => "Total Items",
- "items" => "Items",
- "locations" => "Locations",
- "categories" => "Categories",
- "actions" => "Actions",
- "name" => "Name",
- "category" => "Category",
- "location" => "Location",
- "code" => "Code",
- "code 1" => "Code 1",
- "code 2" => "Code 2",
- "qty" => "Qty",
- "quantity" => "Quantity",
- "assigned to" => "Assigned To",
- "view items" => "View Items",
- "nobody" => "Nobody",
- "view categories" => "View Categories",
- "view locations" => "View Locations",
- "edit" => "Edit",
- "clone" => "Copy",
- "item count" => "Items",
- "delete" => "Delete",
- "new item" => "New Item",
- "editing item" => "Editing {item}",
- "editing category" => "Editing {cat}",
- "editing location" => "Editing {loc}",
- "cloning item" => "Copying {oitem} {nitem}",
- "adding item" => "Adding new item",
- "adding category" => "Adding new category",
- "invalid catid" => "Invalid category ID.",
- "category deleted" => "Category deleted.",
- "category in use" => "Cannot delete category because there is at least one item still in it.",
- "new category" => "New Category",
- "category saved" => "Category saved.",
- "adding location" => "Adding new location",
- "invalid locid" => "Invalid location ID.",
- "location deleted" => "Location deleted.",
- "location in use" => "Cannot delete location because there is at least one item still in it.",
- "new location" => "New Location",
- "location saved" => "Location saved.",
- "name" => "Name",
- "save" => "Save",
- "placeholder item name" => "Foo Bar",
- "placeholder category name" => "Widgets",
- "placeholder location name" => "Over the Hills",
- "description" => "Description",
- "notes" => "Notes",
- "comments" => "Comments",
- "minwant" => "Minimum On Hand",
- "want" => "Need",
- "field not a number" => "You entered something that isn't a number when a number was expected.",
- "understocked items" => "Understocked Items",
- "view understocked" => "View Understocked",
- "only showing understocked" => "Only showing understocked items.",
- "show all items" => "Show all items",
- "missing name" => "You need to enter a name.",
- "use the dropdowns" => "Whoops, you need to use the category and location autocomplete boxes.",
- "make categories and locations" => "Please create at least one category and location before adding an item.",
- "search" => "Search Items",
- "report export" => "Reports/Export",
- "report type" => "Report type",
- "format" => "Format",
- "generate report" => "Generate report",
- "choose an option" => "Choose an option",
- "csv file" => "CSV text file",
- "ods file" => "ODS spreadsheet",
- "html file" => "HTML web page",
- "itemid" => "Item ID",
- "id" => "ID",
- "item cost" => "Item cost",
- "sale price" => "Sale price",
- "cost" => "Cost",
- "price" => "Price"
-]);
diff --git a/langs/en/core.json b/langs/en/core.json
index 639d829..4fd38f5 100644
--- a/langs/en/core.json
+++ b/langs/en/core.json
@@ -1,21 +1,5 @@
{
- "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",
- "settings": "Settings",
- "options": "Options",
"404 error": "404 Error",
"page not found": "Page not found.",
"invalid parameters": "Invalid request parameters.",
diff --git a/langs/en/images.json b/langs/en/images.json
index bb9a405..896e6a0 100644
--- a/langs/en/images.json
+++ b/langs/en/images.json
@@ -6,5 +6,7 @@
"Promoted": "Promoted",
"Promote": "Promote",
"Delete": "Delete",
- "Back": "Back"
+ "Back": "Back",
+ "Image uploaded.": "Image uploaded.",
+ "Upload finished with errors: {arg}": "Upload finished with errors: {arg}"
}
diff --git a/langs/en/index.json b/langs/en/index.json
new file mode 100644
index 0000000..c516bbb
--- /dev/null
+++ b/langs/en/index.json
@@ -0,0 +1,8 @@
+{
+ "You have been logged out.": "You have been logged out.",
+ "Log in again": "Log in again",
+ "login server unavailable": "Login server unavailable. Try again later or contact technical support.",
+ "no access permission": "You do not have permission to access this system.",
+ "Logged in": "Logged in",
+ "Continue": "Continue"
+}
diff --git a/langs/messages.php b/langs/messages.php
index b1602fa..3155be1 100644
--- a/langs/messages.php
+++ b/langs/messages.php
@@ -88,5 +88,13 @@ define("MESSAGES", [
"noloccat" => [
"string" => "make categories and locations",
"type" => "info"
+ ],
+ "upload_warning" => [
+ "string" => "Upload finished with errors: {arg}",
+ "type" => "warning"
+ ],
+ "upload_success" => [
+ "string" => "Image uploaded.",
+ "type" => "success"
]
]);
diff --git a/lib/AccountHubApi.lib.php b/lib/AccountHubApi.lib.php
new file mode 100644
index 0000000..4d23f9e
--- /dev/null
+++ b/lib/AccountHubApi.lib.php
@@ -0,0 +1,56 @@
+ $action,
+ "key" => $SETTINGS['accounthub']['key']
+ ];
+ if (!is_null($data)) {
+ $content = array_merge($content, $data);
+ }
+ $options = [
+ 'http' => [
+ 'method' => 'POST',
+ 'content' => json_encode($content),
+ 'header' => "Content-Type: application/json\r\n" .
+ "Accept: application/json\r\n",
+ "ignore_errors" => true
+ ]
+ ];
+
+ $context = stream_context_create($options);
+ $result = file_get_contents($SETTINGS['accounthub']['api'], false, $context);
+ $response = json_decode($result, true);
+ if ($result === false || !AccountHubApi::checkHttpRespCode($http_response_header) || json_last_error() != JSON_ERROR_NONE) {
+ if ($throwex) {
+ throw new Exception($result);
+ } else {
+ sendError($result);
+ }
+ }
+ return $response;
+ }
+
+ private static function checkHttpRespCode(array $headers): bool {
+ foreach ($headers as $header) {
+ if (preg_match("/HTTP\/[0-9]\.[0-9] [0-9]{3}.*/", $header)) {
+ $respcode = explode(" ", $header)[1] * 1;
+ if ($respcode >= 200 && $respcode < 300) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/lib/FormBuilder.lib.php b/lib/FormBuilder.lib.php
new file mode 100644
index 0000000..35e8fe3
--- /dev/null
+++ b/lib/FormBuilder.lib.php
@@ -0,0 +1,275 @@
+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 = <<
+
+
+
+
+
+HTMLTOP;
+
+ foreach ($this->items as $item) {
+ $required = $item["required"] ? "required" : "";
+ $id = empty($item["id"]) ? "" : "id=\"$item[id]\"";
+ $pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\"";
+ if (empty($item['type'])) {
+ $item['type'] = "text";
+ }
+ $itemhtml = "";
+ $itemlabel = "";
+ if ($item['type'] != "checkbox") {
+ $itemlabel = "
";
+ }
+ $strippedlabel = strip_tags($item['label']);
+ $itemhtml .= <<
+
+ERROR;
+ }
+ $itemhtml .= <<
+
+
\n
+ITEMBOTTOM;
+ $html .= $itemhtml;
+ }
+
+ $html .= <<
+
+HTMLBOTTOM;
+
+ if (!empty($this->buttons)) {
+ $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/Login.lib.php b/lib/Login.lib.php
index fe22a38..219cfea 100644
--- a/lib/Login.lib.php
+++ b/lib/Login.lib.php
@@ -45,50 +45,13 @@ class Login {
return Login::LOGIN_OK;
}
- public static function verifyCaptcha(string $session, string $answer, string $url): bool {
- $data = [
- 'session_id' => $session,
- 'answer_id' => $answer,
- 'action' => "verify"
- ];
- $options = [
- 'http' => [
- 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
- 'method' => 'POST',
- 'content' => http_build_query($data)
- ]
- ];
- $context = stream_context_create($options);
- $result = file_get_contents($url, false, $context);
- $resp = json_decode($result, TRUE);
- if (!$resp['result']) {
- return false;
- } else {
- return true;
- }
- }
-
/**
* Check the login server API for sanity
* @return boolean true if OK, else false
*/
public static function checkLoginServer() {
try {
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "ping"
- ]
- ]);
-
- if ($response->getStatusCode() != 200) {
- return false;
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("ping");
if ($resp['status'] == "OK") {
return true;
} else {
@@ -107,19 +70,7 @@ class Login {
*/
function checkAPIKey($key) {
try {
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => $key,
- 'action' => "ping"
- ]
- ]);
-
- if ($response->getStatusCode() === 200) {
- return true;
- }
+ $resp = AccountHubApi::get("ping", null, true);
return false;
} catch (Exception $e) {
return false;
diff --git a/lib/Notifications.lib.php b/lib/Notifications.lib.php
index c1d93a9..812af26 100644
--- a/lib/Notifications.lib.php
+++ b/lib/Notifications.lib.php
@@ -32,27 +32,15 @@ class Notifications {
$timestamp = date("Y-m-d H:i:s", strtotime($timestamp));
}
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "addnotification",
- 'uid' => $user->getUID(),
- 'title' => $title,
- 'content' => $content,
- 'timestamp' => $timestamp,
- 'url' => $url,
- 'sensitive' => $sensitive
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("addnotification", [
+ 'uid' => $user->getUID(),
+ 'title' => $title,
+ 'content' => $content,
+ 'timestamp' => $timestamp,
+ 'url' => $url,
+ 'sensitive' => $sensitive
+ ]
+ );
if ($resp['status'] == "OK") {
return $resp['id'] * 1;
} else {
diff --git a/lib/Strings.lib.php b/lib/Strings.lib.php
index b094bbf..b03058f 100644
--- a/lib/Strings.lib.php
+++ b/lib/Strings.lib.php
@@ -21,6 +21,10 @@ class Strings {
$this->load("en");
+ if ($language == "en") {
+ return;
+ }
+
if (file_exists(__DIR__ . "/../langs/$language/")) {
$this->language = $language;
$this->load($language);
diff --git a/lib/User.lib.php b/lib/User.lib.php
index 7852e31..763acc5 100644
--- a/lib/User.lib.php
+++ b/lib/User.lib.php
@@ -17,22 +17,7 @@ class User {
public function __construct(int $uid, string $username = "") {
// Check if user exists
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "userexists",
- 'uid' => $uid
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("userexists", ["uid" => $uid]);
if ($resp['status'] == "OK" && $resp['exists'] === true) {
$this->exists = true;
} else {
@@ -43,22 +28,7 @@ class User {
if ($this->exists) {
// Get user info
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "userinfo",
- 'uid' => $uid
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("userinfo", ["uid" => $uid]);
if ($resp['status'] == "OK") {
$this->uid = $resp['data']['uid'] * 1;
$this->username = $resp['data']['username'];
@@ -71,22 +41,7 @@ class User {
}
public static function byUsername(string $username): User {
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'username' => $username,
- 'action' => "userinfo"
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("userinfo", ["username" => $username]);
if (!isset($resp['status'])) {
sendError("Login server error: " . $resp);
}
@@ -105,22 +60,8 @@ class User {
if (!$this->exists) {
return false;
}
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "hastotp",
- 'username' => $this->username
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("hastotp", ['username' => $this->username]);
if ($resp['status'] == "OK") {
return $resp['otp'] == true;
} else {
@@ -150,23 +91,7 @@ class User {
* @return bool
*/
function checkPassword(string $password): bool {
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "auth",
- 'username' => $this->username,
- 'password' => $password
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("auth", ['username' => $this->username, 'password' => $password]);
if ($resp['status'] == "OK") {
return true;
} else {
@@ -178,23 +103,8 @@ class User {
if (!$this->has2fa) {
return true;
}
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "verifytotp",
- 'username' => $this->username,
- 'code' => $code
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("verifytotp", ['username' => $this->username, 'code' => $code]);
if ($resp['status'] == "OK") {
return $resp['valid'];
} else {
@@ -209,23 +119,7 @@ class User {
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
*/
function hasPermission(string $code): bool {
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "permission",
- 'username' => $this->username,
- 'code' => $code
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("permission", ['username' => $this->username, 'code' => $code]);
if ($resp['status'] == "OK") {
return $resp['has_permission'];
} else {
@@ -238,23 +132,7 @@ class User {
* @return \AccountStatus
*/
function getStatus(): AccountStatus {
-
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "acctstatus",
- 'username' => $this->username
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- sendError("Login server error: " . $response->getBody());
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("acctstatus", ['username' => $this->username]);
if ($resp['status'] == "OK") {
return AccountStatus::fromString($resp['account']);
} else {
@@ -262,24 +140,13 @@ class User {
}
}
- function sendAlertEmail(string $appname = SITE_TITLE) {
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "alertemail",
- 'username' => $this->username,
- 'appname' => SITE_TITLE
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- return "An unknown error occurred.";
+ function sendAlertEmail(string $appname = null) {
+ global $SETTINGS;
+ if (is_null($appname)) {
+ $appname = $SETTINGS['site_title'];
}
+ $resp = AccountHubApi::get("alertemail", ['username' => $this->username, 'appname' => $SETTINGS['site_title']]);
- $resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
diff --git a/lib/getitemtable.php b/lib/getitemtable.php
index 88b157c..afe32b5 100644
--- a/lib/getitemtable.php
+++ b/lib/getitemtable.php
@@ -57,7 +57,7 @@ switch ($VARS['order'][0]['column']) {
}
// search
-if (!is_empty($VARS['search']['value'])) {
+if (!empty($VARS['search']['value'])) {
$filter = true;
$wherenolimit = [];
if ($showwant) {
diff --git a/mobile/index.php b/mobile/index.php
index 506608e..beaad4a 100644
--- a/mobile/index.php
+++ b/mobile/index.php
@@ -8,10 +8,6 @@
* Mobile app API
*/
-// The name of the permission needed to log in.
-// Set to null if you don't need it.
-$access_permission = "INV_VIEW";
-
require __DIR__ . "/../required.php";
header('Content-Type: application/json');
@@ -23,21 +19,7 @@ if ($VARS['action'] == "ping") {
}
function mobile_enabled() {
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- 'action' => "mobileenabled"
- ]
- ]);
-
- if ($response->getStatusCode() > 299) {
- return false;
- }
-
- $resp = json_decode($response->getBody(), TRUE);
+ $resp = AccountHubApi::get("mobileenabled");
if ($resp['status'] == "OK" && $resp['mobile'] === TRUE) {
return true;
} else {
@@ -46,26 +28,15 @@ function mobile_enabled() {
}
function mobile_valid($username, $code) {
- $client = new GuzzleHttp\Client();
-
- $response = $client
- ->request('POST', PORTAL_API, [
- 'form_params' => [
- 'key' => PORTAL_KEY,
- "code" => $code,
- "username" => $username,
- 'action' => "mobilevalid"
- ]
- ]);
+ try {
+ $resp = AccountHubApi::get("mobilevalid", ["code" => $code, "username" => $username], true);
- if ($response->getStatusCode() > 299) {
- return false;
- }
-
- $resp = json_decode($response->getBody(), TRUE);
- if ($resp['status'] == "OK" && $resp['valid'] === TRUE) {
- return true;
- } else {
+ if ($resp['status'] == "OK" && $resp['valid'] === TRUE) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (Exception $ex) {
return false;
}
}
@@ -75,7 +46,7 @@ if (mobile_enabled() !== TRUE) {
}
// Make sure we have a username and access key
-if (is_empty($VARS['username']) || is_empty($VARS['key'])) {
+if (empty($VARS['username']) || empty($VARS['key'])) {
http_response_code(401);
die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
}
@@ -95,13 +66,14 @@ switch ($VARS['action']) {
if ($user->exists()) {
if ($user->getStatus()->getString() == "NORMAL") {
if ($user->checkPassword($VARS['password'])) {
- if (is_null($access_permission) || $user->hasPermission($access_permission)) {
- Session::start($user);
- $_SESSION['mobile'] = true;
- exit(json_encode(["status" => "OK"]));
- } else {
- exit(json_encode(["status" => "ERROR", "msg" => lang("no permission", false)]));
+ foreach ($SETTINGS['permissions'] as $perm) {
+ if (!$user->hasPermission($perm)) {
+ exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("no permission", false)]));
+ }
}
+ Session::start($user);
+ $_SESSION['mobile'] = true;
+ exit(json_encode(["status" => "OK"]));
}
}
}
diff --git a/pages.php b/pages.php
index e324a56..57e889b 100644
--- a/pages.php
+++ b/pages.php
@@ -99,5 +99,13 @@ define("PAGES", [
],
"404" => [
"title" => "404 error"
+ ],
+ "form" => [
+ "title" => "Form",
+ "navbar" => true,
+ "icon" => "fas fa-file-alt",
+ "scripts" => [
+ "static/js/form.js"
+ ]
]
]);
diff --git a/pages/editcat.php b/pages/editcat.php
index ed95dcf..8924007 100644
--- a/pages/editcat.php
+++ b/pages/editcat.php
@@ -28,43 +28,24 @@ if (!empty($VARS['id'])) {
header('Location: app.php?page=editcat');
}
}
-?>
-
\ No newline at end of file
+$form->generate();
\ No newline at end of file
diff --git a/pages/editimages.php b/pages/editimages.php
index 0170470..9bb2364 100644
--- a/pages/editimages.php
+++ b/pages/editimages.php
@@ -84,7 +84,7 @@ if (!empty($VARS['id']) && $database->has('items', ['itemid' => $VARS['id']])) {