forked from Business/BinStack
Merge BusinessAppTemplate
# Conflicts: # composer.lock # lang/en_us.php # lib/login.php # mobile/index.php
This commit is contained in:
commit
f2d3c199ab
15
action.php
15
action.php
@ -9,9 +9,6 @@
|
||||
*/
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
require_once __DIR__ . "/lib/login.php";
|
||||
require_once __DIR__ . "/lib/userinfo.php";
|
||||
|
||||
if ($VARS['action'] !== "signout") {
|
||||
dieifnotloggedin();
|
||||
}
|
||||
@ -32,7 +29,7 @@ function returnToSender($msg, $arg = "") {
|
||||
die();
|
||||
}
|
||||
|
||||
if ($VARS['action'] != "signout" && !account_has_permission($_SESSION['username'], "INV_EDIT")) {
|
||||
if ($VARS['action'] != "signout" && !(new User($_SESSION['uid']))->hasPermission("INV_EDIT")) {
|
||||
returnToSender("no_edit_permission");
|
||||
}
|
||||
|
||||
@ -95,10 +92,12 @@ switch ($VARS['action']) {
|
||||
returnToSender('invalid_location');
|
||||
}
|
||||
|
||||
if (!is_empty($VARS['assignedto']) && user_exists($VARS['assignedto'])) {
|
||||
$userid = getUserByUsername($VARS['assignedto'])['uid'];
|
||||
} else {
|
||||
$userid = null;
|
||||
$userid = null;
|
||||
if (!empty($VARS['assignedto'])) {
|
||||
$assigneduser = User::byUsername($VARS['assignedto']);
|
||||
if ($assigneduser->exists()) {
|
||||
$userid = $assigneduser->getUID();
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
|
6
api.php
6
api.php
@ -12,17 +12,15 @@
|
||||
* user passwords.
|
||||
*/
|
||||
require __DIR__ . '/required.php';
|
||||
require_once __DIR__ . '/lib/login.php';
|
||||
require_once __DIR__ . '/lib/userinfo.php';
|
||||
header("Content-Type: application/json");
|
||||
|
||||
$username = $VARS['username'];
|
||||
$password = $VARS['password'];
|
||||
if (user_exists($username) !== true || authenticate_user($username, $password, $errmsg) !== true) {
|
||||
$user = User::byUsername($username);
|
||||
if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) {
|
||||
header("HTTP/1.1 403 Unauthorized");
|
||||
die("\"403 Unauthorized\"");
|
||||
}
|
||||
$userinfo = getUserByUsername($username);
|
||||
|
||||
// query max results
|
||||
$max = 20;
|
||||
|
8
app.php
8
app.php
@ -69,9 +69,9 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
|
||||
if (isset($_GET['msg']) && !is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
|
||||
// optional string generation argument
|
||||
if (!isset($_GET['arg']) || is_empty($_GET['arg'])) {
|
||||
$alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false);
|
||||
$alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
|
||||
} else {
|
||||
$alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
|
||||
$alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
|
||||
}
|
||||
$alerttype = MESSAGES[$_GET['msg']]['type'];
|
||||
$alerticon = "square-o";
|
||||
@ -146,7 +146,7 @@ END;
|
||||
if (isset($pg['icon'])) {
|
||||
?><i class="<?php echo $pg['icon']; ?> fa-fw"></i> <?php
|
||||
}
|
||||
lang($pg['title']);
|
||||
$Strings->get($pg['title']);
|
||||
?>
|
||||
</a>
|
||||
</span>
|
||||
@ -163,7 +163,7 @@ END;
|
||||
</span>
|
||||
<span class="nav-item mr-auto py-<?php echo $navbar_breakpoint; ?>-0">
|
||||
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="action.php?action=signout">
|
||||
<i class="fas fa-sign-out-alt fa-fw"></i><span> <?php lang("sign out") ?></span>
|
||||
<i class="fas fa-sign-out-alt fa-fw"></i><span> <?php $Strings->get("sign out") ?></span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
41
composer.lock
generated
41
composer.lock
generated
@ -4,21 +4,21 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "842a34f918f0d5c33a3031d363796a84",
|
||||
"content-hash": "9790de18454ae4decdf8e9ed095ed537",
|
||||
"hash": "831ae7560f5a64a68c52c69b772a8fc5",
|
||||
"content-hash": "a7d5f4f0d86fc84b983b1a3095ba3288",
|
||||
"packages": [
|
||||
{
|
||||
"name": "catfan/medoo",
|
||||
"version": "v1.5.3",
|
||||
"version": "v1.5.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/catfan/Medoo.git",
|
||||
"reference": "1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07"
|
||||
"reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/catfan/Medoo/zipball/1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07",
|
||||
"reference": "1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07",
|
||||
"url": "https://api.github.com/repos/catfan/Medoo/zipball/8d90cba0e8ff176028847527d0ea76fe41a06ecf",
|
||||
"reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -64,20 +64,20 @@
|
||||
"sql",
|
||||
"sqlite"
|
||||
],
|
||||
"time": "2017-12-25 17:02:41"
|
||||
"time": "2018-06-14 18:59:08"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "6.3.0",
|
||||
"version": "6.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
|
||||
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
|
||||
"reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
|
||||
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -87,7 +87,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "^4.0 || ^5.0",
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
|
||||
"psr/log": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
@ -96,7 +96,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.2-dev"
|
||||
"dev-master": "6.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -129,7 +129,7 @@
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"time": "2017-06-22 18:50:49"
|
||||
"time": "2018-04-22 15:46:56"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
@ -292,16 +292,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
"version": "9.1.1",
|
||||
"version": "9.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/csv.git",
|
||||
"reference": "66118f5c2a7e4da77e743e69f74773c63b73e8f9"
|
||||
"reference": "9c8ad06fb5d747c149875beb6133566c00eaa481"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/csv/zipball/66118f5c2a7e4da77e743e69f74773c63b73e8f9",
|
||||
"reference": "66118f5c2a7e4da77e743e69f74773c63b73e8f9",
|
||||
"url": "https://api.github.com/repos/thephpleague/csv/zipball/9c8ad06fb5d747c149875beb6133566c00eaa481",
|
||||
"reference": "9c8ad06fb5d747c149875beb6133566c00eaa481",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -311,6 +311,9 @@
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"friendsofphp/php-cs-fixer": "^2.0",
|
||||
"phpstan/phpstan": "^0.9.2",
|
||||
"phpstan/phpstan-phpunit": "^0.9.4",
|
||||
"phpstan/phpstan-strict-rules": "^0.9.0",
|
||||
"phpunit/phpunit": "^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
@ -352,7 +355,7 @@
|
||||
"read",
|
||||
"write"
|
||||
],
|
||||
"time": "2017-11-28 08:29:49"
|
||||
"time": "2018-05-01 18:32:48"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
|
101
index.php
101
index.php
@ -5,80 +5,91 @@
|
||||
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
require_once __DIR__ . "/lib/login.php";
|
||||
|
||||
// if we're logged in, we don't need to be here.
|
||||
if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) {
|
||||
header('Location: app.php');
|
||||
}
|
||||
|
||||
if (isset($_GET['permissionerror'])) {
|
||||
$alert = lang("no access permission", false);
|
||||
$alert = $Strings->get("no access permission", false);
|
||||
}
|
||||
|
||||
/* Authenticate user */
|
||||
$userpass_ok = false;
|
||||
$multiauth = false;
|
||||
if (checkLoginServer()) {
|
||||
if (!empty($VARS['progress']) && $VARS['progress'] == "1") {
|
||||
if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && verifyCaptcheck($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) {
|
||||
$errmsg = "";
|
||||
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
|
||||
switch (get_account_status($VARS['username'])) {
|
||||
if (Login::checkLoginServer()) {
|
||||
if (empty($VARS['progress'])) {
|
||||
// Easy way to remove "undefined" warnings.
|
||||
} else if ($VARS['progress'] == "1") {
|
||||
if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && Login::verifyCaptcha($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) {
|
||||
$autherror = "";
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->exists()) {
|
||||
$status = $user->getStatus()->getString();
|
||||
switch ($status) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
$alert = lang("account locked", false);
|
||||
$alert = $Strings->get("account locked", false);
|
||||
break;
|
||||
case "TERMINATED":
|
||||
$alert = lang("account terminated", false);
|
||||
$alert = $Strings->get("account terminated", false);
|
||||
break;
|
||||
case "CHANGE_PASSWORD":
|
||||
$alert = lang("password expired", false);
|
||||
$alert = $Strings->get("password expired", false);
|
||||
break;
|
||||
case "NORMAL":
|
||||
$userpass_ok = true;
|
||||
$username_ok = true;
|
||||
break;
|
||||
case "ALERT_ON_ACCESS":
|
||||
sendLoginAlertEmail($VARS['username']);
|
||||
$userpass_ok = true;
|
||||
$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 ($userpass_ok) {
|
||||
$_SESSION['passok'] = true; // stop logins using only username and authcode
|
||||
if (userHasTOTP($VARS['username'])) {
|
||||
$multiauth = true;
|
||||
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 {
|
||||
doLoginUser($VARS['username'], $VARS['password']);
|
||||
header('Location: app.php');
|
||||
die("Logged in, go to app.php");
|
||||
$alert = $Strings->get("login incorrect", false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!is_empty($errmsg)) {
|
||||
$alert = lang2("login server error", ['arg' => $errmsg], false);
|
||||
} else {
|
||||
$alert = lang("login incorrect", false);
|
||||
}
|
||||
} else { // User does not exist anywhere
|
||||
$alert = $Strings->get("login incorrect", false);
|
||||
}
|
||||
} else {
|
||||
$alert = lang("captcha error", false);
|
||||
$alert = $Strings->get("captcha error", false);
|
||||
}
|
||||
} else if (!empty($VARS['progress']) && $VARS['progress'] == "2") {
|
||||
} 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 (verifyTOTP($VARS['username'], $VARS['authcode'])) {
|
||||
if (doLoginUser($VARS['username'])) {
|
||||
header('Location: app.php');
|
||||
die("Logged in, go to app.php");
|
||||
} else {
|
||||
$alert = lang("login server user data error", false);
|
||||
}
|
||||
if ($user->check2fa($VARS['authcode'])) {
|
||||
Session::start($user);
|
||||
header('Location: app.php');
|
||||
die("Logged in, go to app.php");
|
||||
} else {
|
||||
$alert = lang("2fa incorrect", false);
|
||||
$alert = $Strings->get("2fa incorrect", false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$alert = lang("login server unavailable", false);
|
||||
$alert = $Strings->get("login server unavailable", false);
|
||||
}
|
||||
header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
|
||||
@ -114,7 +125,7 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
|
||||
<div class="row justify-content-center">
|
||||
<div class="card col-11 col-xs-11 col-sm-8 col-md-6 col-lg-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php lang("sign in"); ?></h5>
|
||||
<h5 class="card-title"><?php $Strings->get("sign in"); ?></h5>
|
||||
<form action="" method="POST">
|
||||
<?php
|
||||
if (!empty($alert)) {
|
||||
@ -127,8 +138,8 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
|
||||
|
||||
if ($multiauth != true) {
|
||||
?>
|
||||
<input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
|
||||
<input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
|
||||
<input type="text" class="form-control" name="username" placeholder="<?php $Strings->get("username"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
|
||||
<input type="password" class="form-control" name="password" placeholder="<?php $Strings->get("password"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
|
||||
<?php if (CAPTCHA_ENABLED) { ?>
|
||||
<div class="captcheck_container" data-stylenonce="<?php echo $SECURE_NONCE; ?>"></div>
|
||||
<br />
|
||||
@ -138,16 +149,16 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
|
||||
} else if ($multiauth) {
|
||||
?>
|
||||
<div class="alert alert-info">
|
||||
<?php lang("2fa prompt"); ?>
|
||||
<?php $Strings->get("2fa prompt"); ?>
|
||||
</div>
|
||||
<input type="text" class="form-control" name="authcode" placeholder="<?php lang("authcode"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
|
||||
<input type="text" class="form-control" name="authcode" placeholder="<?php $Strings->get("authcode"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
|
||||
<input type="hidden" name="progress" value="2" />
|
||||
<input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<?php lang("continue"); ?>
|
||||
<?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
26
langs/en/core.json
Normal file
26
langs/en/core.json
Normal file
@ -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",
|
||||
"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 access permission": "You do not have permission to access this system."
|
||||
}
|
7
langs/en/titles.json
Normal file
7
langs/en/titles.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"Home": "Home",
|
||||
"Items": "Items",
|
||||
"Locations": "Locations",
|
||||
"Categories": "Categories",
|
||||
"Reports/Export": "Reports/Export"
|
||||
}
|
13
lib/Exceptions.lib.php
Normal file
13
lib/Exceptions.lib.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
class IncorrectPasswordException extends Exception {
|
||||
public function __construct(string $message = "Incorrect password.", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
135
lib/IPUtils.lib.php
Normal file
135
lib/IPUtils.lib.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
class IPUtils {
|
||||
|
||||
/**
|
||||
* Check if a given ipv4 address is in a given cidr
|
||||
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
|
||||
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
|
||||
* @return boolean true if the ip is in this range / false if not.
|
||||
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
|
||||
*/
|
||||
public static function ip4_in_cidr($ip, $cidr) {
|
||||
if (strpos($cidr, '/') == false) {
|
||||
$cidr .= '/32';
|
||||
}
|
||||
// $range is in IP/CIDR format eg 127.0.0.1/24
|
||||
list( $cidr, $netmask ) = explode('/', $cidr, 2);
|
||||
$range_decimal = ip2long($cidr);
|
||||
$ip_decimal = ip2long($ip);
|
||||
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
|
||||
$netmask_decimal = ~ $wildcard_decimal;
|
||||
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given ipv6 address is in a given cidr
|
||||
* @param string $ip IP to check in IPV6 format
|
||||
* @param string $cidr CIDR netmask
|
||||
* @return boolean true if the IP is in this range, false otherwise.
|
||||
* @author MW. <https://stackoverflow.com/a/7952169>
|
||||
*/
|
||||
public static function ip6_in_cidr($ip, $cidr) {
|
||||
$address = inet_pton($ip);
|
||||
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
|
||||
$subnetMask = explode("/", $cidr)[1];
|
||||
|
||||
$addr = str_repeat("f", $subnetMask / 4);
|
||||
switch ($subnetMask % 4) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
$addr .= "8";
|
||||
break;
|
||||
case 2:
|
||||
$addr .= "c";
|
||||
break;
|
||||
case 3:
|
||||
$addr .= "e";
|
||||
break;
|
||||
}
|
||||
$addr = str_pad($addr, 32, '0');
|
||||
$addr = pack("H*", $addr);
|
||||
|
||||
$binMask = $addr;
|
||||
return ($address & $binMask) == $subnetAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the REMOTE_ADDR is on Cloudflare's network.
|
||||
* @return boolean true if it is, otherwise false
|
||||
*/
|
||||
public static function validateCloudflare() {
|
||||
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
// Using IPv6
|
||||
$cloudflare_ips_v6 = [
|
||||
"2400:cb00::/32",
|
||||
"2405:8100::/32",
|
||||
"2405:b500::/32",
|
||||
"2606:4700::/32",
|
||||
"2803:f800::/32",
|
||||
"2c0f:f248::/32",
|
||||
"2a06:98c0::/29"
|
||||
];
|
||||
$valid = false;
|
||||
foreach ($cloudflare_ips_v6 as $cidr) {
|
||||
if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Using IPv4
|
||||
$cloudflare_ips_v4 = [
|
||||
"103.21.244.0/22",
|
||||
"103.22.200.0/22",
|
||||
"103.31.4.0/22",
|
||||
"104.16.0.0/12",
|
||||
"108.162.192.0/18",
|
||||
"131.0.72.0/22",
|
||||
"141.101.64.0/18",
|
||||
"162.158.0.0/15",
|
||||
"172.64.0.0/13",
|
||||
"173.245.48.0/20",
|
||||
"188.114.96.0/20",
|
||||
"190.93.240.0/20",
|
||||
"197.234.240.0/22",
|
||||
"198.41.128.0/17"
|
||||
];
|
||||
$valid = false;
|
||||
foreach ($cloudflare_ips_v4 as $cidr) {
|
||||
if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a good guess at the client's real IP address.
|
||||
*
|
||||
* @return string Client IP or `0.0.0.0` if we can't find anything
|
||||
*/
|
||||
public static function getClientIP() {
|
||||
// If CloudFlare is in the mix, we should use it.
|
||||
// Check if the request is actually from CloudFlare before trusting it.
|
||||
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
|
||||
if (validateCloudflare()) {
|
||||
return $_SERVER["HTTP_CF_CONNECTING_IP"];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_SERVER["REMOTE_ADDR"])) {
|
||||
return $_SERVER["REMOTE_ADDR"];
|
||||
}
|
||||
|
||||
return "0.0.0.0"; // This will not happen unless we aren't a web server
|
||||
}
|
||||
|
||||
}
|
129
lib/Login.lib.php
Normal file
129
lib/Login.lib.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
class Login {
|
||||
|
||||
const BAD_USERPASS = 1;
|
||||
const BAD_2FA = 2;
|
||||
const ACCOUNT_DISABLED = 3;
|
||||
const LOGIN_OK = 4;
|
||||
|
||||
public static function auth(string $username, string $password, string $twofa = ""): int {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
|
||||
$user = User::byUsername($username);
|
||||
|
||||
if (!$user->exists()) {
|
||||
return Login::BAD_USERPASS;
|
||||
}
|
||||
if (!$user->checkPassword($password)) {
|
||||
return Login::BAD_USERPASS;
|
||||
}
|
||||
|
||||
if ($user->has2fa()) {
|
||||
if (!$user->check2fa($twofa)) {
|
||||
return Login::BAD_2FA;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::TERMINATED:
|
||||
return Login::BAD_USERPASS;
|
||||
case AccountStatus::LOCKED_OR_DISABLED:
|
||||
return Login::ACCOUNT_DISABLED;
|
||||
case AccountStatus::NORMAL:
|
||||
default:
|
||||
return Login::LOGIN_OK;
|
||||
}
|
||||
|
||||
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);
|
||||
if ($resp['status'] == "OK") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given AccountHub API key is valid by attempting to
|
||||
* access the API with it.
|
||||
* @param String $key The API key to check
|
||||
* @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
65
lib/Notifications.lib.php
Normal file
65
lib/Notifications.lib.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
class Notifications {
|
||||
|
||||
/**
|
||||
* Add a new notification.
|
||||
* @global $database
|
||||
* @param User $user
|
||||
* @param string $title
|
||||
* @param string $content
|
||||
* @param string $timestamp If left empty, the current date and time will be used.
|
||||
* @param string $url
|
||||
* @param bool $sensitive If true, the notification is marked as containing sensitive content, and the $content might be hidden on lockscreens and other non-secure places.
|
||||
* @return int The newly-created notification ID.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function add(User $user, string $title, string $content, string $timestamp = "", string $url = "", bool $sensitive = false): int {
|
||||
global $Strings;
|
||||
if ($user->exists()) {
|
||||
if (empty($title) || empty($content)) {
|
||||
throw new Exception($Strings->get("invalid parameters", false));
|
||||
}
|
||||
|
||||
$timestamp = date("Y-m-d H:i:s");
|
||||
if (!empty($timestamp)) {
|
||||
$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);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['id'] * 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
}
|
19
lib/Session.lib.php
Normal file
19
lib/Session.lib.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
class Session {
|
||||
|
||||
public static function start(User $user) {
|
||||
$_SESSION['username'] = $user->getUsername();
|
||||
$_SESSION['uid'] = $user->getUID();
|
||||
$_SESSION['email'] = $user->getEmail();
|
||||
$_SESSION['realname'] = $user->getName();
|
||||
$_SESSION['loggedin'] = true;
|
||||
}
|
||||
|
||||
}
|
118
lib/Strings.lib.php
Normal file
118
lib/Strings.lib.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides translated language strings.
|
||||
*/
|
||||
class Strings {
|
||||
|
||||
private $language = "en";
|
||||
private $strings = [];
|
||||
|
||||
public function __construct($language = "en") {
|
||||
if (!preg_match("/[a-zA-Z\_\-]+/", $language)) {
|
||||
throw new Exception("Invalid language code $language");
|
||||
}
|
||||
|
||||
$this->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", ...]
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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", ...]
|
||||
*/
|
||||
public function getJSON(array $keys): string {
|
||||
$strings = [];
|
||||
foreach ($keys as $k) {
|
||||
$strings[$k] = $this->get($k, false);
|
||||
}
|
||||
return json_encode($strings);
|
||||
}
|
||||
|
||||
}
|
352
lib/User.lib.php
Normal file
352
lib/User.lib.php
Normal file
@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
class User {
|
||||
|
||||
private $uid = null;
|
||||
private $username;
|
||||
private $email;
|
||||
private $realname;
|
||||
private $has2fa = false;
|
||||
private $exists = false;
|
||||
|
||||
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);
|
||||
if ($resp['status'] == "OK" && $resp['exists'] === true) {
|
||||
$this->exists = true;
|
||||
} else {
|
||||
$this->uid = $uid;
|
||||
$this->username = $username;
|
||||
$this->exists = false;
|
||||
}
|
||||
|
||||
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);
|
||||
if ($resp['status'] == "OK") {
|
||||
$this->uid = $resp['data']['uid'] * 1;
|
||||
$this->username = $resp['data']['username'];
|
||||
$this->email = $resp['data']['email'];
|
||||
$this->realname = $resp['data']['name'];
|
||||
} else {
|
||||
sendError("Login server error: " . $resp['msg']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (!isset($resp['status'])) {
|
||||
sendError("Login server error: " . $resp);
|
||||
}
|
||||
if ($resp['status'] == "OK") {
|
||||
return new self($resp['data']['uid'] * 1);
|
||||
} else {
|
||||
return new self(-1, $username);
|
||||
}
|
||||
}
|
||||
|
||||
public function exists(): bool {
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
public function has2fa(): bool {
|
||||
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);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['otp'] == true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getUsername() {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
function getUID() {
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
function getEmail() {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
function getName() {
|
||||
return $this->realname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given plaintext password against the stored hash.
|
||||
* @param string $password
|
||||
* @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);
|
||||
if ($resp['status'] == "OK") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function check2fa(string $code): bool {
|
||||
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);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['valid'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given username has the given permission (or admin access)
|
||||
* @global $database $database
|
||||
* @param string $code
|
||||
* @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);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['has_permission'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status.
|
||||
* @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);
|
||||
if ($resp['status'] == "OK") {
|
||||
return AccountStatus::fromString($resp['account']);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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.";
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return true;
|
||||
} else {
|
||||
return $resp['msg'];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccountStatus {
|
||||
|
||||
const NORMAL = 1;
|
||||
const LOCKED_OR_DISABLED = 2;
|
||||
const CHANGE_PASSWORD = 3;
|
||||
const TERMINATED = 4;
|
||||
const ALERT_ON_ACCESS = 5;
|
||||
|
||||
private $status;
|
||||
|
||||
public function __construct(int $status) {
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public static function fromString(string $status): AccountStatus {
|
||||
switch ($status) {
|
||||
case "NORMAL":
|
||||
return new self(self::NORMAL);
|
||||
case "LOCKED_OR_DISABLED":
|
||||
return new self(self::LOCKED_OR_DISABLED);
|
||||
case "CHANGE_PASSWORD":
|
||||
return new self(self::CHANGE_PASSWORD);
|
||||
case "TERMINATED":
|
||||
return new self(self::TERMINATED);
|
||||
case "ALERT_ON_ACCESS":
|
||||
return new self(self::ALERT_ON_ACCESS);
|
||||
default:
|
||||
return new self(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status/state as an integer.
|
||||
* @return int
|
||||
*/
|
||||
public function get(): int {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status/state as a string representation.
|
||||
* @return string
|
||||
*/
|
||||
public function getString(): string {
|
||||
switch ($this->status) {
|
||||
case self::NORMAL:
|
||||
return "NORMAL";
|
||||
case self::LOCKED_OR_DISABLED:
|
||||
return "LOCKED_OR_DISABLED";
|
||||
case self::CHANGE_PASSWORD:
|
||||
return "CHANGE_PASSWORD";
|
||||
case self::TERMINATED:
|
||||
return "TERMINATED";
|
||||
case self::ALERT_ON_ACCESS:
|
||||
return "ALERT_ON_ACCESS";
|
||||
default:
|
||||
return "OTHER_" . $this->status;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -9,11 +9,9 @@ require_once __DIR__ . '/../required.php';
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
require_once __DIR__ . '/userinfo.php';
|
||||
|
||||
header("Content-Type: application/json");
|
||||
|
||||
$showwant = ($VARS['show_want'] == 1);
|
||||
$showwant = (isset($VARS['show_want']) && $VARS['show_want'] == 1);
|
||||
|
||||
$out = [];
|
||||
|
||||
@ -117,15 +115,15 @@ $out['recordsFiltered'] = $recordsFiltered;
|
||||
|
||||
$usercache = [];
|
||||
for ($i = 0; $i < count($items); $i++) {
|
||||
$items[$i]["editbtn"] = '<a class="btn btn-primary" href="app.php?page=edititem&id=' . $items[$i]['itemid'] . '"><i class="fas fa-edit"></i> ' . lang("edit", false) . '</a>';
|
||||
$items[$i]["clonebtn"] = '<a class="btn btn-success" href="app.php?page=edititem&id=' . $items[$i]['itemid'] . '&clone=1"><i class="fas fa-clone"></i> ' . lang("clone", false) . '</a>';
|
||||
$items[$i]["editbtn"] = '<a class="btn btn-primary" href="app.php?page=edititem&id=' . $items[$i]['itemid'] . '"><i class="fas fa-edit"></i> ' . $Strings->get("edit", false) . '</a>';
|
||||
$items[$i]["clonebtn"] = '<a class="btn btn-success" href="app.php?page=edititem&id=' . $items[$i]['itemid'] . '&clone=1"><i class="fas fa-clone"></i> ' . $Strings->get("clone", false) . '</a>';
|
||||
if (is_null($items[$i]['userid'])) {
|
||||
$items[$i]["username"] = "";
|
||||
} else {
|
||||
if (!isset($usercache[$items[$i]['userid']])) {
|
||||
$usercache[$items[$i]['userid']] = getUserByID($items[$i]['userid']);
|
||||
$usercache[$items[$i]['userid']] = new User($items[$i]['userid']);
|
||||
}
|
||||
$items[$i]["username"] = $usercache[$items[$i]['userid']]['name'];
|
||||
$items[$i]["username"] = $usercache[$items[$i]['userid']]->getName() . " (" . $usercache[$items[$i]['userid']]->getUsername() . ")";
|
||||
}
|
||||
}
|
||||
$out['items'] = $items;
|
||||
|
131
lib/iputils.php
131
lib/iputils.php
@ -1,131 +0,0 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
/**
|
||||
* Check if a given ipv4 address is in a given cidr
|
||||
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
|
||||
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
|
||||
* @return boolean true if the ip is in this range / false if not.
|
||||
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
|
||||
*/
|
||||
function ip4_in_cidr($ip, $cidr) {
|
||||
if (strpos($cidr, '/') == false) {
|
||||
$cidr .= '/32';
|
||||
}
|
||||
// $range is in IP/CIDR format eg 127.0.0.1/24
|
||||
list( $cidr, $netmask ) = explode('/', $cidr, 2);
|
||||
$range_decimal = ip2long($cidr);
|
||||
$ip_decimal = ip2long($ip);
|
||||
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
|
||||
$netmask_decimal = ~ $wildcard_decimal;
|
||||
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given ipv6 address is in a given cidr
|
||||
* @param string $ip IP to check in IPV6 format
|
||||
* @param string $cidr CIDR netmask
|
||||
* @return boolean true if the IP is in this range, false otherwise.
|
||||
* @author MW. <https://stackoverflow.com/a/7952169>
|
||||
*/
|
||||
function ip6_in_cidr($ip, $cidr) {
|
||||
$address = inet_pton($ip);
|
||||
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
|
||||
$subnetMask = explode("/", $cidr)[1];
|
||||
|
||||
$addr = str_repeat("f", $subnetMask / 4);
|
||||
switch ($subnetMask % 4) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
$addr .= "8";
|
||||
break;
|
||||
case 2:
|
||||
$addr .= "c";
|
||||
break;
|
||||
case 3:
|
||||
$addr .= "e";
|
||||
break;
|
||||
}
|
||||
$addr = str_pad($addr, 32, '0');
|
||||
$addr = pack("H*", $addr);
|
||||
|
||||
$binMask = $addr;
|
||||
return ($address & $binMask) == $subnetAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the REMOTE_ADDR is on Cloudflare's network.
|
||||
* @return boolean true if it is, otherwise false
|
||||
*/
|
||||
function validateCloudflare() {
|
||||
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
// Using IPv6
|
||||
$cloudflare_ips_v6 = [
|
||||
"2400:cb00::/32",
|
||||
"2405:8100::/32",
|
||||
"2405:b500::/32",
|
||||
"2606:4700::/32",
|
||||
"2803:f800::/32",
|
||||
"2c0f:f248::/32",
|
||||
"2a06:98c0::/29"
|
||||
];
|
||||
$valid = false;
|
||||
foreach ($cloudflare_ips_v6 as $cidr) {
|
||||
if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Using IPv4
|
||||
$cloudflare_ips_v4 = [
|
||||
"103.21.244.0/22",
|
||||
"103.22.200.0/22",
|
||||
"103.31.4.0/22",
|
||||
"104.16.0.0/12",
|
||||
"108.162.192.0/18",
|
||||
"131.0.72.0/22",
|
||||
"141.101.64.0/18",
|
||||
"162.158.0.0/15",
|
||||
"172.64.0.0/13",
|
||||
"173.245.48.0/20",
|
||||
"188.114.96.0/20",
|
||||
"190.93.240.0/20",
|
||||
"197.234.240.0/22",
|
||||
"198.41.128.0/17"
|
||||
];
|
||||
$valid = false;
|
||||
foreach ($cloudflare_ips_v4 as $cidr) {
|
||||
if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a good guess at the client's real IP address.
|
||||
*
|
||||
* @return string Client IP or `0.0.0.0` if we can't find anything
|
||||
*/
|
||||
function getClientIP() {
|
||||
// If CloudFlare is in the mix, we should use it.
|
||||
// Check if the request is actually from CloudFlare before trusting it.
|
||||
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
|
||||
if (validateCloudflare()) {
|
||||
return $_SERVER["HTTP_CF_CONNECTING_IP"];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_SERVER["REMOTE_ADDR"])) {
|
||||
return $_SERVER["REMOTE_ADDR"];
|
||||
}
|
||||
|
||||
return "0.0.0.0"; // This will not happen unless we aren't a web server
|
||||
}
|
402
lib/login.php
402
lib/login.php
@ -1,402 +0,0 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
/**
|
||||
* Authentication and account functions. Connects to a Portal instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check the login server API for sanity
|
||||
* @return boolean true if OK, else false
|
||||
*/
|
||||
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);
|
||||
if ($resp['status'] == "OK") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given AccountHub API key is valid by attempting to
|
||||
* access the API with it.
|
||||
* @param String $key The API key to check
|
||||
* @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Account handling //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Checks the given credentials against the API.
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean True if OK, else false
|
||||
*/
|
||||
function authenticate_user($username, $password, &$errmsg) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "auth",
|
||||
'username' => $username,
|
||||
'password' => $password
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return true;
|
||||
} else {
|
||||
$errmsg = $resp['msg'];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a username exists.
|
||||
* @param String $username
|
||||
*/
|
||||
function user_exists($username) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "userexists",
|
||||
'username' => $username
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK" && $resp['exists'] === true) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a UID exists.
|
||||
* @param String $uid
|
||||
*/
|
||||
function uid_exists($uid) {
|
||||
$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);
|
||||
if ($resp['status'] == "OK" && $resp['exists'] === true) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
|
||||
* CHANGE_PASSWORD, or ALERT_ON_ACCESS
|
||||
* @param string $username
|
||||
* @return string
|
||||
*/
|
||||
function get_account_status($username) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "acctstatus",
|
||||
'username' => $username
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['account'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given username has the given permission (or admin access)
|
||||
* @param string $username
|
||||
* @param string $permcode
|
||||
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
|
||||
*/
|
||||
function account_has_permission($username, $permcode) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "permission",
|
||||
'username' => $username,
|
||||
'code' => $permcode
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['has_permission'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Login handling //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Setup $_SESSION values with user data and set loggedin flag to true
|
||||
* @param string $username
|
||||
*/
|
||||
function doLoginUser($username) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "userinfo",
|
||||
'username' => $username
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
|
||||
if ($resp['status'] == "OK") {
|
||||
$userinfo = $resp['data'];
|
||||
session_regenerate_id(true);
|
||||
$newSession = session_id();
|
||||
session_write_close();
|
||||
session_id($newSession);
|
||||
session_start();
|
||||
$_SESSION['username'] = $username;
|
||||
$_SESSION['uid'] = $userinfo['uid'];
|
||||
$_SESSION['email'] = $userinfo['email'];
|
||||
$_SESSION['realname'] = $userinfo['name'];
|
||||
$_SESSION['loggedin'] = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sendLoginAlertEmail($username) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "alertemail",
|
||||
'username' => $username,
|
||||
'appname' => SITE_TITLE
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
return "An unknown error occurred.";
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return true;
|
||||
} else {
|
||||
return $resp['msg'];
|
||||
}
|
||||
}
|
||||
|
||||
function simLogin($username, $password) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "login",
|
||||
'username' => $username,
|
||||
'password' => $password
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return true;
|
||||
} else {
|
||||
return $resp['msg'];
|
||||
}
|
||||
}
|
||||
|
||||
function verifyCaptcheck($session, $answer, $url) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 2-factor authentication //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Check if a user has TOTP setup
|
||||
* @param string $username
|
||||
* @return boolean true if TOTP secret exists, else false
|
||||
*/
|
||||
function userHasTOTP($username) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "hastotp",
|
||||
'username' => $username
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['otp'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a TOTP multiauth code
|
||||
* @global $database
|
||||
* @param string $username
|
||||
* @param int $code
|
||||
* @return boolean true if it's legit, else false
|
||||
*/
|
||||
function verifyTOTP($username, $code) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "verifytotp",
|
||||
'username' => $username,
|
||||
'code' => $code
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['valid'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ if (LOADED) {
|
||||
generateReport($VARS['type'], $VARS['format']);
|
||||
die();
|
||||
} else {
|
||||
lang("invalid parameters");
|
||||
$Strings->get("invalid parameters");
|
||||
die();
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,7 @@ if (LOADED) {
|
||||
* @return string
|
||||
*/
|
||||
function getItemReport($filter = []) {
|
||||
global $database;
|
||||
global $database, $Strings;
|
||||
$items = $database->select(
|
||||
"items", [
|
||||
"[>]locations" => ["locid"],
|
||||
@ -77,28 +77,27 @@ function getItemReport($filter = []) {
|
||||
], $filter
|
||||
);
|
||||
$header = [
|
||||
lang("itemid", false),
|
||||
lang("name", false),
|
||||
lang("category", false),
|
||||
lang("location", false),
|
||||
lang("code 1", false),
|
||||
lang("code 2", false),
|
||||
lang("quantity", false),
|
||||
lang("want", false),
|
||||
lang("cost", false),
|
||||
lang("price", false),
|
||||
lang("assigned to", false),
|
||||
lang("description", false),
|
||||
lang("notes", false),
|
||||
lang("comments", false)
|
||||
$Strings->get("itemid", false),
|
||||
$Strings->get("name", false),
|
||||
$Strings->get("category", false),
|
||||
$Strings->get("location", false),
|
||||
$Strings->get("code 1", false),
|
||||
$Strings->get("code 2", false),
|
||||
$Strings->get("quantity", false),
|
||||
$Strings->get("want", false),
|
||||
$Strings->get("Cost", false),
|
||||
$Strings->get("Price", false),
|
||||
$Strings->get("assigned to", false),
|
||||
$Strings->get("Description", false),
|
||||
$Strings->get("Notes", false),
|
||||
$Strings->get("Comments", false)
|
||||
];
|
||||
$out = [$header];
|
||||
for ($i = 0; $i < count($items); $i++) {
|
||||
$user = "";
|
||||
if (!is_null($items[$i]["userid"])) {
|
||||
require_once __DIR__ . "/userinfo.php";
|
||||
$u = getUserByID($items[$i]["userid"]);
|
||||
$user = $u['name'] . " (" . $u['username'] . ')';
|
||||
$u = new User($items[$i]["userid"]);
|
||||
$user = $u->getName() . " (" . $u->getUsername() . ')';
|
||||
}
|
||||
$out[] = [
|
||||
$items[$i]["itemid"],
|
||||
@ -121,12 +120,12 @@ function getItemReport($filter = []) {
|
||||
}
|
||||
|
||||
function getCategoryReport() {
|
||||
global $database;
|
||||
global $database, $Strings;
|
||||
$cats = $database->select('categories', [
|
||||
'catid',
|
||||
'catname'
|
||||
]);
|
||||
$header = [lang("id", false), lang("category", false), lang("item count", false)];
|
||||
$header = [$Strings->get("id", false), $Strings->get("category", false), $Strings->get("item count", false)];
|
||||
$out = [$header];
|
||||
for ($i = 0; $i < count($cats); $i++) {
|
||||
$itemcount = $database->count('items', ['catid' => $cats[$i]['catid']]);
|
||||
@ -140,14 +139,14 @@ function getCategoryReport() {
|
||||
}
|
||||
|
||||
function getLocationReport() {
|
||||
global $database;
|
||||
global $database, $Strings;
|
||||
$locs = $database->select('locations', [
|
||||
'locid',
|
||||
'locname',
|
||||
'loccode',
|
||||
'locinfo'
|
||||
]);
|
||||
$header = [lang("id", false), lang("location", false), lang("code", false), lang("item count", false), lang("description", false)];
|
||||
$header = [$Strings->get("id", false), $Strings->get("location", false), $Strings->get("code", false), $Strings->get("item count", false), $Strings->get("Description", false)];
|
||||
$out = [$header];
|
||||
for ($i = 0; $i < count($locs); $i++) {
|
||||
$itemcount = $database->count('items', ['locid' => $locs[$i]['locid']]);
|
||||
@ -216,7 +215,10 @@ function dataToODS($data, $name = "report") {
|
||||
$rowid++;
|
||||
}
|
||||
$ods->addTable($table);
|
||||
$ods->downloadOdsFile($name . "_" . date("Y-m-d_Hi") . ".ods");
|
||||
// The @ is a workaround to silence the tempnam notice,
|
||||
// which breaks the file. This is apparently the intended behavior:
|
||||
// https://bugs.php.net/bug.php?id=69489
|
||||
@$ods->downloadOdsFile($name . "_" . date("Y-m-d_Hi") . ".ods");
|
||||
}
|
||||
|
||||
function dataToHTML($data, $name = "report") {
|
||||
|
127
lib/userinfo.php
127
lib/userinfo.php
@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
/**
|
||||
* Get user info for the given username.
|
||||
* @param int $u username
|
||||
* @return [string] Array of [uid, username, name]
|
||||
*/
|
||||
function getUserByUsername($u) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "userinfo",
|
||||
'username' => $u
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['data'];
|
||||
} else {
|
||||
// this shouldn't happen, but in case it does just fake it.
|
||||
return ["name" => $u, "username" => $u, "uid" => $u];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user info for the given UID.
|
||||
* @param int $u user ID
|
||||
* @return [string] Array of [uid, username, name]
|
||||
*/
|
||||
function getUserByID($u) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "userinfo",
|
||||
'uid' => $u
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['data'];
|
||||
} else {
|
||||
// this shouldn't happen, but in case it does just fake it.
|
||||
return ["name" => $u, "username" => $u, "uid" => $u];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the first UID is a manager of the second UID.
|
||||
* @param int $m Manager UID
|
||||
* @param int $e Employee UID
|
||||
* @return boolean
|
||||
*/
|
||||
function isManagerOf($m, $e) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "ismanagerof",
|
||||
'manager' => $m,
|
||||
'employee' => $e,
|
||||
'uid' => 1
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['managerof'] === true;
|
||||
} else {
|
||||
// this shouldn't happen, but in case it does just fake it.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of UIDs the given UID is a manager of.
|
||||
* @param int $manageruid The UID of the manager to find employees for.
|
||||
* @return [int]
|
||||
*/
|
||||
function getManagedUIDs($manageruid) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', PORTAL_API, [
|
||||
'form_params' => [
|
||||
'key' => PORTAL_KEY,
|
||||
'action' => "getmanaged",
|
||||
'uid' => $manageruid
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() > 299) {
|
||||
sendError("Login server error: " . $response->getBody());
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
return $resp['employees'];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
@ -14,8 +14,6 @@ $access_permission = "INV_VIEW";
|
||||
|
||||
require __DIR__ . "/../required.php";
|
||||
|
||||
require __DIR__ . "/../lib/login.php";
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
@ -73,7 +71,7 @@ function mobile_valid($username, $code) {
|
||||
}
|
||||
|
||||
if (mobile_enabled() !== TRUE) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("mobile login disabled", false)]));
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
|
||||
}
|
||||
|
||||
// Make sure we have a username and access key
|
||||
@ -93,11 +91,12 @@ if (!mobile_valid($VARS['username'], $VARS['key'])) {
|
||||
switch ($VARS['action']) {
|
||||
case "start_session":
|
||||
// Do a web login.
|
||||
if (user_exists($VARS['username'])) {
|
||||
if (get_account_status($VARS['username']) == "NORMAL") {
|
||||
if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) {
|
||||
if (is_null($access_permission) || account_has_permission($VARS['username'], $access_permission)) {
|
||||
doLoginUser($VARS['username'], $VARS['password']);
|
||||
$user = User::byUsername($VARS['username']);
|
||||
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 {
|
||||
@ -106,7 +105,7 @@ switch ($VARS['action']) {
|
||||
}
|
||||
}
|
||||
}
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
default:
|
||||
http_response_code(404);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));
|
||||
|
16
pages.php
16
pages.php
@ -7,12 +7,12 @@
|
||||
// List of pages and metadata
|
||||
define("PAGES", [
|
||||
"home" => [
|
||||
"title" => "home",
|
||||
"title" => "Home",
|
||||
"navbar" => true,
|
||||
"icon" => "fas fa-home"
|
||||
],
|
||||
"items" => [
|
||||
"title" => "items",
|
||||
"title" => "Items",
|
||||
"navbar" => true,
|
||||
"icon" => "fas fa-boxes",
|
||||
"styles" => [
|
||||
@ -25,7 +25,7 @@ define("PAGES", [
|
||||
],
|
||||
],
|
||||
"locations" => [
|
||||
"title" => "locations",
|
||||
"title" => "Locations",
|
||||
"navbar" => true,
|
||||
"icon" => "fas fa-map-marker",
|
||||
"styles" => [
|
||||
@ -38,7 +38,7 @@ define("PAGES", [
|
||||
],
|
||||
],
|
||||
"categories" => [
|
||||
"title" => "categories",
|
||||
"title" => "Categories",
|
||||
"navbar" => true,
|
||||
"icon" => "fas fa-pallet",
|
||||
"styles" => [
|
||||
@ -51,7 +51,7 @@ define("PAGES", [
|
||||
],
|
||||
],
|
||||
"edititem" => [
|
||||
"title" => "edit item",
|
||||
"title" => "Edit item",
|
||||
"navbar" => false,
|
||||
"styles" => [
|
||||
"static/css/easy-autocomplete.min.css"
|
||||
@ -62,21 +62,21 @@ define("PAGES", [
|
||||
],
|
||||
],
|
||||
"editcat" => [
|
||||
"title" => "edit category",
|
||||
"title" => "Edit category",
|
||||
"navbar" => false,
|
||||
"scripts" => [
|
||||
"static/js/editcat.js"
|
||||
],
|
||||
],
|
||||
"editloc" => [
|
||||
"title" => "edit location",
|
||||
"title" => "Edit location",
|
||||
"navbar" => false,
|
||||
"scripts" => [
|
||||
"static/js/editloc.js"
|
||||
],
|
||||
],
|
||||
"export" => [
|
||||
"title" => "report export",
|
||||
"title" => "Reports/Export",
|
||||
"navbar" => true,
|
||||
"icon" => "fas fa-download",
|
||||
"scripts" => [
|
||||
|
@ -5,6 +5,6 @@
|
||||
?>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
|
||||
<div class="alert alert-warning"><b><?php lang("404 error");?></b><br /> <?php lang("page not found"); ?></div>
|
||||
<div class="alert alert-warning"><b><?php $Strings->get("404 error");?></b><br /> <?php $Strings->get("page not found"); ?></div>
|
||||
</div>
|
||||
</div>
|
@ -8,15 +8,15 @@ require_once __DIR__ . '/../required.php';
|
||||
redirectifnotloggedin();
|
||||
?>
|
||||
<div class="btn-group">
|
||||
<a href="app.php?page=editcat" class="btn btn-success"><i class="fas fa-plus"></i> <?php lang("new category"); ?></a>
|
||||
<a href="app.php?page=editcat" class="btn btn-success"><i class="fas fa-plus"></i> <?php $Strings->get("New Category"); ?></a>
|
||||
</div>
|
||||
<table id="cattable" class="table table-bordered table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-priority="0"></th>
|
||||
<th data-priority="1"><?php lang('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-pallet hidden-sm"></i> <?php lang('category'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-hashtag hidden-sm"></i> <?php lang('item count'); ?></th>
|
||||
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-pallet hidden-sm"></i> <?php $Strings->get('category'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-hashtag hidden-sm"></i> <?php $Strings->get('item count'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -31,7 +31,7 @@ redirectifnotloggedin();
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a class="btn btn-primary btn-sm" href="app.php?page=editcat&id=<?php echo $cat['catid']; ?>"><i class="fas fa-edit"></i> <?php lang("edit"); ?></a>
|
||||
<a class="btn btn-primary btn-sm" href="app.php?page=editcat&id=<?php echo $cat['catid']; ?>"><i class="fas fa-edit"></i> <?php $Strings->get("edit"); ?></a>
|
||||
</td>
|
||||
<td><?php echo $cat['catname']; ?></td>
|
||||
<td><?php echo $itemcount; ?></td>
|
||||
@ -43,9 +43,9 @@ redirectifnotloggedin();
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th data-priority="0"></th>
|
||||
<th data-priority="1"><?php lang('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-pallet hidden-sm"></i> <?php lang('category'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-hashtag hidden-sm"></i> <?php lang('item count'); ?></th>
|
||||
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-pallet hidden-sm"></i> <?php $Strings->get('category'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-hashtag hidden-sm"></i> <?php $Strings->get('item count'); ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
@ -13,7 +13,7 @@ $catdata = [
|
||||
|
||||
$editing = false;
|
||||
|
||||
if (!is_empty($VARS['id'])) {
|
||||
if (!empty($VARS['id'])) {
|
||||
if ($database->has('categories', ['catid' => $VARS['id']])) {
|
||||
$editing = true;
|
||||
$catdata = $database->select(
|
||||
@ -36,32 +36,32 @@ if (!is_empty($VARS['id'])) {
|
||||
<?php
|
||||
if ($editing) {
|
||||
?>
|
||||
<i class="fas fa-edit"></i> <?php lang2("editing category", ['cat' => "<span id=\"name_title\">" . htmlspecialchars($catdata['catname']) . "</span>"]); ?>
|
||||
<i class="fas fa-edit"></i> <?php $Strings->build("editing category", ['cat' => "<span id=\"name_title\">" . htmlspecialchars($catdata['catname']) . "</span>"]); ?>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<i class="fas fa-edit"></i> <?php lang("adding category"); ?>
|
||||
<i class="fas fa-edit"></i> <?php $Strings->get("Adding new category"); ?>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="name"><i class="fas fa-archive"></i> <?php lang("name"); ?></label>
|
||||
<label for="name"><i class="fas fa-archive"></i> <?php $Strings->get("name"); ?></label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="Foo Bar" required="required" value="<?php echo htmlspecialchars($catdata['catname']); ?>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="catid" value="<?php echo htmlspecialchars($VARS['id']); ?>" />
|
||||
<input type="hidden" name="catid" value="<?php echo isset($VARS['id']) ? htmlspecialchars($VARS['id']) : ""; ?>" />
|
||||
<input type="hidden" name="action" value="editcat" />
|
||||
<input type="hidden" name="source" value="categories" />
|
||||
|
||||
<div class="card-footer d-flex">
|
||||
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
|
||||
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
|
||||
<?php
|
||||
if ($editing) {
|
||||
?>
|
||||
<a href="action.php?action=deletecat&source=categories&catid=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger ml-auto"><i class="fas fa-times"></i> <?php lang('delete'); ?></a>
|
||||
<a href="action.php?action=deletecat&source=categories&catid=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger ml-auto"><i class="fas fa-times"></i> <?php $Strings->get('delete'); ?></a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
@ -4,8 +4,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
require_once __DIR__ . '/../required.php';
|
||||
require_once __DIR__ . "/../lib/login.php";
|
||||
require_once __DIR__ . "/../lib/userinfo.php";
|
||||
|
||||
redirectifnotloggedin();
|
||||
|
||||
@ -35,10 +33,10 @@ $itemdata = [
|
||||
$editing = false;
|
||||
$cloning = false;
|
||||
|
||||
if (!is_empty($VARS['id'])) {
|
||||
if (!empty($VARS['id'])) {
|
||||
if ($database->has('items', ['itemid' => $VARS['id']])) {
|
||||
$editing = true;
|
||||
if ($VARS['clone'] == 1) {
|
||||
if (isset($VARS['clone']) && $VARS['clone'] == 1) {
|
||||
$cloning = true;
|
||||
}
|
||||
$itemdata = $database->select(
|
||||
@ -83,37 +81,37 @@ if (!is_empty($VARS['id'])) {
|
||||
<?php
|
||||
if ($cloning) {
|
||||
?>
|
||||
<i class="fas fa-edit"></i> <?php lang2("cloning item", ['oitem' => htmlspecialchars($itemdata['name']), 'nitem' => "<span id=\"name_title\">" . htmlspecialchars($itemdata['name']) . "</span>"]); ?>
|
||||
<i class="fas fa-edit"></i> <?php $Strings->build("cloning item", ['oitem' => htmlspecialchars($itemdata['name']), 'nitem' => "<span id=\"name_title\">" . htmlspecialchars($itemdata['name']) . "</span>"]); ?>
|
||||
<?php
|
||||
} else if ($editing) {
|
||||
?>
|
||||
<i class="fas fa-edit"></i> <?php lang2("editing item", ['item' => "<span id=\"name_title\">" . htmlspecialchars($itemdata['name']) . "</span>"]); ?>
|
||||
<i class="fas fa-edit"></i> <?php $Strings->build("editing item", ['item' => "<span id=\"name_title\">" . htmlspecialchars($itemdata['name']) . "</span>"]); ?>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<i class="fas fa-edit"></i> <?php lang("adding item"); ?>
|
||||
<i class="fas fa-edit"></i> <?php $Strings->get("Adding Item"); ?>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="name"><i class="fas fa-cube"></i> <?php lang("name"); ?></label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="<?php lang("placeholder item name"); ?>" required="required" value="<?php echo htmlspecialchars($itemdata['name']); ?>" />
|
||||
<label for="name"><i class="fas fa-cube"></i> <?php $Strings->get("name"); ?></label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="<?php $Strings->get("placeholder item name"); ?>" required="required" value="<?php echo htmlspecialchars($itemdata['name']); ?>" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="cat"><i class="fas fa-archive"></i> <?php lang("category"); ?></label>
|
||||
<input type="text" name="catstr" class="form-control" id="cat" placeholder="<?php lang("placeholder category name"); ?>" value="<?php echo htmlspecialchars($itemdata['catname']); ?>" />
|
||||
<label for="cat"><i class="fas fa-archive"></i> <?php $Strings->get("category"); ?></label>
|
||||
<input type="text" name="catstr" class="form-control" id="cat" placeholder="<?php $Strings->get("placeholder category name"); ?>" value="<?php echo htmlspecialchars($itemdata['catname']); ?>" />
|
||||
<input type="hidden" id="realcat" name="cat" value="<?php echo $itemdata['catid']; ?>" required="required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="loc"><i class="fas fa-map-marker"></i> <?php lang("location"); ?></label>
|
||||
<input type="text" name="locstr" class="form-control" id="loc" placeholder="<?php lang("placeholder location name"); ?>" value="<?php echo htmlspecialchars($itemdata['locname']); ?>" />
|
||||
<label for="loc"><i class="fas fa-map-marker"></i> <?php $Strings->get("location"); ?></label>
|
||||
<input type="text" name="locstr" class="form-control" id="loc" placeholder="<?php $Strings->get("placeholder location name"); ?>" value="<?php echo htmlspecialchars($itemdata['locname']); ?>" />
|
||||
<input type="hidden" id="realloc" name="loc" value="<?php echo $itemdata['locid']; ?>" required="required" />
|
||||
</div>
|
||||
</div>
|
||||
@ -122,7 +120,7 @@ if (!is_empty($VARS['id'])) {
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="code1"><i class="fas fa-barcode"></i> <?php lang("code 1"); ?></label>
|
||||
<label for="code1"><i class="fas fa-barcode"></i> <?php $Strings->get("code 1"); ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="code1" name="code1" placeholder="123456789" value="<?php echo htmlspecialchars($itemdata['code1']); ?>" />
|
||||
<span class="input-group-btn mobile-app-show">
|
||||
@ -133,7 +131,7 @@ if (!is_empty($VARS['id'])) {
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="code2"><i class="fas fa-qrcode"></i> <?php lang("code 2"); ?></label>
|
||||
<label for="code2"><i class="fas fa-qrcode"></i> <?php $Strings->get("code 2"); ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="code2" name="code2" placeholder="qwerty123" value="<?php echo htmlspecialchars($itemdata['code2']); ?>" />
|
||||
<span class="input-group-btn mobile-app-show">
|
||||
@ -147,22 +145,25 @@ if (!is_empty($VARS['id'])) {
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="qty"><i class="fas fa-hashtag"></i> <?php lang('quantity'); ?></label>
|
||||
<label for="qty"><i class="fas fa-hashtag"></i> <?php $Strings->get('quantity'); ?></label>
|
||||
<input type="number" class="form-control" id="qty" name="qty" placeholder="1" value="<?php echo $itemdata['qty']; ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="want"><i class="fas fa-hashtag"></i> <?php lang('minwant'); ?></label>
|
||||
<label for="want"><i class="fas fa-hashtag"></i> <?php $Strings->get('minwant'); ?></label>
|
||||
<input type="number" class="form-control" id="want" name="want" placeholder="1" value="<?php echo $itemdata['want']; ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="assignedto"><i class="fas fa-user"></i> <?php lang('assigned to'); ?></label>
|
||||
<input type="text" class="form-control" id="assignedto" name="assignedto" placeholder="<?php lang('nobody'); ?>" value="<?php
|
||||
if (!is_empty($itemdata['userid']) && uid_exists($itemdata['userid'])) {
|
||||
echo getUserByID($itemdata['userid'])['username'];
|
||||
<label for="assignedto"><i class="fas fa-user"></i> <?php $Strings->get('assigned to'); ?></label>
|
||||
<input type="text" class="form-control" id="assignedto" name="assignedto" placeholder="<?php $Strings->get('nobody'); ?>" value="<?php
|
||||
if (!empty($itemdata['userid'])) {
|
||||
$user = new User($itemdata['userid']);
|
||||
if ($user->exists()) {
|
||||
echo $user->getUsername();
|
||||
}
|
||||
}
|
||||
?>" />
|
||||
</div>
|
||||
@ -172,19 +173,19 @@ if (!is_empty($VARS['id'])) {
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="cost"><i class="far fa-money-bill-alt"></i> <?php lang('item cost'); ?></label>
|
||||
<label for="cost"><i class="far fa-money-bill-alt"></i> <?php $Strings->get('Item cost'); ?></label>
|
||||
<input type="number" class="form-control" id="cost" name="cost" placeholder="0.00" step="0.01" value="<?php echo $itemdata['cost']; ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="price"><i class="fas fa-shopping-cart"></i> <?php lang('sale price'); ?></label>
|
||||
<label for="price"><i class="fas fa-shopping-cart"></i> <?php $Strings->get('Sale price'); ?></label>
|
||||
<input type="number" class="form-control" id="price" name="price" placeholder="0.00" step="0.01" value="<?php echo $itemdata['price']; ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="info1"><i class="fas fa-info"></i> <?php lang("description"); ?></label>
|
||||
<label for="info1"><i class="fas fa-info"></i> <?php $Strings->get("Description"); ?></label>
|
||||
<textarea class="form-control" id="info1" name="text1"><?php echo htmlspecialchars($itemdata['text1']); ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -193,13 +194,13 @@ if (!is_empty($VARS['id'])) {
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="info2"><i class="fas fa-sticky-note"></i> <?php lang("notes"); ?></label>
|
||||
<label for="info2"><i class="fas fa-sticky-note"></i> <?php $Strings->get("Notes"); ?></label>
|
||||
<textarea class="form-control" id="info2" name="text2"><?php echo htmlspecialchars($itemdata['text2']); ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="info3"><i class="fas fa-comments"></i> <?php lang("comments"); ?></label>
|
||||
<label for="info3"><i class="fas fa-comments"></i> <?php $Strings->get("Comments"); ?></label>
|
||||
<textarea class="form-control" id="info3" name="text3"><?php echo htmlspecialchars($itemdata['text3']); ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -215,11 +216,11 @@ if (!is_empty($VARS['id'])) {
|
||||
<input type="hidden" name="source" value="items" />
|
||||
|
||||
<div class="card-footer d-flex">
|
||||
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
|
||||
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
|
||||
<?php
|
||||
if ($editing && !$cloning) {
|
||||
?>
|
||||
<a href="action.php?action=deleteitem&source=items&itemid=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger ml-auto"><i class="fas fa-times"></i> <?php lang('delete'); ?></a>
|
||||
<a href="action.php?action=deleteitem&source=items&itemid=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger ml-auto"><i class="fas fa-times"></i> <?php $Strings->get('delete'); ?></a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
@ -17,7 +17,7 @@ $locdata = [
|
||||
|
||||
$editing = false;
|
||||
|
||||
if (!is_empty($VARS['id'])) {
|
||||
if (!empty($VARS['id'])) {
|
||||
if ($database->has('locations', ['locid' => $VARS['id']])) {
|
||||
$editing = true;
|
||||
$locdata = $database->select(
|
||||
@ -42,11 +42,11 @@ if (!is_empty($VARS['id'])) {
|
||||
<?php
|
||||
if ($editing) {
|
||||
?>
|
||||
<i class="fas fa-edit"></i> <?php lang2("editing location", ['loc' => "<span id=\"name_title\">" . htmlspecialchars($locdata['locname']) . "</span>"]); ?>
|
||||
<i class="fas fa-edit"></i> <?php $Strings->build("editing location", ['loc' => "<span id=\"name_title\">" . htmlspecialchars($locdata['locname']) . "</span>"]); ?>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<i class="fas fa-edit"></i> <?php lang("adding location"); ?>
|
||||
<i class="fas fa-edit"></i> <?php $Strings->get("Adding new location"); ?>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
@ -55,34 +55,34 @@ if (!is_empty($VARS['id'])) {
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name"><i class="fas fa-map-marker"></i> <?php lang("name"); ?></label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="<?php lang("placeholder location name"); ?>" required="required" value="<?php echo htmlspecialchars($locdata['locname']); ?>" />
|
||||
<label for="name"><i class="fas fa-map-marker"></i> <?php $Strings->get("name"); ?></label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="<?php $Strings->get("placeholder location name"); ?>" required="required" value="<?php echo htmlspecialchars($locdata['locname']); ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="code"><i class="fas fa-barcode"></i> <?php lang("code"); ?></label>
|
||||
<label for="code"><i class="fas fa-barcode"></i> <?php $Strings->get("code"); ?></label>
|
||||
<input type="text" class="form-control" id="code" name="code" placeholder="123456789" value="<?php echo htmlspecialchars($locdata['loccode']); ?>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="info"><i class="fas fa-info"></i> <?php lang("description"); ?></label>
|
||||
<label for="info"><i class="fas fa-info"></i> <?php $Strings->get("Description"); ?></label>
|
||||
<textarea class="form-control" id="info" name="info"><?php echo htmlspecialchars($locdata['locinfo']); ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="locid" value="<?php echo htmlspecialchars($VARS['id']); ?>" />
|
||||
<input type="hidden" name="locid" value="<?php echo isset($VARS['id']) ? htmlspecialchars($VARS['id']) : ""; ?>" />
|
||||
<input type="hidden" name="action" value="editloc" />
|
||||
<input type="hidden" name="source" value="locations" />
|
||||
|
||||
<div class="card-footer d-flex">
|
||||
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
|
||||
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
|
||||
<?php
|
||||
if ($editing) {
|
||||
?>
|
||||
<a href="action.php?action=deleteloc&source=locations&locid=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger ml-auto"><i class="fas fa-times"></i> <?php lang('delete'); ?></a>
|
||||
<a href="action.php?action=deleteloc&source=locations&locid=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger ml-auto"><i class="fas fa-times"></i> <?php $Strings->get('delete'); ?></a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
@ -14,23 +14,23 @@ redirectifnotloggedin();
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="type"><?php lang("report type"); ?></label>
|
||||
<label for="type"><?php $Strings->get("report type"); ?></label>
|
||||
<select name="type" class="form-control" required>
|
||||
<option value="item"><?php lang("items") ?></option>
|
||||
<option value="category"><?php lang("categories") ?></option>
|
||||
<option value="location"><?php lang("locations") ?></option>
|
||||
<option value="itemstock"><?php lang("understocked items") ?></option>
|
||||
<option value="item"><?php $Strings->get("Items") ?></option>
|
||||
<option value="category"><?php $Strings->get("Categories") ?></option>
|
||||
<option value="location"><?php $Strings->get("Locations") ?></option>
|
||||
<option value="itemstock"><?php $Strings->get("Understocked Items") ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="type"><?php lang("format"); ?></label>
|
||||
<label for="type"><?php $Strings->get("format"); ?></label>
|
||||
<select name="format" class="form-control" required>
|
||||
<option value="csv"><?php lang("csv file") ?></option>
|
||||
<option value="ods"><?php lang("ods file") ?></option>
|
||||
<option value="html"><?php lang("html file") ?></option>
|
||||
<option value="csv"><?php $Strings->get("csv file") ?></option>
|
||||
<option value="ods"><?php $Strings->get("ods file") ?></option>
|
||||
<option value="html"><?php $Strings->get("html file") ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,7 +43,7 @@ redirectifnotloggedin();
|
||||
<input type="hidden" name="code" value="<?php echo $code; ?>" />
|
||||
|
||||
<div class="card-footer d-flex">
|
||||
<button type="submit" class="btn btn-success ml-auto" id="genrptbtn"><i class="fas fa-download"></i> <?php lang("generate report"); ?></button>
|
||||
<button type="submit" class="btn btn-success ml-auto" id="genrptbtn"><i class="fas fa-download"></i> <?php $Strings->get("generate report"); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -12,10 +12,10 @@ redirectifnotloggedin();
|
||||
<form action="app.php" method="get">
|
||||
<input type="hidden" name="page" value="items" />
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="q" id="quicklookup_box" placeholder="<?php lang("search"); ?>"/>
|
||||
<input type="text" class="form-control" name="q" id="quicklookup_box" placeholder="<?php $Strings->get("Search"); ?>"/>
|
||||
<div class="input-group-append">
|
||||
<?php
|
||||
if ($_SESSION['mobile']) {
|
||||
if (isset($_SESSION['mobile']) && $_SESSION['mobile']) {
|
||||
?>
|
||||
<span class="btn btn-default" onclick="scancode('#quicklookup_box');">
|
||||
<i class="fas fa-barcode fa-fw"></i>
|
||||
@ -33,11 +33,11 @@ redirectifnotloggedin();
|
||||
<div class="card-deck">
|
||||
<div class="card bg-teal text-light">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><?php lang("total items") ?></h4>
|
||||
<h4 class="card-title"><?php $Strings->get("Total Items") ?></h4>
|
||||
<h1><i class="fas fa-fw fa-boxes"></i> <?php echo $database->count('items'); ?></h1>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="app.php?page=items" class="text-light"><i class="fas fa-arrow-right"></i> <?php lang("view items"); ?></a>
|
||||
<a href="app.php?page=items" class="text-light"><i class="fas fa-arrow-right"></i> <?php $Strings->get("View Items"); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
@ -45,11 +45,11 @@ redirectifnotloggedin();
|
||||
?>
|
||||
<div class="card bg-<?php echo ($lowcnt > 0 ? "deep-orange" : "green"); ?> text-light">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><?php lang("understocked items") ?></h4>
|
||||
<h4 class="card-title"><?php $Strings->get("Understocked Items") ?></h4>
|
||||
<h1><i class="fas fa-fw fa-tachometer-alt"></i> <?php echo $lowcnt; ?></h1>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="app.php?page=items&filter=stock" class="text-light"><i class="fas fa-arrow-right"></i> <?php lang("view understocked"); ?></a>
|
||||
<a href="app.php?page=items&filter=stock" class="text-light"><i class="fas fa-arrow-right"></i> <?php $Strings->get("View Understocked"); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,17 +5,16 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
require_once __DIR__ . '/../required.php';
|
||||
require_once __DIR__ . "/../lib/userinfo.php";
|
||||
|
||||
redirectifnotloggedin();
|
||||
?>
|
||||
|
||||
<div class="btn-group mgn-btm-10px">
|
||||
<a href="app.php?page=edititem" class="btn btn-success"><i class="fa fa-plus"></i> <?php lang("new item"); ?></a>
|
||||
<a href="app.php?page=edititem" class="btn btn-success"><i class="fa fa-plus"></i> <?php $Strings->get("New Item"); ?></a>
|
||||
</div>
|
||||
<?php if ($_GET['filter'] == 'stock') { ?>
|
||||
<?php if (isset($_GET['filter']) && $_GET['filter'] == 'stock') { ?>
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">var filter = "stock";</script>
|
||||
<div class="alert alert-blue-grey"><i class="fa fa-filter fa-fw"></i> <?php lang("only showing understocked"); ?> <a href="app.php?page=items" class="btn btn-sm btn-blue-grey"><?php lang("show all items"); ?></a></div>
|
||||
<div class="alert alert-blue-grey"><i class="fa fa-filter fa-fw"></i> <?php $Strings->get("only showing understocked"); ?> <a href="app.php?page=items" class="btn btn-sm btn-blue-grey"><?php $Strings->get("show all items"); ?></a></div>
|
||||
<?php
|
||||
} else {
|
||||
echo "<script nonce=\"$SECURE_NONCE\">var filter = null;</script>\n";
|
||||
@ -25,15 +24,15 @@ redirectifnotloggedin();
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-priority="0"></th>
|
||||
<th data-priority="1"><?php lang('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-fw fa-box d-none d-md-inline"></i> <?php lang('name'); ?></th>
|
||||
<th data-priority="7"><i class="fas fa-fw fa-pallet d-none d-md-inline"></i> <?php lang('category'); ?></th>
|
||||
<th data-priority="4"><i class="fas fa-fw fa-map-marker d-none d-md-inline"></i> <?php lang('location'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-fw fa-barcode d-none d-md-inline"></i> <?php lang('code 1'); ?></th>
|
||||
<th data-priority="5"><i class="fas fa-fw fa-qrcode d-none d-md-inline"></i> <?php lang('code 2'); ?></th>
|
||||
<th data-priority="3"><i class="fas fa-fw fa-hashtag d-none d-md-inline"></i> <?php lang('qty'); ?></th>
|
||||
<th data-priority="6"><i class="fas fa-fw fa-hashtag d-none d-md-inline"></i> <?php lang('want'); ?></th>
|
||||
<th data-priority="8"><i class="fas fa-fw fa-user d-none d-md-inline"></i> <?php lang('assigned to'); ?></th>
|
||||
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-fw fa-box d-none d-md-inline"></i> <?php $Strings->get('name'); ?></th>
|
||||
<th data-priority="7"><i class="fas fa-fw fa-pallet d-none d-md-inline"></i> <?php $Strings->get('category'); ?></th>
|
||||
<th data-priority="4"><i class="fas fa-fw fa-map-marker d-none d-md-inline"></i> <?php $Strings->get('location'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-fw fa-barcode d-none d-md-inline"></i> <?php $Strings->get('code 1'); ?></th>
|
||||
<th data-priority="5"><i class="fas fa-fw fa-qrcode d-none d-md-inline"></i> <?php $Strings->get('code 2'); ?></th>
|
||||
<th data-priority="3"><i class="fas fa-fw fa-hashtag d-none d-md-inline"></i> <?php $Strings->get('qty'); ?></th>
|
||||
<th data-priority="6"><i class="fas fa-fw fa-hashtag d-none d-md-inline"></i> <?php $Strings->get('want'); ?></th>
|
||||
<th data-priority="8"><i class="fas fa-fw fa-user d-none d-md-inline"></i> <?php $Strings->get('assigned to'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -41,20 +40,20 @@ redirectifnotloggedin();
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th data-priority="0"></th>
|
||||
<th data-priority="1"><?php lang('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-fw fa-box d-none d-md-inline"></i> <?php lang('name'); ?></th>
|
||||
<th data-priority="7"><i class="fas fa-fw fa-pallet d-none d-md-inline"></i> <?php lang('category'); ?></th>
|
||||
<th data-priority="4"><i class="fas fa-fw fa-map-marker d-none d-md-inline"></i> <?php lang('location'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-fw fa-barcode d-none d-md-inline"></i> <?php lang('code 1'); ?></th>
|
||||
<th data-priority="5"><i class="fas fa-fw fa-qrcode d-none d-md-inline"></i> <?php lang('code 2'); ?></th>
|
||||
<th data-priority="3"><i class="fas fa-fw fa-hashtag d-none d-md-inline"></i> <?php lang('qty'); ?></th>
|
||||
<th data-priority="6"><i class="fas fa-fw fa-hashtag d-none d-md-inline"></i> <?php lang('want'); ?></th>
|
||||
<th data-priority="8"><i class="fas fa-fw fa-user d-none d-md-inline"></i> <?php lang('assigned to'); ?></th>
|
||||
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-fw fa-box d-none d-md-inline"></i> <?php $Strings->get('name'); ?></th>
|
||||
<th data-priority="7"><i class="fas fa-fw fa-pallet d-none d-md-inline"></i> <?php $Strings->get('category'); ?></th>
|
||||
<th data-priority="4"><i class="fas fa-fw fa-map-marker d-none d-md-inline"></i> <?php $Strings->get('location'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-fw fa-barcode d-none d-md-inline"></i> <?php $Strings->get('code 1'); ?></th>
|
||||
<th data-priority="5"><i class="fas fa-fw fa-qrcode d-none d-md-inline"></i> <?php $Strings->get('code 2'); ?></th>
|
||||
<th data-priority="3"><i class="fas fa-fw fa-hashtag d-none d-md-inline"></i> <?php $Strings->get('qty'); ?></th>
|
||||
<th data-priority="6"><i class="fas fa-fw fa-hashtag d-none d-md-inline"></i> <?php $Strings->get('want'); ?></th>
|
||||
<th data-priority="8"><i class="fas fa-fw fa-user d-none d-md-inline"></i> <?php $Strings->get('assigned to'); ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<?php
|
||||
if (!is_empty($VARS['q'])) {
|
||||
if (!empty($VARS['q'])) {
|
||||
?>
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
var search_preload_content = "<?php echo $VARS['q']; ?>";
|
||||
|
@ -9,16 +9,16 @@ require_once __DIR__ . '/../required.php';
|
||||
redirectifnotloggedin();
|
||||
?>
|
||||
<div class="btn-group mgn-btm-10px">
|
||||
<a href="app.php?page=editloc" class="btn btn-success"><i class="fa fa-plus"></i> <?php lang("new location"); ?></a>
|
||||
<a href="app.php?page=editloc" class="btn btn-success"><i class="fa fa-plus"></i> <?php $Strings->get("new location"); ?></a>
|
||||
</div>
|
||||
<table id="loctable" class="table table-bordered table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-priority="0"></th>
|
||||
<th data-priority="1"><?php lang('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-map-marker"></i> <?php lang('location'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-barcode"></i> <?php lang('code'); ?></th>
|
||||
<th data-priority="3"><i class="fas fa-hashtag"></i> <?php lang('item count'); ?></th>
|
||||
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-map-marker"></i> <?php $Strings->get('location'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-barcode"></i> <?php $Strings->get('code'); ?></th>
|
||||
<th data-priority="3"><i class="fas fa-hashtag"></i> <?php $Strings->get('item count'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -34,7 +34,7 @@ redirectifnotloggedin();
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a class="btn btn-primary btn-sm" href="app.php?page=editloc&id=<?php echo $loc['locid']; ?>"><i class="fas fa-edit"></i> <?php lang("edit"); ?></a>
|
||||
<a class="btn btn-primary btn-sm" href="app.php?page=editloc&id=<?php echo $loc['locid']; ?>"><i class="fas fa-edit"></i> <?php $Strings->get("edit"); ?></a>
|
||||
</td>
|
||||
<td><?php echo $loc['locname']; ?></td>
|
||||
<td><?php echo $loc['loccode']; ?></td>
|
||||
@ -47,10 +47,10 @@ redirectifnotloggedin();
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th data-priority="0"></th>
|
||||
<th data-priority="1"><?php lang('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-map-marker"></i> <?php lang('location'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-barcode"></i> <?php lang('code'); ?></th>
|
||||
<th data-priority="3"><i class="fas fa-hashtag"></i> <?php lang('item count'); ?></th>
|
||||
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
|
||||
<th data-priority="1"><i class="fas fa-map-marker"></i> <?php $Strings->get('location'); ?></th>
|
||||
<th data-priority="2"><i class="fas fa-barcode"></i> <?php $Strings->get('code'); ?></th>
|
||||
<th data-priority="3"><i class="fas fa-hashtag"></i> <?php $Strings->get('item count'); ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
63
required.php
63
required.php
@ -62,9 +62,14 @@ if ($_SESSION['mobile'] === TRUE) {
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// List of alert messages
|
||||
require __DIR__ . '/lang/messages.php';
|
||||
// text strings (i18n)
|
||||
require __DIR__ . '/lang/' . LANGUAGE . ".php";
|
||||
require __DIR__ . '/langs/messages.php';
|
||||
|
||||
$libs = glob(__DIR__ . "/lib/*.lib.php");
|
||||
foreach ($libs as $lib) {
|
||||
require_once $lib;
|
||||
}
|
||||
|
||||
$Strings = new Strings(LANGUAGE);
|
||||
|
||||
/**
|
||||
* Kill off the running process and spit out an error message
|
||||
@ -136,60 +141,13 @@ function is_empty($str) {
|
||||
return (is_null($str) || !isset($str) || $str == '');
|
||||
}
|
||||
|
||||
/**
|
||||
* I18N string getter. If the key doesn't exist, outputs the key itself.
|
||||
* @param string $key I18N string key
|
||||
* @param boolean $echo whether to echo the result or return it (default echo)
|
||||
*/
|
||||
function lang($key, $echo = true) {
|
||||
if (array_key_exists($key, STRINGS)) {
|
||||
$str = 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I18N string getter (with builder). If the key doesn't exist, outputs the key itself.
|
||||
* @param string $key I18N 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 boolean $echo whether to echo the result or return it (default echo)
|
||||
*/
|
||||
function lang2($key, $replace, $echo = true) {
|
||||
if (array_key_exists($key, STRINGS)) {
|
||||
$str = 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;
|
||||
}
|
||||
}
|
||||
|
||||
function dieifnotloggedin() {
|
||||
if ($_SESSION['loggedin'] != true) {
|
||||
sendError("Session expired. Please log out and log in again.");
|
||||
die();
|
||||
}
|
||||
require_once __DIR__ . "/lib/login.php";
|
||||
if (account_has_permission($_SESSION['username'], "INV_VIEW") == FALSE) {
|
||||
if ((new User($_SESSION['uid']))->hasPermission("INV_VIEW") == FALSE) {
|
||||
die("You don't have permission to be here.");
|
||||
}
|
||||
}
|
||||
@ -248,8 +206,7 @@ function redirectIfNotLoggedIn() {
|
||||
header('Location: ./index.php');
|
||||
die();
|
||||
}
|
||||
require_once __DIR__ . "/lib/login.php";
|
||||
if (account_has_permission($_SESSION['username'], "INV_VIEW") == FALSE) {
|
||||
if ((new User($_SESSION['uid']))->hasPermission("INV_VIEW") == FALSE) {
|
||||
header('Location: ./index.php?permissionerror');
|
||||
die("You don't have permission to be here.");
|
||||
}
|
||||
|
6
static/css/bootstrap.min.css
vendored
6
static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* Font Awesome Free 5.1.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Font Awesome Free 5.3.1 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:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{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}
|
||||
.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}
|
4
static/js/bootstrap.min.js
vendored
4
static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
6
static/js/fontawesome-all.min.js
vendored
6
static/js/fontawesome-all.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user