Apply the configured timezone to PHP and both DB connections, wrap unwrapped ajax payloads so delete buttons reach their engines, normalise and validate invoice due dates, reject stock quantities below the stored 4dp scale, and list stock movements across all warehouses.
117 lines
3.8 KiB
PHP
117 lines
3.8 KiB
PHP
<?php
|
|
|
|
// Apply the configured application timezone before anything formats or stores a
|
|
// date. config.php (loaded by the caller) supplies $time_zone.
|
|
require_once __DIR__ . '/assets/utils/timezone.php';
|
|
|
|
// db connection
|
|
/** overide native PDO function */
|
|
class database extends PDO {
|
|
|
|
protected $query;
|
|
|
|
public function __construct($dsn, $username = '', $password = '', $driver_options = array()) {
|
|
parent::__construct($dsn, $username, $password, $driver_options);
|
|
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('db_statement', array($this)));
|
|
}
|
|
|
|
public function last_query() {
|
|
return $this->query;
|
|
}
|
|
|
|
}
|
|
|
|
/** overide native PDO statement */
|
|
// for XSS protection
|
|
class db_statement extends PDOStatement {
|
|
|
|
protected $pdo;
|
|
|
|
protected function __construct($pdo) {
|
|
$this->pdo = $pdo;
|
|
}
|
|
|
|
// PDOStatement::execute() is declared ?array $params = null : bool. This
|
|
// override deliberately accepts a looser signature so callers may pass
|
|
// positional arguments (see func_get_args() below), so the tightened return
|
|
// type is opted out of rather than the call sites being changed.
|
|
#[\ReturnTypeWillChange]
|
|
public function execute($args = null) {
|
|
// Perform logging here. PDO object is accessible
|
|
// from $this->pdo.
|
|
|
|
if (!is_array($args)) {
|
|
$args = func_get_args();
|
|
}else{
|
|
// Cast all values to string before XSS processing.
|
|
// json_decode requires a string — integers, booleans, and nulls
|
|
// passed as bound parameters would otherwise cause a TypeError.
|
|
// null is preserved as-is so PDO can bind NULL columns correctly.
|
|
$args = array_map(fn($v) => is_null($v) ? null : (string)$v, $args);
|
|
|
|
// escaping array
|
|
// prevent store XSS
|
|
foreach($args as &$item){
|
|
|
|
if (is_null($item)) continue;
|
|
|
|
// decode the JSON data
|
|
// set second parameter boolean TRUE for associative array output.
|
|
$result = json_decode($item);
|
|
if (json_last_error() === JSON_ERROR_NONE) {
|
|
// encode html for json
|
|
$tmp = json_decode($item,true);
|
|
foreach((array)$tmp as &$ii){
|
|
|
|
// Inner values may be arrays (nested JSON objects) — cast to string
|
|
if (!is_string($ii)) {
|
|
$ii = json_encode($ii);
|
|
continue;
|
|
}
|
|
|
|
$result = json_decode($ii);
|
|
if (json_last_error() === JSON_ERROR_NONE) {
|
|
// json inside json
|
|
$tmpp = json_decode($ii,true);
|
|
foreach ((array)$tmpp as &$iii) {
|
|
$iii = htmlspecialchars($ii, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
$ii = json_encode($tmpp);
|
|
}else{
|
|
// string inside json
|
|
$ii = htmlspecialchars($ii, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
}
|
|
$item = json_encode($tmp);
|
|
}else{
|
|
// encode html for string
|
|
$item = htmlspecialchars($item, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
}
|
|
}
|
|
return parent::execute($args);
|
|
}
|
|
|
|
}
|
|
|
|
//..................... PDO1 .....................//
|
|
$pdo1 = new database($db_type.':host='.$db_server.';dbname='.$db_database.';charset=utf8', $db_user, $db_pass);
|
|
$pdo1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
//..................... PDO2 .....................//
|
|
$pdo2 = new database($db_type2.':host='.$db_server2.';dbname='.$db_database2.';charset=utf8', $db_user2, $db_pass2);
|
|
$pdo2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
// Pin both connections to the application timezone, so MySQL NOW() and PHP
|
|
// date() agree no matter how the database server itself is configured. Queries
|
|
// mix the two freely (rows written with NOW(), others with date()), and a
|
|
// mismatch shows up as timestamps hours away from the real clock.
|
|
foreach ([$pdo1, $pdo2] as $pdo_tz) {
|
|
try {
|
|
$pdo_tz->exec("SET time_zone = '" . APP_TIMEZONE_OFFSET . "'");
|
|
} catch (PDOException $e) {
|
|
// A server that refuses the offset keeps its own zone — no worse than
|
|
// before this call existed, and not a reason to fail the request.
|
|
}
|
|
} |