generated from jric11/baseProject
859 lines
28 KiB
PHP
859 lines
28 KiB
PHP
<?php
|
|
|
|
// ==========
|
|
// Base Class
|
|
// ==========
|
|
|
|
require_once __DIR__ . "/../vendor/autoload.php";
|
|
require_once __DIR__ . "/Autoloader.php";
|
|
|
|
class Base {
|
|
|
|
// =========
|
|
// Constants
|
|
// =========
|
|
|
|
const APPLICATION_NAME = "My Hunting Club Roster";
|
|
const APPLICATION_MNEMONIC = "HUNTINGCLUBROSTER";
|
|
const VERSION = "1.04";
|
|
const TIMEZONE = "America/New_York";
|
|
// =================
|
|
// Session Variables
|
|
// =================
|
|
|
|
const ENVIRONMENT = "HUNTINGCLUBROSTER_ENVIRONMENT";
|
|
const ACTIVE_SESSION = "HUNTINGCLUBROSTER_ACTIVE_SESSION";
|
|
const USER_SERIAL = "HUNTINGCLUBROSTER_USER_SERIAL";
|
|
const USER_NAME = "HUNTINGCLUBROSTER_USER_NAME";
|
|
const USER_FULL_NAME = "HUNTINGCLUBROSTER_USER_FULL_NAME";
|
|
const USER_EMAIL = "HUNTINGCLUBROSTER_USER_EMAIL";
|
|
const USER_ROLE = "HUNTINGCLUBROSTER_USER_ROLE";
|
|
const USER_AUTHENTICATED = "HUNTINGCLUBROSTER_USER_AUTHENTICATED";
|
|
const REPORTS_GET = "HUNTINGCLUBROSTER_REPORTS_GET";
|
|
const REPORTS_POST = "HUNTINGCLUBROSTER_REPORTS_POST";
|
|
const THEME = "HUNTINGCLUBROSTER_THEME";
|
|
const AJAX = "HUNTINGCLUBROSTER_AJAX";
|
|
const TODAY = "HUNTINGCLUBROSTER_TODAY";
|
|
const COPYRIGHT = "HUNTINGCLUBROSTER_COPYRIGHT";
|
|
const PAGE = "HUNTINGCLUBROSTER_PAGE";
|
|
const PAGINATION_PAGE = "HUNTINGCLUBROSTER_PAGINATION_PAGE";
|
|
|
|
// =================
|
|
// Class Constructor
|
|
// =================
|
|
|
|
public function __construct() {
|
|
|
|
set_error_handler(array($this, "displayError"));
|
|
|
|
date_default_timezone_set(self::TIMEZONE);
|
|
}
|
|
|
|
// ===============================================
|
|
// Append a SimpleXMLElement to a SimpleXMLElememt
|
|
// ===============================================
|
|
|
|
public function appendSXE(SimpleXMLElement $from, SimpleXMLElement $to) {
|
|
|
|
$toDOM = dom_import_simplexml($to);
|
|
$fromDOM = dom_import_simplexml($from);
|
|
|
|
$toDOM->appendChild($toDOM->ownerDocument->importNode($fromDOM, true));
|
|
}
|
|
|
|
// ==========================================
|
|
// Apply an XSL Stylesheet to an XML Document
|
|
// ==========================================
|
|
|
|
public function applyXSL($XMLString = "", $XSLString = "", $XSLParms = "") {
|
|
|
|
if ($XMLString instanceof SimpleXMLElement) {
|
|
$XMLString = $XMLString->asXML();
|
|
}
|
|
|
|
if (empty($XSLString)) {
|
|
trigger_error("No XSL Stylesheet provided.");
|
|
}
|
|
|
|
if (!is_array($XSLParms)) {
|
|
$XSLParms = array();
|
|
}
|
|
|
|
$XSLParms["CSRF_TOKEN"] = $this->csrfToken();
|
|
$XSLParms["APPLICATION_NAME"] = self::APPLICATION_NAME;
|
|
$XSLParms["APPLICATION_MNEMONIC"] = self::APPLICATION_MNEMONIC;
|
|
$XSLParms["VERSION"] = self::VERSION;
|
|
$XSLParms["THEME"] = $_SESSION[self::THEME] ?? self::THEME;
|
|
$XSLParms["ENVIRONMENT"] = $_SESSION[self::ENVIRONMENT] ?? "Production";
|
|
$XSLParms["USER_NAME"] = $XSLParms["USER_NAME"] ?? $_SESSION[self::USER_NAME] ?? "";
|
|
$XSLParms["USER_FULL_NAME"] = $_SESSION[self::USER_FULL_NAME] ?? "";
|
|
$XSLParms["USER_ROLE"] = $_SESSION[self::USER_ROLE] ?? "Guest";
|
|
$XSLParms["RANDOM_NUMBER"] = rand();
|
|
$XSLParms["TODAY_YYYYMMDD"] = date("Y-m-d");
|
|
$XSLParms["TODAY_MMDDYYYY"] = date("m/d/Y");
|
|
$XSLParms["TODAY_HHMM"] = date("Hi");
|
|
$XSLParms["COPYRIGHT"] = date("Y");
|
|
|
|
$XML = new DOMDocument();
|
|
$XSL = new DOMDocument();
|
|
$XSLT = new XSLTProcessor();
|
|
|
|
if ($XMLString) {
|
|
$XML->loadXML($XMLString, LIBXML_NONET);
|
|
}
|
|
|
|
if ($XSLString) {
|
|
$XSL->loadXML($XSLString, LIBXML_NONET);
|
|
}
|
|
|
|
$XSLT->importStylesheet($XSL);
|
|
|
|
if ($XSLParms) {
|
|
$XSLT->setParameter("", $XSLParms);
|
|
}
|
|
|
|
if (!$XML->documentElement)
|
|
$XML->loadXML('<XML/>');
|
|
$document = $XSLT->transformToXml($XML);
|
|
|
|
return $document;
|
|
}
|
|
|
|
// ============================
|
|
// Return a Database Connection
|
|
// ============================
|
|
|
|
public function connect(): PDO {
|
|
|
|
$settings = $this->getSettings();
|
|
|
|
$host = (string) $settings->DatabaseServer;
|
|
$database = (string) $settings->DatabaseName;
|
|
$username = (string) $settings->DatabaseUser;
|
|
$password = (string) $settings->DatabasePassword;
|
|
|
|
try {
|
|
|
|
$dsn = sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', $host, (int) ($settings->DatabasePort ?? 3306), $database);
|
|
|
|
$options=[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_EMULATE_PREPARES => false,
|
|
PDO::ATTR_PERSISTENT => false,];
|
|
$ca=getenv('HCR_DB_TLS_CA')?:'';
|
|
if((string)$settings->Environment==='Production'&&!in_array(strtolower($host),['localhost','127.0.0.1','::1'],true)&&!$ca)throw new RuntimeException('Remote production database connections require HCR_DB_TLS_CA and verified TLS.');
|
|
if($ca){if(!is_readable($ca))throw new RuntimeException('The database TLS CA file is not readable.');$options[PDO::MYSQL_ATTR_SSL_CA]=$ca;$options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT]=true;}
|
|
$db=new PDO($dsn,$username,$password,$options);
|
|
if($ca){$cipher=$db->query("SHOW SESSION STATUS LIKE 'Ssl_cipher'")->fetch();if(!$cipher||empty($cipher['Value']))throw new RuntimeException('The database connection did not negotiate TLS.');}
|
|
return $db;
|
|
} catch (PDOException $e) {
|
|
|
|
error_log("Database Connection Failed: " . $e->getMessage());
|
|
|
|
throw new Exception("Unable to connect to the database.", 0, $e);
|
|
}
|
|
}
|
|
|
|
// ===============
|
|
// Debug an Object
|
|
// ===============
|
|
|
|
public function debug($object = "") {
|
|
|
|
if ($_SESSION[self::AJAX]) {
|
|
error_log(print_r($object, true));
|
|
} else {
|
|
echo "<pre>";
|
|
print_r($object);
|
|
}
|
|
|
|
exit();
|
|
}
|
|
|
|
// ======================
|
|
// Display a Content Page
|
|
// ======================
|
|
|
|
public function displayContent($content = "") {
|
|
|
|
$html = $this->applyXSL("", $this->getXSL("content"), ["IS_SUPER"=>(new TenantAccess())->isSuper()?"1":"0","CAN_MANAGE_CLUBS" => (new TenantAccess())->managesAny()?"1":"0"]);
|
|
$html = str_replace("$$$-CONTENT-$$$", $content, $html);
|
|
|
|
echo $html;
|
|
|
|
exit();
|
|
}
|
|
|
|
// ======================================================
|
|
// Custom error handler to display informative error page
|
|
// ======================================================
|
|
|
|
public function displayError($error_number = 0, $error_message = '', $error_file = '', $error_line = 0) {
|
|
if (!(error_reporting() & $error_number)) {
|
|
return false;
|
|
}
|
|
// Never log application failures through the failing database or SMTP service.
|
|
error_log("PHP {$error_number}: {$error_message} in {$error_file}:{$error_line}");
|
|
|
|
if (in_array($error_number, [E_DEPRECATED, E_USER_DEPRECATED], true)) {
|
|
return true;
|
|
}
|
|
throw new ErrorException($error_message, 0, $error_number, $error_file, $error_line);
|
|
}
|
|
|
|
// =====================
|
|
// Display a Notice Page
|
|
// =====================
|
|
|
|
public function displayNotice($messgae = "") {
|
|
|
|
$XSLParms["MESSAGE"] = $messgae;
|
|
|
|
$content = $this->applyXSL("", $this->getXSL("notice"), $XSLParms);
|
|
|
|
$this->displayContent($content);
|
|
|
|
exit();
|
|
}
|
|
|
|
// =====================
|
|
// Display the Home Page
|
|
// =====================
|
|
|
|
public function displayHome() {
|
|
(new Roster())->home();
|
|
}
|
|
|
|
// =======================
|
|
// Do the requested Action
|
|
// =======================
|
|
|
|
public function do() {
|
|
|
|
if (empty($_SESSION[self::ACTIVE_SESSION])) {
|
|
$this->initializeSession();
|
|
}
|
|
|
|
$_SESSION[self::AJAX] = strtolower($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'xmlhttprequest';
|
|
|
|
$_SESSION[self::TODAY] = date('Y-m-d');
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
|
|
|
if (!in_array($method, ['GET', 'POST'], true)) {
|
|
http_response_code(405);
|
|
exit('Method not allowed.');
|
|
}
|
|
|
|
// Ticket bodies need room for 8,000 four-byte Unicode characters. Keep
|
|
// a hard transport ceiling; field validators enforce the character limit.
|
|
$candidate=$_POST['action']??$_GET['Xaction']??$_GET['action']??'';
|
|
$ticketSubmission=is_string($candidate)&&strtolower(trim($candidate))==='roster.tickets'
|
|
&& in_array($_POST['step']??null,['openTicket','reply'],true);
|
|
foreach ([$_GET,$_POST] as $source=>$values) {
|
|
foreach($values as $field=>$value){
|
|
$byteLimit=$source===1&&$ticketSubmission&&$field==='message'?65536:8192;
|
|
if (!is_string($value) || strlen($value) > $byteLimit) {
|
|
http_response_code(400);
|
|
exit('Invalid request.');
|
|
}
|
|
}
|
|
}
|
|
$action = strtolower(trim($_POST['action'] ?? $_GET['Xaction'] ?? $_GET['action'] ?? ''));
|
|
if($action==='verifyemail'){(new EmailVerification())->page();return;}
|
|
if ($action === 'signout') {
|
|
if ($method !== 'POST') {
|
|
http_response_code(405);
|
|
exit('Use POST to sign out.');
|
|
}
|
|
$this->requireCsrf();
|
|
$ajax = $_SESSION[self::AJAX];
|
|
$this->logoutSession();
|
|
if ($ajax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(true);
|
|
exit;
|
|
}
|
|
header('Location: main.php', true, 303);
|
|
exit;
|
|
}
|
|
if (in_array($action, ['resetpassword', 'authentication.resetpassword'], true)) {
|
|
(new Authentication())->resetPassword();
|
|
}
|
|
if (in_array($action, ['signup', 'signup.createaccount'], true)) {
|
|
(new SignUp())->createAccount();
|
|
}
|
|
if (in_array($action, ['signin', 'signin.login', 'users.login'], true)) {
|
|
(new SignIn())->login();
|
|
}
|
|
|
|
$user = (new AuthService())->currentUser();
|
|
|
|
if (!$user) {
|
|
$ajax = $_SESSION[self::AJAX];
|
|
if (!empty($_SESSION[self::USER_AUTHENTICATED])) {
|
|
$this->logoutSession();
|
|
}
|
|
if ($ajax) {
|
|
http_response_code(401);
|
|
exit('Please sign in again.');
|
|
}
|
|
(new SignIn())->login();
|
|
}
|
|
if ($method === 'POST') {
|
|
$this->requireCsrf();
|
|
}
|
|
$mfa=new MfaService();
|
|
if($mfa->gate($user,$action))return;
|
|
$sensitive=in_array($action,['users.changepassword','users.adduser','users.edituser','users.deleteuser','users.updateactivestatus','users.useractive','users.resetpassword'],true)
|
|
|| ($action==='roster.membersummary' && ($_POST['step']??'')==='member')
|
|
|| ($action==='roster.workspace' && (($_POST['step']??'')==='member' || (($_POST['step']??'')==='invite' && ($_POST['membership_role']??'')==='administrator')));
|
|
if($method==='POST' && $sensitive && (new TenantAccess())->managesAny())$mfa->requireRecent();
|
|
if ((bool) $user['user_change_password']) {
|
|
(new Users())->changePassword();
|
|
return;
|
|
}
|
|
|
|
// Only named, reviewed entry points are routable. Helpers are never endpoints.
|
|
$routes = [
|
|
'' => [Base::class, 'displayHome', false],
|
|
'./' => [Base::class, 'displayHome', false],
|
|
'displayhome' => [Base::class, 'displayHome', false],
|
|
'toggletheme' => [Base::class, 'toggleTheme', false],
|
|
'getzipcode' => [Base::class, 'getZipcode', false],
|
|
'validatedate' => [Base::class, 'validateDate', false],
|
|
'getage' => [Base::class, 'getAge', false],
|
|
'users.changepassword' => [Users::class, 'changePassword', false],
|
|
'roster.workspace' => [Roster::class,'workspace',false],
|
|
'roster.membersummary' => [MemberSummary::class,'show',false],
|
|
'roster.createclub' => [Roster::class,'createClub','super'],
|
|
'roster.help' => [Roster::class,'help',false],
|
|
'roster.invitations' => [Roster::class,'invitations',false],
|
|
'roster.tickets' => [Roster::class,'tickets',false],
|
|
'rosterreports.catalog' => [RosterReports::class,'catalog',true],
|
|
'rosterreports.ledger' => [RosterReports::class,'ledger',true],
|
|
'rosterreports.csv' => [RosterReports::class,'csv',true],
|
|
'rosterreports.excel' => [RosterReports::class,'excel',true],
|
|
'rosterreports.pdf' => [RosterReports::class,'pdf',true],
|
|
|
|
|
|
|
|
|
|
];
|
|
$adminRoutes = [
|
|
'Users' => ['setupUsers', 'userSummary', 'addUser', 'editUser', 'deleteUser', 'resetPassword', 'userExists', 'getUser_AJAX', 'updateActiveStatus', 'userActive'],
|
|
'Alerts' => ['setupAlerts', 'addAlert', 'editAlert', 'deleteAlert'],
|
|
'Dropdowns' => ['setupDropdowns', 'setupDropdowntypes', 'addDropdown', 'editDropdown', 'deleteDropdown', 'addDropdowntype', 'editDropdowntype', 'deleteDropdowntype', 'dropdownExists', 'dropdowntypeExists', 'dropdowntypetitleExists', 'dropdownActive', 'dropdowntypeActive', 'updateDropdownActive', 'updateDropdownDefault'],
|
|
'Popovers' => ['setupPopovers', 'addPopover', 'editPopover', 'deletePopover', 'popoverExists'],
|
|
'Reports' => ['setupReports', 'selectReport'],
|
|
'Statuses' => ['setupStatuses', 'addStatus', 'editStatus', 'deleteStatus', 'statusExists', 'statusActive'],
|
|
'Journal' => ['viewJournal'],
|
|
'Notes' => ['addNote', 'editNote', 'deleteNote'],
|
|
];
|
|
foreach ($adminRoutes as $class => $methods) {
|
|
foreach ($methods as $entry) {
|
|
$routes[strtolower($class . '.' . $entry)] = [$class, $entry, $class==='Reports'?true:'super'];
|
|
}
|
|
}
|
|
if (!isset($routes[$action])) {
|
|
http_response_code(404);
|
|
exit('Page not found.');
|
|
}
|
|
[$class, $entry, $adminOnly] = $routes[$action];
|
|
if (($adminOnly==='super' && !(new TenantAccess())->isSuper()) || ($adminOnly===true && !(new TenantAccess())->managesAny())) {
|
|
http_response_code(403);
|
|
exit('You do not have permission to perform this action.');
|
|
}
|
|
if ($method !== 'POST' && !in_array($action, ['', './', 'displayhome', 'users.changepassword','roster.workspace','roster.membersummary','roster.createclub','roster.help','roster.invitations','roster.tickets','rosterreports.catalog'], true)) {
|
|
http_response_code(405);
|
|
exit('Use POST for this action.');
|
|
}
|
|
ob_start();
|
|
try{(new $class())->$entry();$body=ob_get_clean();}
|
|
catch(Throwable $e){ob_end_clean();throw $e;}
|
|
if($_SESSION[self::AJAX]??false){header('Content-Type: application/json');echo trim($body)===''?json_encode(['ok'=>true]):$body;}
|
|
else echo $body;
|
|
}
|
|
|
|
public function csrfToken(): string {
|
|
if (empty($_SESSION['csrf_token']))
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
return $_SESSION['csrf_token'];
|
|
}
|
|
|
|
public function requireCsrf(): void {
|
|
$token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
|
if (!is_string($token) || !hash_equals($this->csrfToken(), $token)) {
|
|
http_response_code(403);
|
|
exit('Your form expired. Reload the page and try again.');
|
|
}
|
|
}
|
|
|
|
public function logoutSession(): void {
|
|
$_SESSION = [];
|
|
session_regenerate_id(true);
|
|
$this->initializeSession();
|
|
$this->csrfToken();
|
|
}
|
|
|
|
// ==========================
|
|
// Return an encrypted string
|
|
// ==========================
|
|
|
|
protected function encrypt($string = "", $key = "") {
|
|
|
|
$key = (empty($key)) ? __CLASS__ : $key;
|
|
|
|
$ivlen = openssl_cipher_iv_length($cipher = "AES-128-CBC");
|
|
$iv = openssl_random_pseudo_bytes($ivlen);
|
|
$raw = openssl_encrypt($string, $cipher, $key, $options = OPENSSL_RAW_DATA, $iv);
|
|
$hmac = hash_hmac("sha256", $raw, $key, $as_binary = true);
|
|
$encrypted = base64_encode($iv . $hmac . $raw);
|
|
|
|
return $encrypted;
|
|
}
|
|
|
|
// ========================
|
|
// Execute an SQL Statement
|
|
// ========================
|
|
|
|
public function executeSQL($SQL = "") {
|
|
|
|
if (trim($SQL) == "") {
|
|
return false;
|
|
}
|
|
|
|
return $this->connect()->query($SQL);
|
|
}
|
|
|
|
// ======================
|
|
// Format an XML document
|
|
// ======================
|
|
|
|
public function formatXML($XML = "") {
|
|
|
|
if (!$XML) {
|
|
return false;
|
|
}
|
|
|
|
if ($XML instanceof SimpleXMLElement) {
|
|
$XML = $XML->asXML();
|
|
}
|
|
|
|
$dom = new DOMDocument("1.0");
|
|
$dom->preserveWhiteSpace = false;
|
|
$dom->formatOutput = true;
|
|
$dom->loadXML($XML);
|
|
|
|
return $dom->saveXML();
|
|
}
|
|
|
|
// =====================================
|
|
// Return an Application Settings Object
|
|
// =====================================
|
|
|
|
public function getSettings() {
|
|
|
|
$path = getenv('HCR_SETTINGS_FILE') ?: (getenv('HCR_ENVIRONMENT')!=='Production' && is_file(__DIR__ . '/../xml/settings.local.xml') ? __DIR__ . '/../xml/settings.local.xml' : __DIR__ . '/../xml/settings.xml');
|
|
$settings = simplexml_load_file($path, SimpleXMLElement::class, LIBXML_NONET);
|
|
if (!$settings)
|
|
throw new RuntimeException('Unable to load application settings.');
|
|
foreach (['Environment' => 'HCR_ENVIRONMENT', 'DatabaseServer' => 'HCR_DB_HOST', 'DatabasePort' => 'HCR_DB_PORT', 'DatabaseName' => 'HCR_DB_NAME', 'DatabaseUser' => 'HCR_DB_USER', 'DatabasePassword' => 'HCR_DB_PASSWORD', 'SmtpServer' => 'HCR_SMTP_HOST', 'SmtpUserName' => 'HCR_SMTP_USER', 'SmtpPassword' => 'HCR_SMTP_PASSWORD', 'SmtpPort' => 'HCR_SMTP_PORT', 'SmtpFromEmail' => 'HCR_SMTP_FROM', 'ApplicationUrl' => 'HCR_APPLICATION_URL'] as $name => $variable) {
|
|
$value = getenv($variable);
|
|
if ($value !== false)
|
|
$settings->$name = $value;
|
|
}
|
|
|
|
return $settings;
|
|
}
|
|
|
|
// ==================================================================
|
|
// Return Age in Years based on given Birthdate ("yyyy-mm-dd" format)
|
|
// ==================================================================
|
|
|
|
public function getAge($birthdate = "", $date = "") {
|
|
|
|
$birthdate = filter_input(INPUT_POST, "birthdate", FILTER_UNSAFE_RAW) ?: $birthdate;
|
|
$date = filter_input(INPUT_POST, "date", FILTER_UNSAFE_RAW) ?: $date;
|
|
|
|
$age = 0;
|
|
|
|
$date = (empty($date)) ? new Datetime() : new DateTime($date);
|
|
$birthdate = (empty($birthdate)) ? new Datetime() : new DateTime($birthdate);
|
|
|
|
$age = $birthdate->diff($date)->format("%y");
|
|
|
|
if ($_SESSION[self::AJAX]) {
|
|
echo json_encode($age);
|
|
} else {
|
|
return $age;
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// Return a Constants Object
|
|
// =========================
|
|
|
|
public function getConstants() {
|
|
|
|
$Constants = new SimpleXMLElement($this->getXML("constants"));
|
|
|
|
return $Constants;
|
|
}
|
|
|
|
// ==================
|
|
// Return a Recordset
|
|
// ==================
|
|
|
|
public function getRecordset($SQL = "") {
|
|
|
|
if (empty($SQL)) {
|
|
return false;
|
|
}
|
|
|
|
$recordSet = $this->connect()->query($SQL);
|
|
|
|
return $recordSet;
|
|
}
|
|
|
|
// =======================================
|
|
// Return Table Data as a SimpleXMLElement
|
|
// =======================================
|
|
|
|
public function getTable($table = "", $SQL = "") {
|
|
|
|
if (empty($table)) {
|
|
return new SimpleXMLElement("<unknown/>");
|
|
}
|
|
|
|
$SXE = new SimpleXMLElement("<{$table}/>");
|
|
|
|
$recordset = $this->getRecordset(empty($SQL) ? "select * from {$table}" : $SQL);
|
|
|
|
if ($recordset) {
|
|
|
|
while ($row = $recordset->fetch(PDO::FETCH_ASSOC)) {
|
|
|
|
$record = $SXE->addChild("record");
|
|
|
|
foreach ($row as $name => $value) {
|
|
$record->$name = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $SXE;
|
|
}
|
|
|
|
// =======================================
|
|
// Return Table Data as a SimpleXMLElement
|
|
// =======================================
|
|
|
|
public function getTableXML($table = "", $statement = "") {
|
|
|
|
if (empty($table)) {
|
|
return new SimpleXMLElement("<unknown/>");
|
|
}
|
|
|
|
$SXE = new SimpleXMLElement("<{$table}/>");
|
|
|
|
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
|
|
|
|
$record = $SXE->addChild("record");
|
|
|
|
foreach ($row as $name => $value) {
|
|
$record->$name = $value;
|
|
}
|
|
}
|
|
|
|
return $SXE;
|
|
}
|
|
|
|
// =================================
|
|
// Return the Time Elapsed since now
|
|
// =================================
|
|
|
|
public function getTimeElapsed($timestamp = "") {
|
|
|
|
$now = new DateTime;
|
|
$then = new DateTime($timestamp);
|
|
$diff = (array) $now->diff($then);
|
|
|
|
$diff["w"] = floor($diff["d"] / 7);
|
|
$diff["d"] -= $diff["w"] * 7;
|
|
|
|
$string = array(
|
|
"y" => "year",
|
|
"m" => "month",
|
|
"w" => "week",
|
|
"d" => "day",
|
|
"h" => "hour",
|
|
"i" => "minute",
|
|
"s" => "second",
|
|
);
|
|
|
|
foreach ($string as $k => & $v) {
|
|
|
|
if ($diff[$k]) {
|
|
$v = $diff[$k] . " " . $v . ($diff[$k] > 1 ? "s" : "");
|
|
} else {
|
|
unset($string[$k]);
|
|
}
|
|
}
|
|
|
|
$string = array_slice($string, 0, 1);
|
|
|
|
return $string ? implode(", ", $string) . " ago" : "just now";
|
|
}
|
|
|
|
// ===================
|
|
// Return a Time Stamp
|
|
// ===================
|
|
|
|
public function getTimestamp() {
|
|
|
|
return date("Y-m-d H:i:s");
|
|
}
|
|
|
|
// ======================
|
|
// Return an XML Document
|
|
// ======================
|
|
|
|
public function getXML($document = "") {
|
|
|
|
if (trim($document) == "") {
|
|
return "";
|
|
}
|
|
|
|
$document = __DIR__ . "/../xml/" . trim($document) . ".xml";
|
|
$XML = (file_exists($document) ? file_get_contents($document) : "");
|
|
|
|
return $XML;
|
|
}
|
|
|
|
// ========================
|
|
// Return an XSL Stylesheet
|
|
// ========================
|
|
|
|
public function getXSL($document = "") {
|
|
|
|
if (trim($document) == "") {
|
|
return "";
|
|
}
|
|
|
|
$document = __DIR__ . "/../xsl/" . trim($document) . ".xsl";
|
|
$XSL = (file_exists($document) ? file_get_contents($document) : "");
|
|
|
|
return $XSL;
|
|
}
|
|
|
|
// =======================================
|
|
// Return a Zip Code Detail Object (Array)
|
|
// =======================================
|
|
|
|
public function getZipcode($zip_code = 0) {
|
|
|
|
$zip_code = filter_input(INPUT_POST, "zip_code", FILTER_SANITIZE_NUMBER_INT) ?: 0;
|
|
$zip_code = (int) substr((string) $zip_code, 0, 5);
|
|
|
|
$zipCodeDetail = null;
|
|
|
|
$SQL = "select *
|
|
from zipcodes
|
|
where zipcode_code = {$zip_code}";
|
|
|
|
$zipcodes = $this->getTable("zipcodes", $SQL);
|
|
|
|
if ($zipcodes->count() > 0) {
|
|
|
|
$zipCodeDetail["ZipCode"] = $zip_code;
|
|
$zipCodeDetail["City"] = (string) $zipcodes->record->zipcode_city;
|
|
$zipCodeDetail["State"] = (string) $zipcodes->record->zipcode_state;
|
|
$zipCodeDetail["County"] = (string) $zipcodes->record->zipcode_county;
|
|
$zipCodeDetail["Country"] = (string) $zipcodes->record->zipcode_country;
|
|
}
|
|
|
|
if ($_SESSION[self::AJAX]) {
|
|
echo json_encode($zipCodeDetail);
|
|
} else {
|
|
return $zipCodeDetail;
|
|
}
|
|
}
|
|
|
|
// ======================
|
|
// Initialize the Session
|
|
// ======================
|
|
|
|
public function initializeSession() {
|
|
|
|
$settings = $this->getSettings();
|
|
|
|
$_SESSION[self::ACTIVE_SESSION] = true;
|
|
$_SESSION[self::THEME] = "light";
|
|
$_SESSION[self::ENVIRONMENT] = (string) ($settings->Environment ?? "Production");
|
|
$_SESSION[self::USER_AUTHENTICATED] = false;
|
|
$_SESSION[self::USER_SERIAL] = 0;
|
|
$_SESSION[self::USER_NAME] = "";
|
|
$_SESSION[self::USER_FULL_NAME] = "";
|
|
$_SESSION[self::USER_EMAIL] = "";
|
|
$_SESSION[self::USER_ROLE] = "Guest";
|
|
$_SESSION[self::REPORTS_GET] = "";
|
|
$_SESSION[self::REPORTS_POST] = "";
|
|
$_SESSION[self::AJAX] = false;
|
|
}
|
|
|
|
// ============
|
|
// Log an Error
|
|
// ============
|
|
|
|
public function logError($error_message = "") {
|
|
error_log($error_message);
|
|
throw new AppException('The requested record is invalid or unavailable.',404);
|
|
}
|
|
|
|
// ==========================
|
|
// Merge thisXML with thatXML
|
|
// ==========================
|
|
|
|
public function mergeXML($thisXML = "", $thatXML = "") {
|
|
|
|
if ($thisXML instanceof SimpleXMLElement) {
|
|
$thisXML = $thisXML->asXML();
|
|
}
|
|
|
|
if ($thatXML instanceof SimpleXMLElement) {
|
|
$thatXML = $thatXML->asXML();
|
|
}
|
|
|
|
if (trim($thisXML) == "") {
|
|
return $thatXML;
|
|
}
|
|
|
|
if (trim($thatXML) == "") {
|
|
return $thisXML;
|
|
}
|
|
|
|
$thisDOM = new DOMDocument();
|
|
$thisDOM->loadXML($thisXML);
|
|
|
|
$thatDOM = new DOMDocument();
|
|
$thatDOM->loadXML($thatXML);
|
|
|
|
$xpath = new DOMXpath($thisDOM);
|
|
$xpathQuery = $xpath->query("/*");
|
|
|
|
for ($i = 0; $i < $xpathQuery->length; $i++) {
|
|
$thatDOM->documentElement->appendChild($thatDOM->importNode($xpathQuery->item($i), true));
|
|
}
|
|
|
|
$thatSXE = simplexml_import_dom($thatDOM);
|
|
|
|
return $thatSXE->asXML();
|
|
}
|
|
|
|
// ==============
|
|
// Print a Report
|
|
// ==============
|
|
|
|
// =================
|
|
// Sanitize a string
|
|
// =================
|
|
|
|
public function sanitizeString($string = "") {
|
|
|
|
return trim(preg_replace("/\s+/", " ", htmlspecialchars($string, ENT_NOQUOTES | ENT_HTML5)));
|
|
}
|
|
|
|
// ========================
|
|
// Toggle the current Theme
|
|
// ========================
|
|
|
|
public function toggleTheme() {
|
|
|
|
$_SESSION[self::THEME] = (($_SESSION[self::THEME] ?? "light") === "dark") ? "light" : "dark";
|
|
}
|
|
|
|
// ===============================
|
|
// Validate a Date (local or AJAX)
|
|
// ===============================
|
|
|
|
public function validateDate($date = "", $format = "Y-m-d") {
|
|
|
|
$date = filter_input(INPUT_POST, "date") ?: $date;
|
|
|
|
$datetime = DateTime::createFromFormat($format, $date);
|
|
$valid = $datetime && $datetime->format($format) === $date;
|
|
|
|
if ($_SESSION[self::AJAX]) {
|
|
echo json_encode($valid);
|
|
} else {
|
|
return $valid;
|
|
}
|
|
}
|
|
|
|
// ========================
|
|
// Generate Random Password
|
|
// ========================
|
|
|
|
public function generateRandomPassword($length = 12) {
|
|
|
|
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()-_+=';
|
|
$password = '';
|
|
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$index = random_int(0, strlen($characters) - 1);
|
|
$password .= $characters[$index];
|
|
}
|
|
|
|
return $password;
|
|
}
|
|
|
|
// ====================
|
|
// User Change Password
|
|
// ====================
|
|
|
|
public function userChangePassword() {
|
|
|
|
$user = (new Users())->getUserByUserName($_SESSION[self::USER_NAME]);
|
|
|
|
if ($user->record->user_change_password == 1) {
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ==============================
|
|
// Alter Sting to Parahgraph Case
|
|
// ==============================
|
|
|
|
public function toParagraphCase($text) {
|
|
|
|
$text = ucwords(strtolower($text));
|
|
|
|
return $text;
|
|
}
|
|
|
|
// ==========================
|
|
// Developer Testing Function
|
|
// ==========================
|
|
|
|
public function testing() {
|
|
|
|
$to = "james.richie@onesourceits.com";
|
|
$subject = "Test Subject";
|
|
$message = "Test Message";
|
|
|
|
$email = (new Email())->sendEmail($to, $subject, $message);
|
|
|
|
$this->debug($email);
|
|
|
|
exit();
|
|
}
|
|
|
|
// =====================
|
|
// Forgot/Reset Password
|
|
// =====================
|
|
}
|