1587 lines
50 KiB
PHP
1587 lines
50 KiB
PHP
<?php
|
|
/******************************************************************************
|
|
* YMS - Yacht Management Software
|
|
* Copyright (C) 2024-2026 Thomas Hooge
|
|
*
|
|
* SPDX-License-Identifier: WTFPL
|
|
******************************************************************************
|
|
|
|
******************
|
|
* Edition History
|
|
*
|
|
* # Date Changes by
|
|
* ------ ------------ ---------------------------------------------- -----
|
|
* 0.1.0 2024-03-12 Started development tho
|
|
' 0.2.0 2026-08-18 First public release tho
|
|
*
|
|
*/
|
|
|
|
// ========== CONSTANT DEFINITIONS ============================================
|
|
|
|
define ('E_NONE', 0);
|
|
|
|
define ('OPTIONAL', 0);
|
|
define ('REQUIRED', 1);
|
|
define ('ENABLED', 1);
|
|
define ('DISABLED', 0);
|
|
|
|
define ('ACT_DEFAULT', 0);
|
|
define ('ACT_ADD', 1);
|
|
define ('ACT_VIEW', 2);
|
|
define ('ACT_EDIT', 3);
|
|
define ('ACT_DELETE', 4);
|
|
define ('ACT_COPY', 5);
|
|
define ('ACT_JOIN', 6);
|
|
define ('ACT_LEAVE', 7);
|
|
define ('ACT_EDIT_DETAIL', 8);
|
|
define ('ACT_DEL_DETAIL', 9);
|
|
define ('ACT_LINK', 10);
|
|
define ('ACT_UNLINK', 11);
|
|
define ('ACT_MAIL', 12);
|
|
define ('ACT_VIEW_LIST', 13);
|
|
|
|
define('ROLE_VESSEL', 'vessel');
|
|
define('ROLE_CAPTAIN', 'captain');
|
|
define('ROLE_HELMSMAN', 'helmsman');
|
|
define('ROLE_SAILOR', 'sailor');
|
|
|
|
// ========== PAGE START CODE =================================================
|
|
|
|
// global version string
|
|
$g_version = 'v0.2.0';
|
|
|
|
$g_scriptname = basename($_SERVER['SCRIPT_NAME']);
|
|
|
|
require 'config.inc';
|
|
|
|
// Localization
|
|
setlocale(LC_ALL, $g_locale);
|
|
$g_lconv = localeconv();
|
|
$g_lang = substr($g_locale, 0, 2); // short language identifier
|
|
|
|
bindtextdomain($g_textdomain, 'locale');
|
|
bind_textdomain_codeset($g_textdomain, 'UTF-8');
|
|
textdomain($g_textdomain);
|
|
|
|
date_default_timezone_set($g_timezone);
|
|
|
|
if ($g_scriptname != 'login.php') {
|
|
session_name(APP_SESSION);
|
|
session_start();
|
|
if (!isset($_SESSION['userid'])) {
|
|
$_SESSION['prelogin'] = $_SERVER['REQUEST_URI'];
|
|
header_location('login.php');
|
|
}
|
|
$g_referrer = $_SESSION['coming_from'] ?? $g_scriptname;
|
|
$_SESSION['coming_from'] = $g_scriptname;
|
|
}
|
|
|
|
// database
|
|
try {
|
|
$pdo = new PDO("mysql:host=$g_db_host;dbname=$g_db_schema;charset=utf8mb4", $g_db_username, $g_db_password);
|
|
} catch (PDOException $e) {
|
|
echo "<!DOCTYPE html>\n";
|
|
echo '<html>';
|
|
echo '<body><pre>';
|
|
switch ($e->getCode()) {
|
|
case 2002:
|
|
echo "Unable to connect to database, perhaps host info is wrong.\n";
|
|
break;
|
|
case 1044:
|
|
case 1045:
|
|
case 1698:
|
|
// access denied to db or generic access denied
|
|
echo "Unable to connect to database, perhaps username or password is wrong.\n";
|
|
break;
|
|
case 1049:
|
|
echo "Unable to connect to database, perhaps database name is wrong.\n";
|
|
break;
|
|
default:
|
|
print_r($e);
|
|
}
|
|
echo '</pre></body>';
|
|
echo '</html>';
|
|
exit(1);
|
|
}
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
|
# $pdo->query("SET lc_time_names='de_DE'");
|
|
try {
|
|
$sth = $pdo->query("SELECT valint FROM settings WHERE userid=0 AND sno=0");
|
|
} catch (PDOException $e) {
|
|
if ($e->getCode() == 1146) {
|
|
echo '<pre>';
|
|
echo 'Settings table not found! Perhaps database is not initialized';
|
|
echo '</pre>';
|
|
exit(1);
|
|
}
|
|
}
|
|
$g_db_scheme_version = $sth->fetchColumn();
|
|
// TODO Check version
|
|
|
|
$user = User::GetInstance(); // There can be only one
|
|
if ($g_scriptname != 'login.php') {
|
|
$user->init_from_session();
|
|
$user->load_vessel();
|
|
$user->load_settings();
|
|
}
|
|
|
|
$action = NULL;
|
|
$valid = FALSE;
|
|
|
|
// document storage
|
|
$g_doc_mimetypes = ['image/png', 'image/jpeg', 'image/svg+xml', 'application/pdf'];
|
|
|
|
// form option lists
|
|
$g_opt_all = array(-2 => _('― all ―'));
|
|
$g_opt_none = array(-1 => _('― none ―'));
|
|
$g_opt_empty = array('' => _('― none ―'));
|
|
$g_opt_unknown = array(-1 => _('― unknown ―'));
|
|
|
|
// Initialize message system
|
|
$g_message = new Message;
|
|
$g_success = new MessageSuccess;
|
|
$g_warning = new MessageWarning;
|
|
$g_error = new MessageError;
|
|
|
|
// ========== MESSAGE FUNCTIONS ===============================================
|
|
|
|
class Message {
|
|
|
|
var $count = 0;
|
|
var $text = array();
|
|
var $caption;
|
|
var $alertclass;
|
|
|
|
function __construct() {
|
|
$this->caption = _('Information');
|
|
$this->alertclass = 'alert-info';
|
|
if (isset($_SESSION['message']['info'])) {
|
|
$this->Add($_SESSION['message']['info']);
|
|
unset($_SESSION['message']['info']);
|
|
}
|
|
}
|
|
|
|
function SetCaption($str) {
|
|
$this->caption = $str;
|
|
}
|
|
|
|
function Add($msg) {
|
|
$this->count++;
|
|
$this->text[$this->count] = $msg;
|
|
}
|
|
|
|
function GetCount() {
|
|
return $this->count;
|
|
}
|
|
|
|
function PrintOut() {
|
|
if ($this->count > 0) {
|
|
echo '<div class="alert ', $this->alertclass, ' alert-dismissible fade show" role="alert">', "\n";
|
|
echo '<h4 class="alert-heading">', $this->caption, "</h4>\n";
|
|
echo "<ul>\n";
|
|
for ($i=1; $i<=$this->count; $i++) {
|
|
echo "\t<li>", $this->text[$i],"</li>\n";
|
|
}
|
|
echo "</ul>\n";
|
|
echo '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>', "\n";
|
|
echo "</div>\n";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class MessageSuccess extends Message {
|
|
function __construct() {
|
|
$this->caption = _('Success');
|
|
$this->alertclass = 'alert-success';
|
|
if (isset($_SESSION['message']['succ'])) {
|
|
$this->Add($_SESSION['message']['succ']);
|
|
unset($_SESSION['message']['succ']);
|
|
}
|
|
}
|
|
}
|
|
|
|
class MessageWarning extends Message {
|
|
function __construct() {
|
|
$this->caption = _('Warning');
|
|
$this->alertclass = 'alert-warning';
|
|
if (isset($_SESSION['message']['warn'])) {
|
|
$this->Add($_SESSION['message']['warn']);
|
|
unset($_SESSION['message']['warn']);
|
|
}
|
|
}
|
|
}
|
|
|
|
class MessageError extends Message {
|
|
function __construct() {
|
|
$this->caption = _('Error');
|
|
$this->alertclass = 'alert-danger';
|
|
if (isset($_SESSION['message']['err'])) {
|
|
$this->Add($_SESSION['message']['err']);
|
|
unset($_SESSION['message']['err']);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========== USER FUNCTIONS ==================================================
|
|
|
|
class User {
|
|
|
|
private static $instance = NULL;
|
|
|
|
private $loggedin;
|
|
private $errormessage;
|
|
|
|
public $id;
|
|
public $login;
|
|
public $displayname;
|
|
public $role;
|
|
public $flags;
|
|
public $vid; // current selected vessel
|
|
public $vessel; // name of current vessel
|
|
public $datefmt;
|
|
public $rows_pp;
|
|
public $menu;
|
|
|
|
private function __construct() {
|
|
global $g_rows_pp;
|
|
$this->loggedin = FALSE;
|
|
$this->errormessage = NULL;
|
|
$this->flags = [];
|
|
$this->vid = 1;
|
|
$this->vessel = 'Yacht';
|
|
$this->datefmt = 'Y-m-d';
|
|
$this->rows_pp = $g_rows_pp;
|
|
$this->menu = [1, 2, 3, 4, 5, 6];
|
|
}
|
|
|
|
public static function getInstance() {
|
|
if (self::$instance == NULL) {
|
|
self::$instance = new User();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
function login($user_name, $user_pass) {
|
|
global $pdo;
|
|
if ($user_name == '' or $user_pass == '') {
|
|
$this->loggedin = FALSE;
|
|
$this->errormessage = _('Username or password was empty');
|
|
return FALSE;
|
|
}
|
|
$sql = "SELECT userid, pass, displayname, role, flags FROM user WHERE login=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$user_name]);
|
|
$row = $sth->fetch(PDO::FETCH_OBJ);
|
|
$this->role = $row->role;
|
|
$this->flags = array_filter(explode(',', $row->flags));
|
|
if (($row->flags != 0) and ($row->flags != '')) {
|
|
// deleted or locked, intentionally not return detailed reason
|
|
$this->errormessage = _('User account does not exist');
|
|
} elseif (password_verify($user_pass, $row->pass)) {
|
|
// password check successful
|
|
$_SESSION['userid'] = $row->userid;
|
|
$this->loggedin = TRUE;
|
|
$target = $_SESSION['prelogin'] ?? 'index.php';
|
|
unset($_SESSION['prelogin']);
|
|
header_location($target);
|
|
} else {
|
|
$this->errormessage = _('Username or password invalid');
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
function logout() {
|
|
global $pdo;
|
|
// remove session completely
|
|
$_SESSION = array();
|
|
if (ini_get("session.use_cookies")) {
|
|
$params = session_get_cookie_params();
|
|
setcookie(session_name(), '', time() - 42000,
|
|
$params["path"], $params["domain"],
|
|
$params["secure"], $params["httponly"]
|
|
);
|
|
}
|
|
session_destroy();
|
|
}
|
|
|
|
function init_from_session() {
|
|
global $pdo;
|
|
if (!isset($_SESSION['userid'])) {
|
|
$this->loggedin = FALSE;
|
|
$this->errormessage = _('User not logged in');
|
|
$this->id = NULL;
|
|
return;
|
|
}
|
|
|
|
$this->id = $_SESSION['userid'];
|
|
$sql = "SELECT displayname, role FROM user WHERE userid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$this->id]);
|
|
$row = $sth->fetch();
|
|
if ($row) {
|
|
$this->displayname = $row['displayname'];
|
|
$this->role = $row['role'];
|
|
}
|
|
}
|
|
|
|
function load_vessel() {
|
|
global $pdo;
|
|
$sql = "SELECT v.vid, v.vesselname "
|
|
. "FROM settings AS s INNER JOIN vessel AS v ON s.valint=v.vid "
|
|
. "WHERE s.userid=? and s.sno=1";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$this->id]);
|
|
$row = $sth->fetch();
|
|
if ($row) {
|
|
$this->vid = $row['vid'];
|
|
$this->vessel = $row['vesselname'];
|
|
}
|
|
}
|
|
|
|
function load_settings() {
|
|
global $pdo;
|
|
// additional user settings
|
|
// date format from user settings
|
|
$sql = "SELECT valstr FROM settings WHERE userid=? AND sno=2";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$this->id]);
|
|
$user_datefmt = $sth->fetchColumn();
|
|
if ($user_datefmt) {
|
|
$this->datefmt = $user_datefmt;
|
|
}
|
|
// pagination rows from user settings
|
|
$sql = "SELECT valint FROM settings WHERE userid=? AND sno=10";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$this->id]);
|
|
$user_rows_pp = $sth->fetchColumn();
|
|
if ($user_rows_pp) {
|
|
$this->rows_pp = $user_rows_pp;
|
|
}
|
|
// user defined menu
|
|
$sql = "SELECT valstr FROM settings WHERE userid=? AND sno=3";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$this->id]);
|
|
$res = $sth->fetchColumn();
|
|
if ($res === false) {
|
|
$sql = "INSERT INTO settings (userid, sno, valstr) VALUES (?, 3, ?)";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$this->id, implode(',', $this->menu)]);
|
|
} else {
|
|
$this->menu = array_map('intval', explode(',', $res));
|
|
}
|
|
}
|
|
|
|
function get_last_error() {
|
|
return $this->errormessage;
|
|
}
|
|
|
|
}
|
|
|
|
function get_userlist() {
|
|
// Get list of all users for fast name lookup
|
|
global $pdo;
|
|
$ulist = array(-1 => ['-', 0]); // unknown
|
|
$sql = "SELECT userid, displayname, role, flags FROM user ORDER BY displayname";
|
|
$sth = $pdo->query($sql);
|
|
$res = $sth->fetchAll();
|
|
foreach ($res as $row) {
|
|
$flags = array_filter(explode(',', $row['flags']));
|
|
$deleted = in_array('deleted', $flags);
|
|
$locked = in_array('locked', $flags);
|
|
$ulist[$row['userid']] = [$row['displayname'], $deleted, $locked];
|
|
}
|
|
return $ulist;
|
|
}
|
|
|
|
// ========== DATABASE FUNCTIONS ==============================================
|
|
|
|
function db_exec_insert($table, $params) {
|
|
// $params must have keys corresponding to table fields prefixed with ':'
|
|
// e.g. $param[':field']
|
|
global $pdo;
|
|
global $g_error;
|
|
$keys = array_keys($params);
|
|
$fields = join(', ', array_map(function($s) { return ltrim($s, ':'); }, $keys));
|
|
$pnames = join(', ', $keys);
|
|
$sql = "INSERT INTO $table ($fields) VALUES ($pnames)";
|
|
// $sth = $pdo->prepare("INSERT INTO $table ($fields) VALUES ($pnames)");
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute($params);
|
|
} catch (PDOexception $e) {
|
|
$g_error->Add('SQL-Error: '. $e->getMessage());
|
|
$g_error->Add($sql);
|
|
$g_error->Add(print_r($params, true));
|
|
return false;
|
|
}
|
|
return $pdo->LastInsertId();
|
|
}
|
|
|
|
function db_exec_update($table, $params, $pk) {
|
|
// primary key can be a single field name or multiple fields: $pk is an array
|
|
global $pdo;
|
|
global $g_error;
|
|
// remove primary key fields from list of fields to update
|
|
if (is_array($pk)) {
|
|
if (count($pk) == 1) {
|
|
$pk = $pk[0];
|
|
$fields = $params;
|
|
unset($fields[':'.$pk]);
|
|
$where = "$pk=:$pk";
|
|
} else {
|
|
$pkparams = array_map(fn($value) => ':' . $value, $pk);
|
|
$fields = array_diff_key($params, array_flip($pkparams));
|
|
$where = join(' AND ', array_map(fn($s) => "$s=:$s", $pk));
|
|
}
|
|
} else {
|
|
$fields = $params;
|
|
unset($fields[':'.$pk]);
|
|
$where = "$pk=:$pk";
|
|
}
|
|
$fieldlist = join(', ', array_map(fn($s) => ltrim($s, ':') . '=' . $s, array_keys($fields)));
|
|
$sql = "UPDATE $table SET $fieldlist WHERE $where";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute($params);
|
|
} catch (PDOexception $e) {
|
|
$g_error->Add('SQL-Error: '. $e->getMessage());
|
|
$g_error->Add($sql);
|
|
$g_error->Add(print_r($params, true));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function db_exec_delete($table, $condition, $params) {
|
|
// WIP untested!
|
|
// condition contains the where part of the sql statement
|
|
// params are values for placeholders in condition
|
|
// returns the number of deleted records
|
|
global $pdo;
|
|
global $g_error;
|
|
// convert params to array if single value is given
|
|
if (!is_array($params)) {
|
|
$params = [$params];
|
|
}
|
|
$sql = "DELETE FROM $table";
|
|
if (!empty($condition)) {
|
|
$sql .= ' WHERE ' . $condition;
|
|
}
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute($params);
|
|
} catch (PDOexception $e) {
|
|
$g_error->Add('SQL-Error: '. $e->getMessage());
|
|
$g_error->Add($sql);
|
|
$g_error->Add(print_r($params, true));
|
|
return -1;
|
|
}
|
|
return $sth->rowCount();
|
|
}
|
|
|
|
function db_clear_filter($sno) {
|
|
global $user;
|
|
global $pdo;
|
|
$sql = "DELETE FROM settings WHERE userid=? AND sno=?";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$user->id, $sno]);
|
|
} catch (PDOexception $e) {
|
|
$g_warning->Add('SQL-Error: '. $e->getMessage());
|
|
$g_warning->Add($sql);
|
|
$g_warning->Add('Setting: ', $sno);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function db_save_filter($flt, $sno) {
|
|
global $user;
|
|
global $pdo;
|
|
global $g_warning;
|
|
$sql = "UPDATE settings SET valstr=? WHERE userid=? AND sno=?";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([json_encode($flt), $user->id, $sno]);
|
|
} catch (PDOexception $e) {
|
|
$g_warning->Add('SQL-Error: '. $e->getMessage());
|
|
$g_warning->Add($sql);
|
|
$g_warning->Add(print_r($flt, true));
|
|
$g_warning->Add('Setting: ', $sno);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function get_enum($enum) {
|
|
// translate and beautify enums
|
|
$lookup = array(
|
|
'NULL' => _('n/a'),
|
|
'active' => _('Active'),
|
|
'bad' => _('Bad'),
|
|
'box' => _('Box'),
|
|
'cable' => _('Cable'),
|
|
'cat' => _('Catamaran'),
|
|
'completed' => _('Completed'),
|
|
'closed' => _('Closed'),
|
|
'company' => _('Company'),
|
|
'defect' => _('Defect'),
|
|
'deleted' => _('Deleted'),
|
|
'done' => _('Done'),
|
|
'drawing' => _('Drawing'),
|
|
'equipment' => _('Equipment'),
|
|
'estimated' => _('Estimated'),
|
|
'excellent' => _('Excellent'),
|
|
'fair' => _('Fair'),
|
|
'finished' => _('Finished'),
|
|
'fixed' => _('Fixed'),
|
|
'fuse' => _('Fuse'),
|
|
'generic' => _('Generic'),
|
|
'good' => _('Good'),
|
|
'high' => _('High'),
|
|
'historic' => _('Historic'),
|
|
'invoice' => _('Invoice'),
|
|
'locked' => _('Locked'),
|
|
'low' => _('Low'),
|
|
'manual' => _('Manual'),
|
|
'medium' => _('Medium'),
|
|
'mono' => _('Monohull'),
|
|
'new' => _('New'),
|
|
'none' => _('None'),
|
|
'normal' => _('Normal'),
|
|
'ongoing' => _('Ongoing'),
|
|
'open' => _('Open'),
|
|
'ordered' => _('Ordered'),
|
|
'paused' => _('Paused'),
|
|
'pending' => _('Pending'),
|
|
'picture' => _('Picture'),
|
|
'planned' => _('Planned'),
|
|
'precise' => _('Precise'),
|
|
'project' => _('Project'),
|
|
'removed' => _('Removed'),
|
|
'repairable' => _('Repairable'),
|
|
'rough' => _('Rough'),
|
|
'storage' => _('Storage'),
|
|
'task' => _('Task'),
|
|
'tri' => _('Trimaran'),
|
|
'unknown' => _('Unknown'),
|
|
'user' => _('User'),
|
|
'vessel' => _('Vessel'),
|
|
'waiting' => _('waiting')
|
|
);
|
|
return $lookup[$enum] ?? $enum;
|
|
}
|
|
|
|
function db_load_enum($table, $column, $lookup=false, $as_dict=false, $check_null=false) {
|
|
// returns array of enum-values as defined in database
|
|
// if lookup is true the name lookup and translaton is applied
|
|
// if as_dict is true the enum value is returned as the array key
|
|
// if check_null is true the nullable-check is applied and returned
|
|
// works also for set datatype
|
|
global $pdo;
|
|
$sql = "SELECT TRIM(TRAILING ')' FROM SUBSTRING(column_type,6)), is_nullable "
|
|
. "FROM information_schema.columns "
|
|
. "WHERE table_schema=DATABASE() AND table_name=? AND column_name=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$table, $column]);
|
|
$row = $sth->fetch(PDO::FETCH_NUM);
|
|
// for PHP < 7.4
|
|
// return array_map(function($x) { return trim($x, "'"); }, explode(',', $row[0]));
|
|
// for PHP => 7.4
|
|
$arr = array_map(fn($x) => trim($x, "'"), explode(',', $row[0]));
|
|
// prepend special value NULL if column is nullable
|
|
if ($check_null and $row[1] == 'YES') {
|
|
array_unshift($arr, 'NULL');
|
|
}
|
|
if ($lookup) {
|
|
if ($as_dict) {
|
|
$dict = array();
|
|
foreach ($arr as $x) {
|
|
$dict[$x] = get_enum($x);
|
|
}
|
|
return $dict;
|
|
} else {
|
|
return array_map(fn($x) => get_enum($x), $arr);
|
|
}
|
|
} else {
|
|
return $arr;
|
|
}
|
|
}
|
|
|
|
function db_load_taglist($objtype, $objid) {
|
|
// returns comma separated list for object
|
|
global $pdo;
|
|
global $g_error;
|
|
$taglist = '';
|
|
$sql = "SELECT GROUP_CONCAT(tagname) "
|
|
. "FROM tagref JOIN tag USING (tagid) "
|
|
. "WHERE objtype=? AND objid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$objtype, $objid]);
|
|
$taglist = $sth->fetchColumn();
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
}
|
|
return $taglist;
|
|
}
|
|
|
|
function db_save_taglist($objtype, $objid, $taglist) {
|
|
global $pdo;
|
|
global $g_error;
|
|
// WIP
|
|
// - taglist is comma separated string
|
|
// - create not existing tags
|
|
// - update tagref: create new, remove unused
|
|
$tags = array_filter(explode(',', preg_replace('/\s+/u', '', $taglist)));
|
|
if (empty($tags)) {
|
|
// remove all from tagref
|
|
$sth = $pdo->prepare("DELETE FROM tagref WHERE objtype=? AND objid=?");
|
|
try {
|
|
$sth->execute([$objtype, $objid]);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
}
|
|
} else {
|
|
// get list of non existing tags
|
|
$in = str_repeat('?,', count($tags) - 1) . '?';
|
|
$sql = "SELECT tagid, tagname FROM tag WHERE tagname IN ($in)";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute(array_values($tags));
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
}
|
|
$tagids = array();
|
|
$existing = array();
|
|
foreach ($sth->fetchAll() as $row) {
|
|
$existing[] = $row['tagname'];
|
|
$tagids[] = $row['tagid'];
|
|
}
|
|
$newtags = array_diff($tags, $existing);
|
|
$sth = $pdo->prepare("INSERT INTO tag (tagname) VALUES (?)");
|
|
foreach ($newtags as $t) {
|
|
// maxlength 20 for each tag!
|
|
$sth->execute([substr($t, 0, 20)]);
|
|
$tagids[] = $pdo->LastInsertId();
|
|
}
|
|
// update references
|
|
$sql = "SELECT tagid FROM tagref WHERE objtype=? AND objid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$objtype, $objid]);
|
|
$refids = array();
|
|
$obsolete = array();
|
|
foreach ($sth->fetchAll() as $row) {
|
|
if (in_array($row['tagid'], $tagids)) {
|
|
// mark existing used references
|
|
$refids[] = $row['tagid'];
|
|
} else {
|
|
// obsolete reference
|
|
$obsolete[] = $row['tagid'];
|
|
}
|
|
}
|
|
// create missing references
|
|
$missing = array_diff($tagids, $refids);
|
|
if (! empty($missing)) {
|
|
$sql = "INSERT INTO tagref (tagid, objid, objtype) VALUES (?, ?, ?)";
|
|
$sth = $pdo->prepare($sql);
|
|
foreach ($missing as $m) {
|
|
$sth->execute([$m, $objid, $objtype]);
|
|
}
|
|
}
|
|
// remove obsolete references
|
|
if (! empty($obsolete)) {
|
|
$sql = "DELETE FROM tagref WHERE tagid=? AND objid=? and objtype=?";
|
|
$sth = $pdo->prepare($sql);
|
|
foreach ($obsolete as $o) {
|
|
$sth->execute([$o, $objid, $objtype]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function db_get_tag_target($objtype, $objid) {
|
|
// get page name and desctiption e.g. for link creation
|
|
global $pdo;
|
|
global $g_error;
|
|
switch ($objtype) {
|
|
case 'equip':
|
|
$sql = "SELECT ename FROM equipment WHERE eid=?";
|
|
$target = "equipment.php";
|
|
break;
|
|
case 'inv':
|
|
$sql = "SELECT invname FROM inventory WHERE invid=?";
|
|
$target = "inventory.php";
|
|
break;
|
|
case 'prov':
|
|
$sql = "SELECT provname FROM provisions WHERE provvid=?";
|
|
$target = "provisions.php";
|
|
break;
|
|
case 'doc':
|
|
$sql = "SELECT title FROM document WHERE docid=?";
|
|
$target = "documents.php";
|
|
break;
|
|
case 'proj':
|
|
$sql = "SELECT projname FROM project WHERE projid=?";
|
|
$target = "projects.php";
|
|
break;
|
|
case 'task':
|
|
$sql = "SELECT taskname FROM task WHERE taskid=?";
|
|
$target = "task.php";
|
|
break;
|
|
case 'maint':
|
|
$sql = "SELECT activities FROM maintenance WHERE maintid=?";
|
|
$target = 'maintenance.php';
|
|
break;
|
|
}
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$objid]);
|
|
$desc = $sth->fetchColumn();
|
|
return [$target, $desc];
|
|
}
|
|
|
|
function db_get_ddtext($ddid, $ddval) {
|
|
// returns single value for dropdown
|
|
global $pdo;
|
|
global $g_error;
|
|
$sql = "SELECT ddtext FROM dropdown WHERE ddid=? AND ddval=?";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$ddid, $ddval]);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
return '#err';
|
|
}
|
|
return $sth->fetchColumn();
|
|
}
|
|
|
|
// Functions to get option lists (key/value)
|
|
|
|
function db_get_options($ddid, $orderby = '', $short = False, $default = NULL) {
|
|
// returns list for dropdown
|
|
// if short is set the short value will be returned
|
|
global $pdo;
|
|
$list = array();
|
|
if ($default != NULL) {
|
|
$list[0] = $default;
|
|
}
|
|
if ($short) {
|
|
$sql = "SELECT ddval, ddshort";
|
|
} else {
|
|
$sql = "SELECT ddval, ddtext";
|
|
}
|
|
$sql .= " FROM dropdown WHERE ddid=?";
|
|
if ($orderby == 'ddtext') {
|
|
$sql .= ' ORDER BY ddtext';
|
|
} elseif ($orderby == 'sort') {
|
|
$sql .= ' ORDER BY sort';
|
|
} else {
|
|
$sql .= ' ORDER BY ddval';
|
|
}
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$ddid]);
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $rec) {
|
|
$list[$rec[0]] = $rec[1];
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_opt_vessel() {
|
|
global $pdo;
|
|
$list = array();
|
|
$sth = $pdo->query("SELECT vid, vesselname, model FROM vessel ORDER BY vid");
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $row) {
|
|
$list[$rec[0]] = $rec[1];
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_opt_storage($vid) {
|
|
global $pdo;
|
|
global $g_error;
|
|
$list = array();
|
|
// exclude storage type "tank" from list
|
|
$sql = "SELECT sid, sname FROM storage WHERE vid=? AND stype<>1 ORDER BY sname";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$vid]);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
}
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $rec) {
|
|
$list[$rec[0]] = $rec[1];
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_opt_box($vid, $exclude=[]) {
|
|
global $pdo;
|
|
global $g_error;
|
|
$list = array();
|
|
$sql = "SELECT boxid, label, content "
|
|
. "FROM box INNER JOIN storage USING (sid) "
|
|
. "WHERE vid=? "
|
|
. "ORDER BY label";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$vid]);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
}
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $rec) {
|
|
if (! in_array($rec[0], $exclude)) {
|
|
$list[$rec[0]] = $rec[1] . ($rec[2] ? ' - '. $rec[2] : '');
|
|
}
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_opt_equip($vid, $default=NULL, $exclude=[]) {
|
|
global $pdo;
|
|
global $g_error;
|
|
if (isset($default)) {
|
|
$list = $default;
|
|
} else {
|
|
$list = array();
|
|
}
|
|
$sql = "SELECT eid, ename FROM equipment WHERE vid=? ORDER BY ename";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$vid]);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
}
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $rec) {
|
|
if (! in_array($rec[0], $exclude)) {
|
|
$list[$rec[0]] = $rec[1];
|
|
}
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_opt_manuf($default=NULL) {
|
|
global $pdo;
|
|
if (isset($default)) {
|
|
$list = $default;
|
|
} else {
|
|
$list = array();
|
|
}
|
|
$sql = "SELECT compid, compname FROM company WHERE comptype=2 ORDER BY compname";
|
|
$sth = $pdo->query($sql);
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $row) {
|
|
$list[$row[0]] = $row[1];
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_opt_supp($default=NULL) {
|
|
global $pdo;
|
|
if (isset($default)) {
|
|
$list = $default;
|
|
} else {
|
|
$list = array();
|
|
}
|
|
$sql = "SELECT compid, compname FROM company WHERE comptype=1 ORDER BY compname";
|
|
$sth = $pdo->query($sql);
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $row) {
|
|
$list[$row[0]] = $row[1];
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_opt_user($userid, $default=NULL) {
|
|
global $pdo;
|
|
if (isset($default)) {
|
|
$list = $default;
|
|
} else {
|
|
$list = array();
|
|
}
|
|
$sql = "SELECT userid, displayname FROM user WHERE userid>0 ORDER BY displayname";
|
|
$sth = $pdo->query($sql);
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $row) {
|
|
$list[$row[0]] = $row[1];
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_opt_proj($vid, $exclude=[], $default=NULL) {
|
|
// exclude is a list of project states
|
|
global $pdo;
|
|
global $g_error;
|
|
if (isset($default)) {
|
|
$list = $default;
|
|
} else {
|
|
$list = array();
|
|
}
|
|
$sql = "SELECT projid, projname FROM project WHERE vid=?";
|
|
$where = [];
|
|
foreach ($exclude as $excl) {
|
|
$where[] = "NOT FIND_IN_SET('$excl',projstate)";
|
|
}
|
|
if (! empty($where)) {
|
|
$sql .= ' AND '. implode(' AND ', $where);
|
|
}
|
|
$sql .= " ORDER BY projname";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$vid]);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
}
|
|
foreach ($sth->fetchAll(PDO::FETCH_NUM) as $rec) {
|
|
$list[$rec[0]] = $rec[1];
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
function db_get_filter($user, $sno, $fields = []) {
|
|
// if fields supplied empting missing field are created
|
|
global $pdo;
|
|
global $g_warning;
|
|
$flt = new stdClass();
|
|
$sth = $pdo->prepare("SELECT valstr FROM settings WHERE userid=? AND sno=?");
|
|
try {
|
|
$sth->execute([$user, $sno]);
|
|
$json = $sth->fetchColumn();
|
|
if ($json === false) {
|
|
// no settings record. create empty one for later updates
|
|
$sth = $pdo->prepare("INSERT INTO settings (userid, sno, valstr) VALUES (?, ?, '{}')");
|
|
$sth->execute([$user, $sno]);
|
|
} else {
|
|
$flt = json_decode($json);
|
|
}
|
|
// create missing fields
|
|
foreach ($fields as $f) {
|
|
if (!property_exists($flt, $f)) {
|
|
$flt->$f = null;
|
|
}
|
|
}
|
|
} catch (PDOexception $e) {
|
|
$g_warning->Add('SQL-Error: '. $e->getMessage());
|
|
$g_warning->PrintOut();
|
|
}
|
|
return $flt;
|
|
}
|
|
|
|
function db_get_default_storage($vesselid, $fallback=true) {
|
|
global $pdo;
|
|
global $g_error, $g_warning;
|
|
$sql = "SELECT sid_default FROM vessel WHERE vid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$vesselid]);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
return false;
|
|
}
|
|
$sid = $sth->fetchColumn();
|
|
if ($sid == NULL && $fallback) {
|
|
// try to get another storage as last fallback
|
|
$g_warning->Add(_('No default storage defined for current vessel'));
|
|
$sql = "SELECT MIN(sid), sname FROM storage WHERE vid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute([$vesselid]);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
return false;
|
|
}
|
|
$row = $sth->fetch(PDO::FETCH_NUM);
|
|
if ($row[0] == NULL) {
|
|
$g_error->Add('Error: No storage defined for current vessel');
|
|
return false;
|
|
}
|
|
$sid = $row[0];
|
|
$g_warning->Add(sprintf(_("Selected fallback storage '%s'"), $row[1]));
|
|
}
|
|
return $sid;
|
|
}
|
|
|
|
function date_to_sql($var) {
|
|
if (!isset($var) || $var == '') {
|
|
return NULL;
|
|
} else {
|
|
list($d, $m, $y) = explode('.', $var);
|
|
return sprintf("%04d-%02d-%02d", $y, $m, $d);
|
|
}
|
|
}
|
|
|
|
function float_to_sql($var) {
|
|
global $g_lconv;
|
|
if (!isset($var) || $var == '') {
|
|
return NULL;
|
|
} else {
|
|
// remove thousands separator
|
|
$var = str_replace($g_lconv['thousands_sep'], '', $var);
|
|
// force decimal point
|
|
if ($g_lconv['decimal_point'] != '.') {
|
|
$var = str_replace($g_lconv['decimal_point'], '.', $var);
|
|
}
|
|
return $var;
|
|
}
|
|
}
|
|
|
|
// ========== FORM FUNCTIONS ==================================================
|
|
|
|
function form_get_action() {
|
|
if (!isset($_POST['submit'])) {
|
|
if (isset($_GET['f'])) {
|
|
$submit = $_GET['f'];
|
|
// strip parameters
|
|
/* $pos = strpos($submit, '?');
|
|
if ($pos > 2) {
|
|
$submit = substr($submit, 0, $pos);
|
|
} */
|
|
} else {
|
|
$submit = NULL;
|
|
}
|
|
} else {
|
|
$submit = $_POST['submit'];
|
|
}
|
|
if (is_array($submit)) {
|
|
$submit = key($submit);
|
|
}
|
|
return strtolower($submit);
|
|
}
|
|
|
|
function form_create_input($fieldname, $label, $value) {
|
|
// TODO WIP
|
|
echo '<div class="mb-3">', "\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_create_text($fieldname, $label, $value) {
|
|
// TODO WIP
|
|
echo '<div class="mb-3">', "\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_create_check($fieldname, $label, $checked) {
|
|
// create single checkbox with assigned label
|
|
echo '<div class="mb-3">', "\n";
|
|
echo '<input type="checkbox" class="form-check-input" name="', $fieldname, '" id="', $fieldname, '"';
|
|
if ($checked) {
|
|
echo ' checked';
|
|
}
|
|
echo ">\n";
|
|
echo '<label for="', $fieldname, '">', $label, "</label>\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_create_select($fieldname, $label, $optlist, $selval=NULL, $multiple=FALSE) {
|
|
echo '<div class="mb-3">', "\n";
|
|
echo '<label for="', $fieldname, '">', $label, "</label>\n";
|
|
echo '<select class="form-select" name="', $fieldname,'" id="', $fieldname, $multiple ? ' multiple' : '', '">', "\n";
|
|
foreach ($optlist as $k => $v) {
|
|
echo '<option value="', $k, '"';
|
|
if (isset($selval) and ($k == $selval)) {
|
|
echo ' selected';
|
|
}
|
|
echo '>', $v, "</option>\n";
|
|
}
|
|
echo "</select>\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_create_checks($fieldname, $label, $optlist, $selected = []) {
|
|
// create multiple combined checkboxes with title
|
|
echo '<fieldset class="mb-3">';
|
|
echo '<legend class="fs-6 mb-1 border-bottom pb-1">', $label, "</legend>\n";
|
|
echo '<div class="d-flex flex-wrap gap-3">';
|
|
foreach ($optlist as $k => $v) {
|
|
echo '<div class="form-check">';
|
|
$checked = in_array($k, $selected, true) ? ' checked' : '';
|
|
echo '<label class="form-check-label">';
|
|
echo '<input type="checkbox" class="form-check-input" name="', $fieldname, '[', $k, ']"', $checked, '>';
|
|
echo $v;
|
|
echo '</label>';
|
|
echo '</div>';
|
|
}
|
|
echo '</div>';
|
|
echo '</fieldset>';
|
|
}
|
|
|
|
// TODO DEPRECATED for touch multiple adjacent buttuns are not well suited.
|
|
function form_action_buttons($script, $id) {
|
|
echo "<td>";
|
|
echo '<a title="', _('View'), '" href="', $script, '?f=view&id=', $id, '"><i class="bi-eye"></i></a>', "\n";
|
|
echo '<a title="', _('Edit'), '" href="', $script, '?f=edit&id=', $id, '"><i class="bi-pencil"></i></a>', "\n";
|
|
echo "</td>\n";
|
|
}
|
|
|
|
function form_add_button($script, $id=NULL, $idname='id') {
|
|
echo '<div class="container-fluid px-0 my-3">', "\n";
|
|
echo '<a href="', $script, '?f=add';
|
|
if (isset($id)) {
|
|
echo '&'.$idname.'='.$id;
|
|
}
|
|
echo '" class="btn btn-primary" role="button">', _('Add'), "</a>\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_view_buttons($script, $id, $backlink=NULL) {
|
|
echo '<div class="container-fluid px-0 my-3">', "\n";
|
|
echo '<a href="', $script, '?f=edit&id=', $id, '" class="btn btn-primary" role="button">', _('Edit'), "</a>\n";
|
|
echo ' ';
|
|
echo '<a href="', $script, '?f=del&id=', $id, '" class="btn btn-secondary" role="button">', _('Delete'), "</a>\n";
|
|
echo ' ';
|
|
echo '<a href="', $backlink ?? $script, '" class="btn btn-secondary">', _('Back'), "</a>\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_new_buttons($script) {
|
|
echo '<div class="container-fluid px-0 my-3">', "\n";
|
|
echo '<button type="submit" name="submit[insert]" class="btn btn-primary">', _('Save'), "</button>\n";
|
|
echo '<a href="', $script, '" class="btn btn-secondary">', _('Back'), "</a>\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_edit_buttons($script, $id) {
|
|
echo '<div class="container-fluid px-0 my-3">', "\n";
|
|
echo '<button type="submit" name="submit[update]" class="btn btn-primary">', _('Save'), "</button>\n";
|
|
echo '<a href="', $script, '?f=view&id=', $id, '" class="btn btn-secondary">', _('Back'), "</a>\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_delete_buttons($script, $id, $token) {
|
|
echo '<div class="container-fluid px-0 my-3">', "\n";
|
|
echo '<form method="post" action="', $script, '">', "\n";
|
|
echo '<input type="hidden" name="id" value="', $id .'">', "\n";
|
|
echo '<input type="hidden" name="token" value="', $token, '">', "\n";
|
|
echo '<button type="submit" name="submit[delete]" class="btn btn-primary">', _('Delete'), "</button>\n";
|
|
echo '<a href="', $script, '?f=view&id=', $id, '" class="btn btn-secondary">', _('Back'), "</a>\n";
|
|
echo "</form>\n";
|
|
echo "</div>\n";
|
|
}
|
|
|
|
function form_tag_assignment($script, $id, $tags) {
|
|
echo '<tr>';
|
|
echo '<th>', _('Tags'), "</th>\n";
|
|
echo '<td>', $tags, '</td>';
|
|
echo "</tr>\n<tr>";
|
|
echo '<th>', _('Tag assignment'), "</th>\n";
|
|
echo '<td><form method="post" action="', $script, '">', "\n";
|
|
echo '<div class="form-floating">';
|
|
echo '<input type="hidden" name="id" value="', $id .'">', "\n";
|
|
echo '<label>', _('(Separate by comma)'), '</label>', "\n";
|
|
echo '<input type="text" maxlength="80" class="form-control" name="tagstring" value="', $tags, '">', "\n";
|
|
echo '<input type="submit">', "\n";
|
|
echo "</div></form></td></td>\n";
|
|
echo "</tr>";
|
|
}
|
|
|
|
function filter_create_select($fieldname, $label, $optlist, $selval=NULL, $multiple=FALSE) {
|
|
// - Wenn multiple gesetzt ist, muß selval ein array sein
|
|
// ansonsten ein einfacher String
|
|
/* if ($multiple) {
|
|
if (!is_array($selval)) {
|
|
}
|
|
} */
|
|
echo '<div class="d-flex flex-nowrap gap-2">', "\n";
|
|
echo '<label for="', $fieldname,'">', $label,'</label>', "\n";
|
|
echo '<select class="form-select" id="', $fieldname, '" name="', $fieldname, '"', $multiple ? ' multiple' : '', ">\n";
|
|
foreach ($optlist as $k => $v) {
|
|
echo '<option value="', $k, '"';
|
|
if ($k == $selval) {
|
|
echo ' selected';
|
|
}
|
|
echo '>', $v, '</option>', "\n";
|
|
}
|
|
echo "</select>\n";
|
|
echo "</div>";
|
|
}
|
|
|
|
function filter_create_checks($fieldname, $label, $optlist, $selected = []) {
|
|
// TODO WIP
|
|
echo '<fieldset class="mb-3">';
|
|
echo '<legend class="fs-6 mb-1 border-bottom pb-1">', $label, "</legend>\n";
|
|
echo '<div class="d-flex flex-wrap gap-3">';
|
|
foreach ($optlist as $k => $v) {
|
|
echo '<div class="form-check">';
|
|
$checked = in_array($k, $selected, true) ? ' checked' : '';
|
|
echo '<label class="form-check-label">';
|
|
echo '<input type="checkbox" class="form-check-input" name="', $fieldname, '[', $k, ']"', $checked, '>';
|
|
echo $v;
|
|
echo '</label>';
|
|
echo '</div>';
|
|
}
|
|
echo '</div>';
|
|
echo '</fieldset>';
|
|
}
|
|
|
|
// ========== FORMAT FUNCTIONS ================================================
|
|
|
|
function format_float($val, $decimals = 2, $suffix = '') {
|
|
global $g_lconv;
|
|
if (!isset($val) || $val == '') {
|
|
return '';
|
|
}
|
|
return number_format($val, $decimals, $g_lconv['decimal_point'], $g_lconv['thousands_sep']) . ($suffix ? " $suffix" : '');
|
|
}
|
|
|
|
function format_currency($val) {
|
|
global $g_lconv;
|
|
if (!isset($val) || $val == '') {
|
|
return '';
|
|
}
|
|
if ($g_lconv['currency_symbol'] == '$') {
|
|
return '$'.number_format($val, 2, $g_lconv['decimal_point'], $g_lconv['thousands_sep']);
|
|
} else {
|
|
return number_format($val, 2, $g_lconv['decimal_point'], $g_lconv['thousands_sep']) . ' ' . $g_lconv['currency_symbol'];
|
|
}
|
|
}
|
|
|
|
function format_color($color) {
|
|
$colstr = '<span style="';
|
|
$style = "padding:2px 4px;border-radius:4px;background-color:#".$color;
|
|
if (get_color_brightness($color) < 0.4) {
|
|
$style .= ';color:white';
|
|
}
|
|
$colstr .= $style . '">#' . $color . '</span>' ;
|
|
return $colstr;
|
|
}
|
|
|
|
function format_filesize($size, $format=2) {
|
|
switch ($format) {
|
|
case 1: // short
|
|
if ($size < 1024) {
|
|
$size_string = sprintf(_('%d B'), $size);
|
|
} elseif ($size < 1048576) {
|
|
$size_string = sprintf(_('%.1f kB'), $size/1024);
|
|
} else {
|
|
$size_string = sprintf(_('%.1f MB'), $size/1048576);
|
|
}
|
|
break;
|
|
case 2: // medium
|
|
if ($size < 1024) {
|
|
$size_string = sprintf(_('%d Byte'), $size);
|
|
} elseif ($size < 1048576) {
|
|
$size_string = sprintf(_('%.1f kByte'), $size/1024);
|
|
} else {
|
|
$size_string = sprintf(_('%.1f MByte'), $size/1048576);
|
|
}
|
|
break;
|
|
case 3: // long
|
|
if ($size < 1024) {
|
|
$size_string = sprintf(_('%d Byte'), $size);
|
|
} elseif ($size < 1048576) {
|
|
$size_string = sprintf(_('approx. %.1f kByte'), $size/1024);
|
|
} else {
|
|
$size_string = sprintf(_('approx. %.1f MByte'), $size/1048576);
|
|
}
|
|
break;
|
|
default:
|
|
$size_string = trim($size);
|
|
}
|
|
return $size_string;
|
|
}
|
|
|
|
function format_measurement($n, $unit, $v1, $v2, $v3) {
|
|
$m = '';
|
|
switch ($n) {
|
|
case 1:
|
|
$m = "$v1 $unit";
|
|
break;
|
|
case 2:
|
|
$m = "$v1×$v2 $unit";
|
|
break;
|
|
case 3:
|
|
$m = "$v1×$v2×$v3 $unit";
|
|
break;
|
|
}
|
|
return $m;
|
|
}
|
|
|
|
// ========== COMMON FUNCTIONS ================================================
|
|
|
|
function header_location($location, $message=NULL) {
|
|
if (is_array($message)) {
|
|
$valid_keys = array('succ', 'info', 'warn', 'err');
|
|
foreach ($message as $k => $v) {
|
|
if (in_array($k, $valid_keys)) {
|
|
$_SESSION['message'][$k] = $v;
|
|
}
|
|
}
|
|
}
|
|
header("Location: $location");
|
|
exit;
|
|
}
|
|
|
|
function pgettext($context, $msgid) {
|
|
// implement missing function
|
|
global $g_textdomain;
|
|
$ctxstr = "{$context}\004{$msgid}";
|
|
$translation = dcgettext($g_textdomain, $ctxstr, LC_MESSAGES);
|
|
if ($translation == $ctxstr) {
|
|
return $msgid;
|
|
} else {
|
|
return $translation;
|
|
}
|
|
}
|
|
|
|
function get_color_brightness($color, $default=0.5) {
|
|
// returns a value between 0 and 1
|
|
// color is a 6 char hex string
|
|
if (strlen($color) != 6) {
|
|
return $default;
|
|
}
|
|
list($red, $green, $blue) = array_map('hexdec', str_split($color, 2));
|
|
$red = $red / 255 * 0.2126;
|
|
$green = $green / 255 * 0.7152;
|
|
$blue = $blue / 255 * 0.0722;
|
|
return $red + $green + $blue;
|
|
}
|
|
|
|
function page_caption_search($caption) {
|
|
// print page caption with searchbox
|
|
?>
|
|
<div class="row row align-items-center justify-content-between">
|
|
<div class="col">
|
|
<h1><?=$caption?></h1>
|
|
</div>
|
|
<div class="col-4">
|
|
<form class="form-inline" method="post" action="search.php">
|
|
<div class="input-group input-group-sm">
|
|
<input name="needle" type="search" class="form-control" placeholder="<?=_('Search')?>">
|
|
<div class="input-group-append">
|
|
<button class="btn btn-sm btn-primary" type="submit"><i class="bi bi-search"></i></button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
function get_pagination($script, $param, $page, $lastpage, $small=false) {
|
|
// no direct output, returns string for multiple output
|
|
|
|
if ($lastpage < 2) {
|
|
return '';
|
|
}
|
|
|
|
$psep = empty($param) ? '?' : "?$param&";
|
|
|
|
$out = '<nav class="my-3">' . "\n";
|
|
$out .= '<ul class="pagination ' . ($small ? 'pagination-sm ' : '') . 'justify-content-center">' . "\n";
|
|
|
|
// First Prev ... Next Last
|
|
// TODO build entries for first, prev, next last
|
|
|
|
if ($page > 1) {
|
|
// Go to first page or previous page
|
|
$out .= '<li class="page-item"><a class="page-link" href="'. $script. $psep. 'p=1">'. _('First'). '</a></li>'. "\n";
|
|
$out .= '<li class="page-item"><a class="page-link" href="'. $script. $psep. 'p='. ($page - 1). '">'. _('Previous'). '</a></li>'. "\n";
|
|
} else {
|
|
$out .= '<li class="page-item disabled"><span class="page-link">'. _('First'). '</span></li>'. "\n";
|
|
$out .= '<li class="page-item disabled"><span class="page-link">'. _('Previous'). '</span></li>'. "\n";
|
|
}
|
|
|
|
if ($lastpage < 10) {
|
|
for ($n = 1; $n <= $lastpage; $n++) {
|
|
$out .='<li class="page-item"><a class="page-link';
|
|
if ($n == $page) $out .= ' active';
|
|
$out .= '" href="'. $script. $psep. 'p='. $n. '">'. $n. '</a></li>'. "\n";
|
|
}
|
|
} else {
|
|
// start working with ... dots
|
|
$start = max(2, $page - 2);
|
|
$end = min($lastpage - 1, $page + 2);
|
|
$out .= '<li class="page-item"><a class="page-link';
|
|
if ($page == 1) $out .= ' active';
|
|
$out .= '" href="'. $script. $psep. 'p=1">1</a></li>'. "\n";
|
|
if ($start > 2) {
|
|
$out .= '<li class="page-item disabled"><span class="page-link">...</span></li>'. "\n";
|
|
}
|
|
for ($n = $start; $n <= $end; $n++) {
|
|
$out .='<li class="page-item"><a class="page-link';
|
|
if ($n == $page) $out .= ' active';
|
|
$out .= '" href="'. $script. $psep. 'p='. $n. '">'. $n. '</a></li>'. "\n";
|
|
}
|
|
if ($end < $lastpage - 1) {
|
|
$out .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
|
|
}
|
|
$out .= '<li class="page-item"><a class="page-link';
|
|
if ($page == $lastpage) $out .= ' active';
|
|
$out .= '" href="'. $script. $psep. 'p='. $lastpage. '">'. $lastpage. '</a></li>'. "\n";
|
|
}
|
|
|
|
if ($page < $lastpage) {
|
|
// go to last page or next page
|
|
$out .= '<li class="page-item"><a class="page-link" href="'. $script. $psep. 'p='. ($page + 1). '">'. _('Next'). '</a></li>'. "\n";
|
|
$out .= '<li class="page-item"><a class="page-link" href="'. $script. $psep. 'p='. $lastpage. '">'. _('Last'). '</a></li>'. "\n";
|
|
} else {
|
|
$out .= '<li class="page-item disabled"><span class="page-link">'. _('Next'). '</span></li>'. "\n";
|
|
$out .= '<li class="page-item disabled"><span class="page-link">'. _('Last'). '</span></li>'. "\n";
|
|
}
|
|
|
|
// idea: small entry to input page number
|
|
|
|
$out .= "</ul>\n</nav>\n";
|
|
return $out;
|
|
}
|
|
|
|
function table_rec_summary($start, $end, $total, $cols) {
|
|
// common summary table footer for data tables
|
|
echo "<tfoot>\n";
|
|
echo ' <tr><td colspan="', $cols, '">';
|
|
if ($total > 0) {
|
|
echo sprintf(_('Records %d to %d of %d'), $start, $end, $total);
|
|
} else {
|
|
echo _('No records found');
|
|
}
|
|
echo "</td></tr>\n";
|
|
echo "</tfoot>\n";
|
|
}
|
|
|
|
function pdf_get_pagecount($filename) {
|
|
$pages = exec("/usr/bin/mutool show $filename Root/Pages/Count", $output, $retval);
|
|
// TODO error checking
|
|
return $pages;
|
|
}
|
|
|
|
function pdf_get_preview($docid) {
|
|
global $g_doc_basepath;
|
|
// create file in thumbs if needed
|
|
$thumbfile = $g_doc_basepath . '/thumbs/doc-' . $docid . '.jpg';
|
|
if (file_exists($thumbfile)) {
|
|
//return "<img src=""
|
|
// return existing thumbfile
|
|
}
|
|
|
|
}
|
|
|
|
function make_weblink($dest) {
|
|
$dest = trim($dest);
|
|
if ($dest === '') {
|
|
return '';
|
|
}
|
|
$scheme = strtolower((string) parse_url($dest, PHP_URL_SCHEME));
|
|
if ($scheme === '') {
|
|
$dest = 'https://' . $dest;
|
|
} elseif (!in_array($scheme, ['http', 'https'], true)) {
|
|
return '';
|
|
}
|
|
return '<a href="' . $dest . '" target="_blank"><i class="bi-globe"></i></a>';
|
|
}
|
|
|
|
function make_maillink($dest) {
|
|
$dest = trim($dest);
|
|
if ($dest === '') {
|
|
return '';
|
|
}
|
|
$scheme = strtolower((string) parse_url($dest, PHP_URL_SCHEME));
|
|
if ($scheme === '') {
|
|
$dest = 'mailto:' . $dest;
|
|
} elseif ($scheme !== 'mailto') {
|
|
return '';
|
|
}
|
|
return '<a href="' . $dest . '"><i class="bi-envelope"></i></a>';
|
|
}
|
|
|
|
function make_phonelink($dest) {
|
|
$dest = normalize_phone_e164($dest);
|
|
if ($dest === '') {
|
|
return '';
|
|
}
|
|
$scheme = strtolower((string) parse_url($dest, PHP_URL_SCHEME));
|
|
if ($scheme === '') {
|
|
$dest = 'tel:' . $dest;
|
|
} elseif ($scheme !== 'tel') {
|
|
return '';
|
|
}
|
|
return '<a href="' . $dest . '"><i class="bi-telephone"></i></a>';
|
|
}
|
|
|
|
function normalize_phone_e164($phone) {
|
|
$phone = trim($phone);
|
|
if ($phone === '') {
|
|
return '';
|
|
}
|
|
$phone = preg_replace('/[^\d+]/', '', $phone);
|
|
if (str_starts_with($phone, '+')) {
|
|
return $phone;
|
|
}
|
|
if (str_starts_with($phone, '00')) {
|
|
return '+' . substr($phone, 2);
|
|
}
|
|
if (str_starts_with($phone, '0')) {
|
|
$phone = substr($phone, 1);
|
|
}
|
|
return '+49' . $phone;
|
|
}
|
|
|
|
// ========== MENU FUNCTIONS ==================================================
|
|
|
|
$g_modules = Array(
|
|
1 => ['file' => 'equipment.php',
|
|
'title' => _('Equipment'),
|
|
'type' => 'primary',
|
|
'group' => 'main'],
|
|
2 => ['file' => 'inventory.php',
|
|
'title' => _('Inventory'),
|
|
'type' => 'primary',
|
|
'group' => 'main'],
|
|
3 => ['file' => 'provisions.php',
|
|
'title' => _('Provisions'),
|
|
'type' => 'primary',
|
|
'group' => 'main'],
|
|
4 => ['file' => 'maintenance.php',
|
|
'title' => _('Maintenance'),
|
|
'type' => 'primary',
|
|
'group' => 'main'],
|
|
5 => ['file' => 'projects.php',
|
|
'title' => _('Projects'),
|
|
'type' => 'primary',
|
|
'group' => 'main'],
|
|
6 => ['file' => 'documents.php',
|
|
'title' => _('Documents'),
|
|
'type' => 'primary',
|
|
'group' => 'main'],
|
|
7 => ['file' => 'company.php',
|
|
'title' => _('Companies'),
|
|
'type' => 'primary',
|
|
'group' => 'base'],
|
|
8 => ['file' => 'task.php',
|
|
'title' => _('Task'),
|
|
'type' => 'primary',
|
|
'group' => 'extra'],
|
|
9 => ['file' => 'measurement.php',
|
|
'title' => _('Measurement'),
|
|
'type' => 'primary',
|
|
'group' => 'extra'],
|
|
10 => ['file' => 'storage.php',
|
|
'title' => _('Storage'),
|
|
'type' => 'primary',
|
|
'group' => 'base'],
|
|
11 => ['file' => 'box.php',
|
|
'title' => _('Boxes'),
|
|
'type' => 'primary',
|
|
'group' => 'extra'],
|
|
12 => ['file' => 'cable.php',
|
|
'title' => _('Cables'),
|
|
'type' => 'primary',
|
|
'group' => 'extra'],
|
|
13 => ['file' => 'fuse.php',
|
|
'title' => _('Fuses'),
|
|
'type' => 'primary',
|
|
'group' => 'extra'],
|
|
14 => ['file' => 'checklist.php',
|
|
'title' => _('Checklists'),
|
|
'type' => 'primary',
|
|
'group' => 'extra'],
|
|
15 => ['file' => 'tag.php',
|
|
'title' => _('Tags'),
|
|
'type' => 'secondary',
|
|
'group' => 'base'],
|
|
16 => ['file' => 'settings.php',
|
|
'title' => _('Settings'),
|
|
'type' => 'secondary',
|
|
'group' => 'extra'],
|
|
17 => ['file' => 'dropdown.php',
|
|
'title' => _('Selection lists'),
|
|
'type' => 'secondary',
|
|
'group' => 'base']);
|