itflow/functions.php

1344 lines
66 KiB
PHP

<?php
// Role check failed wording
DEFINE("WORDING_ROLECHECK_FAILED", "You are not permitted to do that!");
// PHP Mailer Libs
require_once "plugins/PHPMailer/src/Exception.php";
require_once "plugins/PHPMailer/src/PHPMailer.php";
require_once "plugins/PHPMailer/src/SMTP.php";
// Initiate PHPMailer
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
// Function to generate both crypto & URL safe random strings
function randomString($length = 16)
{
// Generate some cryptographically safe random bytes
// Generate a little more than requested as we'll lose some later converting
$random_bytes = random_bytes($length + 5);
// Convert the bytes to something somewhat human-readable
$random_base_64 = base64_encode($random_bytes);
// Replace the nasty characters that come with base64
$bad_chars = array("/", "+", "=");
$random_string = str_replace($bad_chars, random_int(0, 9), $random_base_64);
// Truncate the string to the requested $length and return
return substr($random_string, 0, $length);
}
// Older keygen function - only used for TOTP currently
function key32gen()
{
$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$chars .= "234567";
while (1) {
$key = '';
srand((float) microtime() * 1000000);
for ($i = 0; $i < 32; $i++) {
$key .= substr($chars, (rand() % (strlen($chars))), 1);
}
break;
}
return $key;
}
function nullable_htmlentities($unsanitizedInput)
{
return htmlentities($unsanitizedInput ?? '');
}
function initials($str)
{
if (!empty($str)) {
$ret = '';
foreach (explode(' ', $str) as $word)
$ret .= strtoupper($word[0]);
$ret = substr($ret, 0, 2);
return $ret;
}
}
function removeDirectory($path)
{
if (!file_exists($path)) {
return;
}
$files = glob($path . '/*');
foreach ($files as $file) {
is_dir($file) ? removeDirectory($file) : unlink($file);
}
rmdir($path);
}
function getUserAgent()
{
return $_SERVER['HTTP_USER_AGENT'];
}
function getIP()
{
if (defined("CONST_GET_IP_METHOD")) {
if (CONST_GET_IP_METHOD == "HTTP_X_FORWARDED_FOR") {
$ip = getenv('HTTP_X_FORWARDED_FOR');
} else {
$ip = $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['REMOTE_ADDR'];
}
} else {
$ip = $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['REMOTE_ADDR'];
}
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
exit("Potential Security Violation");
}
return $ip;
}
function getWebBrowser($user_browser)
{
$browser = "Unknown Browser";
$browser_array = array(
'/msie/i' => "<i class='fab fa-fw fa-internet-explorer text-secondary'></i> Internet Explorer",
'/firefox/i' => "<i class='fab fa-fw fa-firefox text-secondary'></i> Firefox",
'/safari/i' => "<i class='fab fa-fw fa-safari text-secondary'></i> Safari",
'/chrome/i' => "<i class='fab fa-fw fa-chrome text-secondary'></i> Chrome",
'/edg/i' => "<i class='fab fa-fw fa-edge text-secondary'></i> Edge",
'/opr/i' => "<i class='fab fa-fw fa-opera text-secondary'></i> Opera",
'/ddg/i' => "<i class='fas fa-fw fa-globe text-secondary'></i> DuckDuckGo"
);
foreach ($browser_array as $regex => $value) {
if (preg_match($regex, $user_browser)) {
$browser = $value;
}
}
return $browser;
}
function getOS($user_os)
{
$os_platform = "Unknown OS";
$os_array = array(
'/windows/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows",
'/macintosh|mac os x/i' => "<i class='fab fa-fw fa-apple text-secondary'></i> MacOS",
'/linux/i' => "<i class='fab fa-fw fa-linux text-secondary'></i> Linux",
'/ubuntu/i' => "<i class='fab fa-fw fa-ubuntu text-secondary'></i> Ubuntu",
'/fedora/i' => "<i class='fab fa-fw fa-fedora text-secondary'></i> Fedora",
'/iphone/i' => "<i class='fab fa-fw fa-apple text-secondary'></i> iPhone",
'/ipad/i' => "<i class='fab fa-fw fa-apple text-secondary'></i> iPad",
'/android/i' => "<i class='fab fa-fw fa-android text-secondary'></i> Android"
);
foreach ($os_array as $regex => $value) {
if (preg_match($regex, $user_os)) {
$os_platform = $value;
}
}
return $os_platform;
}
function getDevice()
{
$tablet_browser = 0;
$mobile_browser = 0;
if (preg_match('/(tablet|ipad|playbook)|(android(?!.*(mobi|opera mini)))/i', strtolower($_SERVER['HTTP_USER_AGENT']))) {
$tablet_browser++;
}
if (preg_match('/(up.browser|up.link|mmp|symbian|smartphone|midp|wap|phone|android|iemobile)/i', strtolower($_SERVER['HTTP_USER_AGENT']))) {
$mobile_browser++;
}
if ((strpos(strtolower($_SERVER['HTTP_ACCEPT']), 'application/vnd.wap.xhtml+xml') > 0) || ((isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])))) {
$mobile_browser++;
}
$mobile_ua = strtolower(substr(getUserAgent(), 0, 4));
$mobile_agents = array(
'w3c ', 'acs-', 'alav', 'alca', 'amoi', 'audi', 'avan', 'benq', 'bird', 'blac',
'blaz', 'brew', 'cell', 'cldc', 'cmd-', 'dang', 'doco', 'eric', 'hipt', 'inno',
'ipaq', 'java', 'jigs', 'kddi', 'keji', 'leno', 'lg-c', 'lg-d', 'lg-g', 'lge-',
'maui', 'maxo', 'midp', 'mits', 'mmef', 'mobi', 'mot-', 'moto', 'mwbp', 'nec-',
'newt', 'noki', 'palm', 'pana', 'pant', 'phil', 'play', 'port', 'prox',
'qwap', 'sage', 'sams', 'sany', 'sch-', 'sec-', 'send', 'seri', 'sgh-', 'shar',
'sie-', 'siem', 'smal', 'smar', 'sony', 'sph-', 'symb', 't-mo', 'teli', 'tim-',
'tosh', 'tsm-', 'upg1', 'upsi', 'vk-v', 'voda', 'wap-', 'wapa', 'wapi', 'wapp',
'wapr', 'webc', 'winw', 'winw', 'xda ', 'xda-'
);
if (in_array($mobile_ua, $mobile_agents)) {
$mobile_browser++;
}
if (strpos(strtolower(getUserAgent()), 'opera mini') > 0) {
$mobile_browser++;
//Check for tablets on Opera Mini alternative headers
$stock_ua = strtolower(isset($_SERVER['HTTP_X_OPERAMINI_PHONE_UA']) ? $_SERVER['HTTP_X_OPERAMINI_PHONE_UA'] : (isset($_SERVER['HTTP_DEVICE_STOCK_UA']) ? $_SERVER['HTTP_DEVICE_STOCK_UA'] : ''));
if (preg_match('/(tablet|ipad|playbook)|(android(?!.*mobile))/i', $stock_ua)) {
$tablet_browser++;
}
}
if ($tablet_browser > 0) {
//do something for tablet devices
return 'Tablet';
} else if ($mobile_browser > 0) {
//do something for mobile devices
return 'Mobile';
} else {
//do something for everything else
return 'Computer';
}
}
function truncate($text, $chars)
{
if (strlen($text) <= $chars) {
return $text;
}
$text = $text . " ";
$text = substr($text, 0, $chars);
$lastSpacePos = strrpos($text, ' ');
if ($lastSpacePos !== false) {
$text = substr($text, 0, $lastSpacePos);
}
return $text . "...";
}
function formatPhoneNumber($phoneNumber)
{
global $mysqli;
// Get Phone Mask Option
$phone_mask = mysqli_fetch_array(mysqli_query($mysqli, "SELECT config_phone_mask FROM settings WHERE company_id = 1"))[0];
if ($phone_mask == 0) {
return $phoneNumber;
}
$phoneNumber = $phoneNumber ? preg_replace('/[^0-9]/', '', $phoneNumber) : "";
if (strlen($phoneNumber) > 10) {
$countryCode = substr($phoneNumber, 0, strlen($phoneNumber) - 10);
$areaCode = substr($phoneNumber, -10, 3);
$nextThree = substr($phoneNumber, -7, 3);
$lastFour = substr($phoneNumber, -4, 4);
$phoneNumber = '+' . $countryCode . ' (' . $areaCode . ') ' . $nextThree . '-' . $lastFour;
} else if (strlen($phoneNumber) == 10) {
$areaCode = substr($phoneNumber, 0, 3);
$nextThree = substr($phoneNumber, 3, 3);
$lastFour = substr($phoneNumber, 6, 4);
$phoneNumber = '(' . $areaCode . ') ' . $nextThree . '-' . $lastFour;
} else if (strlen($phoneNumber) == 7) {
$nextThree = substr($phoneNumber, 0, 3);
$lastFour = substr($phoneNumber, 3, 4);
$phoneNumber = $nextThree . '-' . $lastFour;
}
return $phoneNumber;
}
function mkdirMissing($dir)
{
if (!is_dir($dir)) {
mkdir($dir);
}
}
// Called during initial setup
// Encrypts the master key with the user's password
function setupFirstUserSpecificKey($user_password, $site_encryption_master_key)
{
$iv = randomString();
$salt = randomString();
//Generate 128-bit (16 byte/char) kdhash of the users password
$user_password_kdhash = hash_pbkdf2('sha256', $user_password, $salt, 100000, 16);
//Encrypt the master key with the users kdf'd hash and the IV
$ciphertext = openssl_encrypt($site_encryption_master_key, 'aes-128-cbc', $user_password_kdhash, 0, $iv);
return $salt . $iv . $ciphertext;
}
/*
* For additional users / password changes (and now the API)
* New Users: Requires the admin setting up their account have a Specific/Session key configured
* Password Changes: Will use the current info in the session.
*/
function encryptUserSpecificKey($user_password)
{
$iv = randomString();
$salt = randomString();
// Get the session info.
$user_encryption_session_ciphertext = $_SESSION['user_encryption_session_ciphertext'];
$user_encryption_session_iv = $_SESSION['user_encryption_session_iv'];
$user_encryption_session_key = $_COOKIE['user_encryption_session_key'];
// Decrypt the session key to get the master key
$site_encryption_master_key = openssl_decrypt($user_encryption_session_ciphertext, 'aes-128-cbc', $user_encryption_session_key, 0, $user_encryption_session_iv);
// Generate 128-bit (16 byte/char) kdhash of the users (new) password
$user_password_kdhash = hash_pbkdf2('sha256', $user_password, $salt, 100000, 16);
// Encrypt the master key with the users kdf'd hash and the IV
$ciphertext = openssl_encrypt($site_encryption_master_key, 'aes-128-cbc', $user_password_kdhash, 0, $iv);
return $salt . $iv . $ciphertext;
}
// Given a ciphertext (incl. IV) and the user's (or API key) password, returns the site master key
// Ran at login, to facilitate generateUserSessionKey
function decryptUserSpecificKey($user_encryption_ciphertext, $user_password)
{
//Get the IV, salt and ciphertext
$salt = substr($user_encryption_ciphertext, 0, 16);
$iv = substr($user_encryption_ciphertext, 16, 16);
$ciphertext = substr($user_encryption_ciphertext, 32);
//Generate 128-bit (16 byte/char) kdhash of the users password
$user_password_kdhash = hash_pbkdf2('sha256', $user_password, $salt, 100000, 16);
//Use this hash to get the original/master key
return openssl_decrypt($ciphertext, 'aes-128-cbc', $user_password_kdhash, 0, $iv);
}
/*
Generates what is probably best described as a session key (ephemeral-ish)
- Allows us to store the master key on the server whilst the user is using the application, without prompting to type their password everytime they want to decrypt a credential
- Ciphertext/IV is stored on the server in the users' session, encryption key is controlled/provided by the user as a cookie
- Only the user can decrypt their session ciphertext to get the master key
- Encryption key never hits the disk in cleartext
*/
function generateUserSessionKey($site_encryption_master_key)
{
$user_encryption_session_key = randomString();
$user_encryption_session_iv = randomString();
$user_encryption_session_ciphertext = openssl_encrypt($site_encryption_master_key, 'aes-128-cbc', $user_encryption_session_key, 0, $user_encryption_session_iv);
// Store ciphertext in the user's session
$_SESSION['user_encryption_session_ciphertext'] = $user_encryption_session_ciphertext;
$_SESSION['user_encryption_session_iv'] = $user_encryption_session_iv;
// Give the user "their" key as a cookie
include 'config.php';
if ($config_https_only) {
setcookie("user_encryption_session_key", "$user_encryption_session_key", ['path' => '/', 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
} else {
setcookie("user_encryption_session_key", $user_encryption_session_key, 0, "/");
$_SESSION['alert_message'] = "Unencrypted connection flag set: Using non-secure cookies.";
}
}
// Decrypts an encrypted password (website/asset login), returns it as a string
function decryptLoginEntry($login_password_ciphertext)
{
// Split the login into IV and Ciphertext
$login_iv = substr($login_password_ciphertext, 0, 16);
$login_ciphertext = $salt = substr($login_password_ciphertext, 16);
// Get the user session info.
$user_encryption_session_ciphertext = $_SESSION['user_encryption_session_ciphertext'];
$user_encryption_session_iv = $_SESSION['user_encryption_session_iv'];
$user_encryption_session_key = $_COOKIE['user_encryption_session_key'];
// Decrypt the session key to get the master key
$site_encryption_master_key = openssl_decrypt($user_encryption_session_ciphertext, 'aes-128-cbc', $user_encryption_session_key, 0, $user_encryption_session_iv);
// Decrypt the login password using the master key
return openssl_decrypt($login_ciphertext, 'aes-128-cbc', $site_encryption_master_key, 0, $login_iv);
}
// Encrypts a website/asset login password
function encryptLoginEntry($login_password_cleartext)
{
$iv = randomString();
// Get the user session info.
$user_encryption_session_ciphertext = $_SESSION['user_encryption_session_ciphertext'];
$user_encryption_session_iv = $_SESSION['user_encryption_session_iv'];
$user_encryption_session_key = $_COOKIE['user_encryption_session_key'];
//Decrypt the session key to get the master key
$site_encryption_master_key = openssl_decrypt($user_encryption_session_ciphertext, 'aes-128-cbc', $user_encryption_session_key, 0, $user_encryption_session_iv);
//Encrypt the website/asset login using the master key
$ciphertext = openssl_encrypt($login_password_cleartext, 'aes-128-cbc', $site_encryption_master_key, 0, $iv);
return $iv . $ciphertext;
}
function apiDecryptLoginEntry($login_ciphertext, $api_key_decrypt_hash, #[\SensitiveParameter]$api_key_decrypt_password)
{
// Split the login entry (username/password) into IV and Ciphertext
$login_iv = substr($login_ciphertext, 0, 16);
$login_ciphertext = $salt = substr($login_ciphertext, 16);
// Decrypt the api hash to get the master key
$site_encryption_master_key = decryptUserSpecificKey($api_key_decrypt_hash, $api_key_decrypt_password);
// Decrypt the login password using the master key
return openssl_decrypt($login_ciphertext, 'aes-128-cbc', $site_encryption_master_key, 0, $login_iv);
}
function apiEncryptLoginEntry(#[\SensitiveParameter]$credential_cleartext, $api_key_decrypt_hash, #[\SensitiveParameter]$api_key_decrypt_password)
{
$iv = randomString();
// Decrypt the api hash to get the master key
$site_encryption_master_key = decryptUserSpecificKey($api_key_decrypt_hash, $api_key_decrypt_password);
// Encrypt the credential using the master key
$ciphertext = openssl_encrypt($credential_cleartext, 'aes-128-cbc', $site_encryption_master_key, 0, $iv);
return $iv . $ciphertext;
}
// Get domain general info (whois + NS/A/MX records)
function getDomainRecords($name)
{
$records = array();
// Only run if we think the domain is valid
if (!filter_var($name, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
$records['a'] = '';
$records['ns'] = '';
$records['mx'] = '';
$records['whois'] = '';
return $records;
}
$domain = escapeshellarg(str_replace('www.', '', $name));
$records['a'] = substr(trim(strip_tags(shell_exec("dig +short $domain"))), 0, 254);
$records['ns'] = substr(trim(strip_tags(shell_exec("dig +short NS $domain"))), 0, 254);
$records['mx'] = substr(trim(strip_tags(shell_exec("dig +short MX $domain"))), 0, 254);
$records['txt'] = substr(trim(strip_tags(shell_exec("dig +short TXT $domain"))), 0, 254);
$records['whois'] = substr(trim(strip_tags(shell_exec("whois -H $domain | sed 's/ //g' | head -30"))), 0, 254);
return $records;
}
// Used to automatically attempt to get SSL certificates as part of adding domains
// The logic for the fetch (sync) button on the client_certificates page is in ajax.php, and allows ports other than 443
function getSSL($full_name)
{
// Parse host and port
$name = parse_url("//$full_name", PHP_URL_HOST);
$port = parse_url("//$full_name", PHP_URL_PORT);
// Default port
if (!$port) {
$port = "443";
}
$certificate = array();
$certificate['success'] = false;
// Only run if we think the domain is valid
if (!filter_var($name, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
$certificate['expire'] = '';
$certificate['issued_by'] = '';
$certificate['public_key'] = '';
return $certificate;
}
// Get SSL/TSL certificate (using verify peer false to allow for self-signed certs) for domain on default port
$socket = "ssl://$name:$port";
$get = stream_context_create(array("ssl" => array("capture_peer_cert" => true, "verify_peer" => false,)));
$read = stream_socket_client($socket, $errno, $errstr, 5, STREAM_CLIENT_CONNECT, $get);
// If the socket connected
if ($read) {
$cert = stream_context_get_params($read);
$cert_public_key_obj = openssl_x509_parse($cert['options']['ssl']['peer_certificate']);
openssl_x509_export($cert['options']['ssl']['peer_certificate'], $export);
if ($cert_public_key_obj) {
$certificate['success'] = true;
$certificate['expire'] = date('Y-m-d', $cert_public_key_obj['validTo_time_t']);
$certificate['issued_by'] = strip_tags($cert_public_key_obj['issuer']['O']);
$certificate['public_key'] = $export;
}
}
return $certificate;
}
function strtoAZaz09($string)
{
// Gets rid of non-alphanumerics
return preg_replace('/[^A-Za-z0-9_-]/', '', $string);
}
// Cross-Site Request Forgery check for sensitive functions
// Validates the CSRF token provided matches the one in the users session
function validateCSRFToken($token)
{
if (hash_equals($token, $_SESSION['csrf_token'])) {
return true;
} else {
$_SESSION['alert_type'] = "warning";
$_SESSION['alert_message'] = "CSRF token verification failed. Try again, or log out to refresh your token.";
header("Location: index.php");
exit();
}
}
/*
* Role validation
* Admin - 3
* Tech - 2
* Accountant - 1
*/
function validateAdminRole()
{
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] != 3) {
$_SESSION['alert_type'] = "danger";
$_SESSION['alert_message'] = WORDING_ROLECHECK_FAILED;
header("Location: " . $_SERVER["HTTP_REFERER"]);
exit();
}
}
// Validates a user is a tech (or admin). Stops page load and attempts to direct away from the page if not (i.e. user is an accountant)
function validateTechRole()
{
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] == 1) {
$_SESSION['alert_type'] = "danger";
$_SESSION['alert_message'] = WORDING_ROLECHECK_FAILED;
header("Location: " . $_SERVER["HTTP_REFERER"]);
exit();
}
}
// Validates a user is an accountant (or admin). Stops page load and attempts to direct away from the page if not (i.e. user is a tech)
function validateAccountantRole()
{
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] == 2) {
$_SESSION['alert_type'] = "danger";
$_SESSION['alert_message'] = WORDING_ROLECHECK_FAILED;
header("Location: " . $_SERVER["HTTP_REFERER"]);
exit();
}
}
// Send a single email to a single recipient
function sendSingleEmail($config_smtp_host, $config_smtp_username, $config_smtp_password, $config_smtp_encryption, $config_smtp_port, $from_email, $from_name, $to_email, $to_name, $subject, $body, $ics_str)
{
$mail = new PHPMailer(true);
if (empty($config_smtp_username)) {
$smtp_auth = false;
} else {
$smtp_auth = true;
}
try {
// Mail Server Settings
$mail->CharSet = "UTF-8"; // Specify UTF-8 charset to ensure symbols ($/£) load correctly
$mail->SMTPDebug = 0; // No Debugging
$mail->isSMTP(); // Set mailer to use SMTP
$mail->Host = $config_smtp_host; // Specify SMTP server
$mail->SMTPAuth = $smtp_auth; // Enable SMTP authentication
$mail->Username = $config_smtp_username; // SMTP username
$mail->Password = $config_smtp_password; // SMTP password
$mail->SMTPSecure = $config_smtp_encryption; // Enable TLS encryption, `ssl` also accepted
$mail->Port = $config_smtp_port; // TCP port to connect to
//Recipients
$mail->setFrom($from_email, $from_name);
$mail->addAddress("$to_email", "$to_name"); // Add a recipient
// Content
$mail->isHTML(true); // Set email format to HTML
$mail->Subject = "$subject"; // Subject
$mail->Body = "<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
color: #333;
line-height: 1.6;
}
.email-container {
max-width: 600px;
margin: auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.header {
font-size: 18px;
margin-bottom: 20px;
}
.link-button {
display: inline-block;
background-color: #007bff;
color: #ffffff;
padding: 10px 20px;
text-decoration: none;
border-radius: 5px;
margin: 10px 0;
}
.footer {
font-size: 14px;
color: #666;
margin-top: 20px;
border-top: 1px solid #ddd;
padding-top: 10px;
}
.no-reply {
color: #999;
font-size: 12px;
}
</style>
</head>
<body>
<div class='email-container'>
$body
</div>
</body>
</html>
"; // Content
// Attachments - todo
//$mail->addAttachment('/var/tmp/file.tar.gz'); // Add attachments
//$mail->addAttachment('/tmp/image.jpg', 'new.jpg'); // Optional name
if (!empty($ics_str)) {
$mail->addStringAttachment($ics_str, 'Scheduled_ticket.ics', 'base64', 'text/calendar');
}
// Send
$mail->send();
// Return true if this was successful
return true;
} catch (Exception $e) {
// If we couldn't send the message return the error, so we can log it in the database (truncated)
error_log("ITFlow - Failed to send email: " . $mail->ErrorInfo);
return substr("Mailer Error: $mail->ErrorInfo", 0, 100) . "...";
}
}
function roundUpToNearestMultiple($n, $increment = 1000)
{
return (int) ($increment * ceil($n / $increment));
}
function getAssetIcon($asset_type)
{
if ($asset_type == 'Laptop') {
$device_icon = "laptop";
} elseif ($asset_type == 'Desktop') {
$device_icon = "desktop";
} elseif ($asset_type == 'Server') {
$device_icon = "server";
} elseif ($asset_type == 'Printer') {
$device_icon = "print";
} elseif ($asset_type == 'Camera') {
$device_icon = "video";
} elseif ($asset_type == 'Switch' || $asset_type == 'Firewall/Router') {
$device_icon = "network-wired";
} elseif ($asset_type == 'Access Point') {
$device_icon = "wifi";
} elseif ($asset_type == 'Phone') {
$device_icon = "phone";
} elseif ($asset_type == 'Mobile Phone') {
$device_icon = "mobile-alt";
} elseif ($asset_type == 'Tablet') {
$device_icon = "tablet-alt";
} elseif ($asset_type == 'TV') {
$device_icon = "tv";
} elseif ($asset_type == 'Virtual Machine') {
$device_icon = "cloud";
} else {
$device_icon = "tag";
}
return $device_icon;
}
function getInvoiceBadgeColor($invoice_status)
{
if ($invoice_status == "Sent") {
$invoice_badge_color = "warning text-white";
} elseif ($invoice_status == "Viewed") {
$invoice_badge_color = "info";
} elseif ($invoice_status == "Partial") {
$invoice_badge_color = "primary";
} elseif ($invoice_status == "Paid") {
$invoice_badge_color = "success";
} elseif ($invoice_status == "Cancelled") {
$invoice_badge_color = "danger";
} else {
$invoice_badge_color = "secondary";
}
return $invoice_badge_color;
}
// Pass $_FILE['file'] to check an uploaded file before saving it
function checkFileUpload($file, $allowed_extensions)
{
// Variables
$name = $file['name'];
$tmp = $file['tmp_name'];
$size = $file['size'];
$extarr = explode('.', $name);
$extension = strtolower(end($extarr));
// Check a file is actually attached/uploaded
if ($tmp === '') {
// No file uploaded
return false;
}
// Check the extension is allowed
if (!in_array($extension, $allowed_extensions)) {
// Extension not allowed
return false;
}
// Check the size is under 500 MB
$maxSizeBytes = 500 * 1024 * 1024; // 500 MB
if ($size > $maxSizeBytes) {
return "File size exceeds the limit.";
}
// Read the file content
$fileContent = file_get_contents($tmp);
// Hash the file content using SHA-256
$hashedContent = hash('sha256', $fileContent);
// Generate a secure filename using the hashed content
$secureFilename = $hashedContent . randomString(2) . '.' . $extension;
return $secureFilename;
}
function sanitizeInput($input)
{
global $mysqli;
// Remove HTML and PHP tags
$input = strip_tags((string) $input);
// Remove white space from beginning and end of input
$input = trim($input);
// Escape special characters
$input = mysqli_real_escape_string($mysqli, $input);
// Return sanitized input
return $input;
}
function sanitizeForEmail($data)
{
$sanitized = htmlspecialchars($data);
$sanitized = strip_tags($sanitized);
$sanitized = trim($sanitized);
return $sanitized;
}
function timeAgo($datetime)
{
if (is_null($datetime)) {
return "-";
}
$time = strtotime($datetime);
$difference = $time - time(); // Changed to handle future dates
if ($difference == 0) {
return 'right now';
}
$isFuture = $difference > 0; // Check if the date is in the future
$difference = abs($difference); // Absolute value for calculation
$timeRules = array(
31536000 => 'year',
2592000 => 'month',
604800 => 'week',
86400 => 'day',
3600 => 'hour',
60 => 'minute',
1 => 'second'
);
foreach ($timeRules as $secs => $str) {
$div = $difference / $secs;
if ($div >= 1) {
$t = round($div);
$timeStr = $t . ' ' . $str . ($t > 1 ? 's' : '');
return $isFuture ? 'in ' . $timeStr : $timeStr . ' ago';
}
}
}
// Function to remove Emojis in messages, this seems to break the mail queue
function removeEmoji($text)
{
return preg_replace('/\x{1F3F4}\x{E0067}\x{E0062}(?:\x{E0077}\x{E006C}\x{E0073}|\x{E0073}\x{E0063}\x{E0074}|\x{E0065}\x{E006E}\x{E0067})\x{E007F}|(?:\x{1F9D1}\x{1F3FF}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FF}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FF}\x{200D}\x{1FAF2})[\x{1F3FB}-\x{1F3FE}]|(?:\x{1F9D1}\x{1F3FE}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FE}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FE}\x{200D}\x{1FAF2})[\x{1F3FB}-\x{1F3FD}\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FD}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FD}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FD}\x{200D}\x{1FAF2})[\x{1F3FB}\x{1F3FC}\x{1F3FE}\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FC}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FC}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FC}\x{200D}\x{1FAF2})[\x{1F3FB}\x{1F3FD}-\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FB}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FB}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FB}\x{200D}\x{1FAF2})[\x{1F3FC}-\x{1F3FF}]|\x{1F468}(?:\x{1F3FB}(?:\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}])|\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}]))|\x{1F91D}\x{200D}\x{1F468}[\x{1F3FC}-\x{1F3FF}]|[\x{2695}\x{2696}\x{2708}]\x{FE0F}|[\x{2695}\x{2696}\x{2708}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]))?|[\x{1F3FC}-\x{1F3FF}]\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}])|\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}]))|\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F468}|[\x{1F468}\x{1F469}]\x{200D}(?:\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}])|\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FE}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FE}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FD}\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FD}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}\x{1F3FC}\x{1F3FE}\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FC}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}\x{1F3FD}-\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])\x{FE0F}|\x{200D}(?:[\x{1F468}\x{1F469}]\x{200D}[\x{1F466}\x{1F467}]|[\x{1F466}\x{1F467}])|\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{200D}[\x{2695}\x{2696}\x{2708}])?|(?:\x{1F469}(?:\x{1F3FB}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}]))|[\x{1F3FC}-\x{1F3FF}]\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])))|\x{1F9D1}[\x{1F3FB}-\x{1F3FF}]\x{200D}\x{1F91D}\x{200D}\x{1F9D1})[\x{1F3FB}-\x{1F3FF}]|\x{1F469}\x{200D}\x{1F469}\x{200D}(?:\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}])|\x{1F469}(?:\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}]))|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FE}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FD}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FC}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FB}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F9D1}(?:\x{200D}(?:\x{1F91D}\x{200D}\x{1F9D1}|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FE}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FD}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FC}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FB}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F469}\x{200D}\x{1F469}\x{200D}[\x{1F466}\x{1F467}]|\x{1F469}\x{200D}\x{1F467}\x{200D}[\x{1F466}\x{1F467}]|(?:\x{1F441}\x{FE0F}?\x{200D}\x{1F5E8}|\x{1F9D1}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F469}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F636}\x{200D}\x{1F32B}|\x{1F3F3}\x{FE0F}?\x{200D}\x{26A7}|\x{1F43B}\x{200D}\x{2744}|(?:[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}])\x{200D}[\x{2640}\x{2642}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}](?:[\x{FE0F}\x{1F3FB}-\x{1F3FF}]\x{200D}[\x{2640}\x{2642}]|\x{200D}[\x{2640}\x{2642}])|\x{1F3F4}\x{200D}\x{2620}|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93C}-\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]\x{200D}[\x{2640}\x{2642}]|[\xA9\xAE\x{203C}\x{2049}\x{2122}\x{2139}\x{2194}-\x{2199}\x{21A9}\x{21AA}\x{231A}\x{231B}\x{2328}\x{23CF}\x{23ED}-\x{23EF}\x{23F1}\x{23F2}\x{23F8}-\x{23FA}\x{24C2}\x{25AA}\x{25AB}\x{25B6}\x{25C0}\x{25FB}\x{25FC}\x{25FE}\x{2600}-\x{2604}\x{260E}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262A}\x{262E}\x{262F}\x{2638}-\x{263A}\x{2640}\x{2642}\x{2648}-\x{2653}\x{265F}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267B}\x{267E}\x{267F}\x{2692}\x{2694}-\x{2697}\x{2699}\x{269B}\x{269C}\x{26A0}\x{26A7}\x{26AA}\x{26B0}\x{26B1}\x{26BD}\x{26BE}\x{26C4}\x{26C8}\x{26CF}\x{26D1}\x{26D3}\x{26E9}\x{26F0}-\x{26F5}\x{26F7}\x{26F8}\x{26FA}\x{2702}\x{2708}\x{2709}\x{270F}\x{2712}\x{2714}\x{2716}\x{271D}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2763}\x{27A1}\x{2934}\x{2935}\x{2B05}-\x{2B07}\x{2B1B}\x{2B1C}\x{2B55}\x{3030}\x{303D}\x{3297}\x{3299}\x{1F004}\x{1F170}\x{1F171}\x{1F17E}\x{1F17F}\x{1F202}\x{1F237}\x{1F321}\x{1F324}-\x{1F32C}\x{1F336}\x{1F37D}\x{1F396}\x{1F397}\x{1F399}-\x{1F39B}\x{1F39E}\x{1F39F}\x{1F3CD}\x{1F3CE}\x{1F3D4}-\x{1F3DF}\x{1F3F5}\x{1F3F7}\x{1F43F}\x{1F4FD}\x{1F549}\x{1F54A}\x{1F56F}\x{1F570}\x{1F573}\x{1F576}-\x{1F579}\x{1F587}\x{1F58A}-\x{1F58D}\x{1F5A5}\x{1F5A8}\x{1F5B1}\x{1F5B2}\x{1F5BC}\x{1F5C2}-\x{1F5C4}\x{1F5D1}-\x{1F5D3}\x{1F5DC}-\x{1F5DE}\x{1F5E1}\x{1F5E3}\x{1F5E8}\x{1F5EF}\x{1F5F3}\x{1F5FA}\x{1F6CB}\x{1F6CD}-\x{1F6CF}\x{1F6E0}-\x{1F6E5}\x{1F6E9}\x{1F6F0}\x{1F6F3}])\x{FE0F}|\x{1F441}\x{FE0F}?\x{200D}\x{1F5E8}|\x{1F9D1}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F469}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F3F3}\x{FE0F}?\x{200D}\x{1F308}|\x{1F469}\x{200D}\x{1F467}|\x{1F469}\x{200D}\x{1F466}|\x{1F636}\x{200D}\x{1F32B}|\x{1F3F3}\x{FE0F}?\x{200D}\x{26A7}|\x{1F635}\x{200D}\x{1F4AB}|\x{1F62E}\x{200D}\x{1F4A8}|\x{1F415}\x{200D}\x{1F9BA}|\x{1FAF1}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F9D1}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F469}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F43B}\x{200D}\x{2744}|(?:[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}])\x{200D}[\x{2640}\x{2642}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}](?:[\x{FE0F}\x{1F3FB}-\x{1F3FF}]\x{200D}[\x{2640}\x{2642}]|\x{200D}[\x{2640}\x{2642}])|\x{1F3F4}\x{200D}\x{2620}|\x{1F1FD}\x{1F1F0}|\x{1F1F6}\x{1F1E6}|\x{1F1F4}\x{1F1F2}|\x{1F408}\x{200D}\x{2B1B}|\x{2764}(?:\x{FE0F}\x{200D}[\x{1F525}\x{1FA79}]|\x{200D}[\x{1F525}\x{1FA79}])|\x{1F441}\x{FE0F}?|\x{1F3F3}\x{FE0F}?|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93C}-\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]\x{200D}[\x{2640}\x{2642}]|\x{1F1FF}[\x{1F1E6}\x{1F1F2}\x{1F1FC}]|\x{1F1FE}[\x{1F1EA}\x{1F1F9}]|\x{1F1FC}[\x{1F1EB}\x{1F1F8}]|\x{1F1FB}[\x{1F1E6}\x{1F1E8}\x{1F1EA}\x{1F1EC}\x{1F1EE}\x{1F1F3}\x{1F1FA}]|\x{1F1FA}[\x{1F1E6}\x{1F1EC}\x{1F1F2}\x{1F1F3}\x{1F1F8}\x{1F1FE}\x{1F1FF}]|\x{1F1F9}[\x{1F1E6}\x{1F1E8}\x{1F1E9}\x{1F1EB}-\x{1F1ED}\x{1F1EF}-\x{1F1F4}\x{1F1F7}\x{1F1F9}\x{1F1FB}\x{1F1FC}\x{1F1FF}]|\x{1F1F8}[\x{1F1E6}-\x{1F1EA}\x{1F1EC}-\x{1F1F4}\x{1F1F7}-\x{1F1F9}\x{1F1FB}\x{1F1FD}-\x{1F1FF}]|\x{1F1F7}[\x{1F1EA}\x{1F1F4}\x{1F1F8}\x{1F1FA}\x{1F1FC}]|\x{1F1F5}[\x{1F1E6}\x{1F1EA}-\x{1F1ED}\x{1F1F0}-\x{1F1F3}\x{1F1F7}-\x{1F1F9}\x{1F1FC}\x{1F1FE}]|\x{1F1F3}[\x{1F1E6}\x{1F1E8}\x{1F1EA}-\x{1F1EC}\x{1F1EE}\x{1F1F1}\x{1F1F4}\x{1F1F5}\x{1F1F7}\x{1F1FA}\x{1F1FF}]|\x{1F1F2}[\x{1F1E6}\x{1F1E8}-\x{1F1ED}\x{1F1F0}-\x{1F1FF}]|\x{1F1F1}[\x{1F1E6}-\x{1F1E8}\x{1F1EE}\x{1F1F0}\x{1F1F7}-\x{1F1FB}\x{1F1FE}]|\x{1F1F0}[\x{1F1EA}\x{1F1EC}-\x{1F1EE}\x{1F1F2}\x{1F1F3}\x{1F1F5}\x{1F1F7}\x{1F1FC}\x{1F1FE}\x{1F1FF}]|\x{1F1EF}[\x{1F1EA}\x{1F1F2}\x{1F1F4}\x{1F1F5}]|\x{1F1EE}[\x{1F1E8}-\x{1F1EA}\x{1F1F1}-\x{1F1F4}\x{1F1F6}-\x{1F1F9}]|\x{1F1ED}[\x{1F1F0}\x{1F1F2}\x{1F1F3}\x{1F1F7}\x{1F1F9}\x{1F1FA}]|\x{1F1EC}[\x{1F1E6}\x{1F1E7}\x{1F1E9}-\x{1F1EE}\x{1F1F1}-\x{1F1F3}\x{1F1F5}-\x{1F1FA}\x{1F1FC}\x{1F1FE}]|\x{1F1EB}[\x{1F1EE}-\x{1F1F0}\x{1F1F2}\x{1F1F4}\x{1F1F7}]|\x{1F1EA}[\x{1F1E6}\x{1F1E8}\x{1F1EA}\x{1F1EC}\x{1F1ED}\x{1F1F7}-\x{1F1FA}]|\x{1F1E9}[\x{1F1EA}\x{1F1EC}\x{1F1EF}\x{1F1F0}\x{1F1F2}\x{1F1F4}\x{1F1FF}]|\x{1F1E8}[\x{1F1E6}\x{1F1E8}\x{1F1E9}\x{1F1EB}-\x{1F1EE}\x{1F1F0}-\x{1F1F5}\x{1F1F7}\x{1F1FA}-\x{1F1FF}]|\x{1F1E7}[\x{1F1E6}\x{1F1E7}\x{1F1E9}-\x{1F1EF}\x{1F1F1}-\x{1F1F4}\x{1F1F6}-\x{1F1F9}\x{1F1FB}\x{1F1FC}\x{1F1FE}\x{1F1FF}]|\x{1F1E6}[\x{1F1E8}-\x{1F1EC}\x{1F1EE}\x{1F1F1}\x{1F1F2}\x{1F1F4}\x{1F1F6}-\x{1F1FA}\x{1F1FC}\x{1F1FD}\x{1F1FF}]|[#\*0-9]\x{FE0F}?\x{20E3}|\x{1F93C}[\x{1F3FB}-\x{1F3FF}]|\x{2764}\x{FE0F}?|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}][\x{FE0F}\x{1F3FB}-\x{1F3FF}]?|\x{1F3F4}|[\x{270A}\x{270B}\x{1F385}\x{1F3C2}\x{1F3C7}\x{1F442}\x{1F443}\x{1F446}-\x{1F450}\x{1F466}\x{1F467}\x{1F46B}-\x{1F46D}\x{1F472}\x{1F474}-\x{1F476}\x{1F478}\x{1F47C}\x{1F483}\x{1F485}\x{1F48F}\x{1F491}\x{1F4AA}\x{1F57A}\x{1F595}\x{1F596}\x{1F64C}\x{1F64F}\x{1F6C0}\x{1F6CC}\x{1F90C}\x{1F90F}\x{1F918}-\x{1F91F}\x{1F930}-\x{1F934}\x{1F936}\x{1F977}\x{1F9B5}\x{1F9B6}\x{1F9BB}\x{1F9D2}\x{1F9D3}\x{1F9D5}\x{1FAC3}-\x{1FAC5}\x{1FAF0}\x{1FAF2}-\x{1FAF6}][\x{1F3FB}-\x{1F3FF}]|[\x{261D}\x{270C}\x{270D}\x{1F574}\x{1F590}][\x{FE0F}\x{1F3FB}-\x{1F3FF}]|[\x{261D}\x{270A}-\x{270D}\x{1F385}\x{1F3C2}\x{1F3C7}\x{1F408}\x{1F415}\x{1F43B}\x{1F442}\x{1F443}\x{1F446}-\x{1F450}\x{1F466}\x{1F467}\x{1F46B}-\x{1F46D}\x{1F472}\x{1F474}-\x{1F476}\x{1F478}\x{1F47C}\x{1F483}\x{1F485}\x{1F48F}\x{1F491}\x{1F4AA}\x{1F574}\x{1F57A}\x{1F590}\x{1F595}\x{1F596}\x{1F62E}\x{1F635}\x{1F636}\x{1F64C}\x{1F64F}\x{1F6C0}\x{1F6CC}\x{1F90C}\x{1F90F}\x{1F918}-\x{1F91F}\x{1F930}-\x{1F934}\x{1F936}\x{1F93C}\x{1F977}\x{1F9B5}\x{1F9B6}\x{1F9BB}\x{1F9D2}\x{1F9D3}\x{1F9D5}\x{1FAC3}-\x{1FAC5}\x{1FAF0}\x{1FAF2}-\x{1FAF6}]|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}]|[\xA9\xAE\x{203C}\x{2049}\x{2122}\x{2139}\x{2194}-\x{2199}\x{21A9}\x{21AA}\x{231A}\x{231B}\x{2328}\x{23CF}\x{23ED}-\x{23EF}\x{23F1}\x{23F2}\x{23F8}-\x{23FA}\x{24C2}\x{25AA}\x{25AB}\x{25B6}\x{25C0}\x{25FB}\x{25FC}\x{25FE}\x{2600}-\x{2604}\x{260E}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262A}\x{262E}\x{262F}\x{2638}-\x{263A}\x{2640}\x{2642}\x{2648}-\x{2653}\x{265F}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267B}\x{267E}\x{267F}\x{2692}\x{2694}-\x{2697}\x{2699}\x{269B}\x{269C}\x{26A0}\x{26A7}\x{26AA}\x{26B0}\x{26B1}\x{26BD}\x{26BE}\x{26C4}\x{26C8}\x{26CF}\x{26D1}\x{26D3}\x{26E9}\x{26F0}-\x{26F5}\x{26F7}\x{26F8}\x{26FA}\x{2702}\x{2708}\x{2709}\x{270F}\x{2712}\x{2714}\x{2716}\x{271D}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2763}\x{27A1}\x{2934}\x{2935}\x{2B05}-\x{2B07}\x{2B1B}\x{2B1C}\x{2B55}\x{3030}\x{303D}\x{3297}\x{3299}\x{1F004}\x{1F170}\x{1F171}\x{1F17E}\x{1F17F}\x{1F202}\x{1F237}\x{1F321}\x{1F324}-\x{1F32C}\x{1F336}\x{1F37D}\x{1F396}\x{1F397}\x{1F399}-\x{1F39B}\x{1F39E}\x{1F39F}\x{1F3CD}\x{1F3CE}\x{1F3D4}-\x{1F3DF}\x{1F3F5}\x{1F3F7}\x{1F43F}\x{1F4FD}\x{1F549}\x{1F54A}\x{1F56F}\x{1F570}\x{1F573}\x{1F576}-\x{1F579}\x{1F587}\x{1F58A}-\x{1F58D}\x{1F5A5}\x{1F5A8}\x{1F5B1}\x{1F5B2}\x{1F5BC}\x{1F5C2}-\x{1F5C4}\x{1F5D1}-\x{1F5D3}\x{1F5DC}-\x{1F5DE}\x{1F5E1}\x{1F5E3}\x{1F5E8}\x{1F5EF}\x{1F5F3}\x{1F5FA}\x{1F6CB}\x{1F6CD}-\x{1F6CF}\x{1F6E0}-\x{1F6E5}\x{1F6E9}\x{1F6F0}\x{1F6F3}]|[\x{23E9}-\x{23EC}\x{23F0}\x{23F3}\x{25FD}\x{2693}\x{26A1}\x{26AB}\x{26C5}\x{26CE}\x{26D4}\x{26EA}\x{26FD}\x{2705}\x{2728}\x{274C}\x{274E}\x{2753}-\x{2755}\x{2757}\x{2795}-\x{2797}\x{27B0}\x{27BF}\x{2B50}\x{1F0CF}\x{1F18E}\x{1F191}-\x{1F19A}\x{1F201}\x{1F21A}\x{1F22F}\x{1F232}-\x{1F236}\x{1F238}-\x{1F23A}\x{1F250}\x{1F251}\x{1F300}-\x{1F320}\x{1F32D}-\x{1F335}\x{1F337}-\x{1F37C}\x{1F37E}-\x{1F384}\x{1F386}-\x{1F393}\x{1F3A0}-\x{1F3C1}\x{1F3C5}\x{1F3C6}\x{1F3C8}\x{1F3C9}\x{1F3CF}-\x{1F3D3}\x{1F3E0}-\x{1F3F0}\x{1F3F8}-\x{1F407}\x{1F409}-\x{1F414}\x{1F416}-\x{1F43A}\x{1F43C}-\x{1F43E}\x{1F440}\x{1F444}\x{1F445}\x{1F451}-\x{1F465}\x{1F46A}\x{1F479}-\x{1F47B}\x{1F47D}-\x{1F480}\x{1F484}\x{1F488}-\x{1F48E}\x{1F490}\x{1F492}-\x{1F4A9}\x{1F4AB}-\x{1F4FC}\x{1F4FF}-\x{1F53D}\x{1F54B}-\x{1F54E}\x{1F550}-\x{1F567}\x{1F5A4}\x{1F5FB}-\x{1F62D}\x{1F62F}-\x{1F634}\x{1F637}-\x{1F644}\x{1F648}-\x{1F64A}\x{1F680}-\x{1F6A2}\x{1F6A4}-\x{1F6B3}\x{1F6B7}-\x{1F6BF}\x{1F6C1}-\x{1F6C5}\x{1F6D0}-\x{1F6D2}\x{1F6D5}-\x{1F6D7}\x{1F6DD}-\x{1F6DF}\x{1F6EB}\x{1F6EC}\x{1F6F4}-\x{1F6FC}\x{1F7E0}-\x{1F7EB}\x{1F7F0}\x{1F90D}\x{1F90E}\x{1F910}-\x{1F917}\x{1F920}-\x{1F925}\x{1F927}-\x{1F92F}\x{1F93A}\x{1F93F}-\x{1F945}\x{1F947}-\x{1F976}\x{1F978}-\x{1F9B4}\x{1F9B7}\x{1F9BA}\x{1F9BC}-\x{1F9CC}\x{1F9D0}\x{1F9E0}-\x{1F9FF}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7C}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAAC}\x{1FAB0}-\x{1FABA}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD9}\x{1FAE0}-\x{1FAE7}]/u', '', $text);
}
function shortenClient($client)
{
// Pre-process by removing any non-alphanumeric characters except for certain punctuations.
$client = html_entity_decode($client); // Decode any HTML entities
$client = str_replace("'", "", $client); // Removing all occurrences of '
$cleaned = preg_replace('/[^a-zA-Z0-9&]+/', ' ', $client);
// Break into words.
$words = explode(' ', trim($cleaned));
$shortened = '';
// If there's only one word.
if (count($words) == 1) {
$word = $words[0];
if (strlen($word) <= 3) {
return strtoupper($word);
}
// Prefer starting and ending characters.
$shortened = $word[0] . substr($word, -2);
} else {
// Less weightage to common words.
$commonWords = ['the', 'of', 'and'];
foreach ($words as $word) {
if (!in_array(strtolower($word), $commonWords) || strlen($shortened) < 2) {
$shortened .= $word[0];
}
}
// If there are still not enough characters, take from the last word.
while (strlen($shortened) < 3 && !empty($word)) {
$shortened .= substr($word, 1, 1);
$word = substr($word, 1);
}
}
return strtoupper(substr($shortened, 0, 3));
}
function roundToNearest15($time)
{
// Validate the input time format
if (!preg_match('/^(\d{2}):(\d{2}):(\d{2})$/', $time, $matches)) {
return false; // or throw an exception
}
// Extract hours, minutes, and seconds from the matched time string
list(, $hours, $minutes, $seconds) = $matches;
// Convert everything to seconds for easier calculation
$totalSeconds = ($hours * 3600) + ($minutes * 60) + $seconds;
// Calculate the remainder when divided by 900 seconds (15 minutes)
$remainder = $totalSeconds % 900;
if ($remainder > 450) { // If remainder is more than 7.5 minutes (450 seconds), round up
$totalSeconds += (900 - $remainder);
} else { // Else round down
$totalSeconds -= $remainder;
}
// Convert total seconds to decimal hours
$decimalHours = $totalSeconds / 3600;
// Return the decimal hours
return number_format($decimalHours, 2);
}
// Get the value of a setting from the database
function getSettingValue($mysqli, $setting_name)
{
//if starts with config_ then get from config table
if (substr($setting_name, 0, 7) == "config_") {
$sql = mysqli_query($mysqli, "SELECT $setting_name FROM settings");
$row = mysqli_fetch_array($sql);
return $row[$setting_name];
} elseif (substr($setting_name, 0, 7) == "company") {
$sql = mysqli_query($mysqli, "SELECT $setting_name FROM companies");
$row = mysqli_fetch_array($sql);
return $row[$setting_name];
} else {
return "Cannot Find Setting Name";
}
}
function getMonthlyTax($tax_name, $month, $year, $mysqli)
{
// SQL to calculate monthly tax
$sql = "SELECT SUM(item_tax) AS monthly_tax FROM invoice_items
LEFT JOIN invoices ON invoice_items.item_invoice_id = invoices.invoice_id
LEFT JOIN payments ON invoices.invoice_id = payments.payment_invoice_id
WHERE YEAR(payments.payment_date) = $year AND MONTH(payments.payment_date) = $month
AND invoice_items.item_tax_id = (SELECT tax_id FROM taxes WHERE tax_name = '$tax_name')";
$result = mysqli_query($mysqli, $sql);
$row = mysqli_fetch_assoc($result);
return $row['monthly_tax'] ?? 0;
}
function getQuarterlyTax($tax_name, $quarter, $year, $mysqli)
{
// Calculate start and end months for the quarter
$start_month = ($quarter - 1) * 3 + 1;
$end_month = $start_month + 2;
// SQL to calculate quarterly tax
$sql = "SELECT SUM(item_tax) AS quarterly_tax FROM invoice_items
LEFT JOIN invoices ON invoice_items.item_invoice_id = invoices.invoice_id
LEFT JOIN payments ON invoices.invoice_id = payments.payment_invoice_id
WHERE YEAR(payments.payment_date) = $year AND MONTH(payments.payment_date) BETWEEN $start_month AND $end_month
AND invoice_items.item_tax_id = (SELECT tax_id FROM taxes WHERE tax_name = '$tax_name')";
$result = mysqli_query($mysqli, $sql);
$row = mysqli_fetch_assoc($result);
return $row['quarterly_tax'] ?? 0;
}
function getTotalTax($tax_name, $year, $mysqli)
{
// SQL to calculate total tax
$sql = "SELECT SUM(item_tax) AS total_tax FROM invoice_items
LEFT JOIN invoices ON invoice_items.item_invoice_id = invoices.invoice_id
LEFT JOIN payments ON invoices.invoice_id = payments.payment_invoice_id
WHERE YEAR(payments.payment_date) = $year
AND invoice_items.item_tax_id = (SELECT tax_id FROM taxes WHERE tax_name = '$tax_name')";
$result = mysqli_query($mysqli, $sql);
$row = mysqli_fetch_assoc($result);
return $row['total_tax'] ?? 0;
}
//Get account currency code
function getAccountCurrencyCode($mysqli, $account_id)
{
$sql = mysqli_query($mysqli, "SELECT account_currency_code FROM accounts WHERE account_id = $account_id");
$row = mysqli_fetch_array($sql);
$account_currency_code = nullable_htmlentities($row['account_currency_code']);
return $account_currency_code;
}
function calculateAccountBalance($mysqli, $account_id)
{
$sql_account = mysqli_query($mysqli, "SELECT * FROM accounts LEFT JOIN account_types ON accounts.account_type = account_types.account_type_id WHERE account_archived_at IS NULL AND account_id = $account_id ORDER BY account_name ASC; ");
$row = mysqli_fetch_array($sql_account);
$opening_balance = floatval($row['opening_balance']);
$account_id = intval($row['account_id']);
$sql_payments = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS total_payments FROM payments WHERE payment_account_id = $account_id");
$row = mysqli_fetch_array($sql_payments);
$total_payments = floatval($row['total_payments']);
$sql_revenues = mysqli_query($mysqli, "SELECT SUM(revenue_amount) AS total_revenues FROM revenues WHERE revenue_account_id = $account_id");
$row = mysqli_fetch_array($sql_revenues);
$total_revenues = floatval($row['total_revenues']);
$sql_expenses = mysqli_query($mysqli, "SELECT SUM(expense_amount) AS total_expenses FROM expenses WHERE expense_account_id = $account_id");
$row = mysqli_fetch_array($sql_expenses);
$total_expenses = floatval($row['total_expenses']);
$balance = $opening_balance + $total_payments + $total_revenues - $total_expenses;
if ($balance == '') {
$balance = '0.00';
}
return $balance;
}
function generateReadablePassword($security_level)
{
// Cap security level at 5
$security_level = intval($security_level);
$security_level = min($security_level, 5);
// Arrays of words
$articles = ['The', 'A'];
$adjectives = ['Smart', 'Swift', 'Secure', 'Stable', 'Digital', 'Virtual', 'Active', 'Dynamic', 'Innovative', 'Efficient', 'Portable', 'Wireless', 'Rapid', 'Intuitive', 'Automated', 'Robust', 'Reliable', 'Sleek', 'Modern', 'Happy', 'Funny', 'Quick', 'Bright', 'Clever', 'Gentle', 'Brave', 'Calm', 'Eager', 'Fierce', 'Kind', 'Lucky', 'Proud', 'Silly', 'Witty', 'Bold', 'Curious', 'Elated', 'Gracious', 'Honest', 'Jolly', 'Merry', 'Noble', 'Optimistic', 'Playful', 'Quirky', 'Rustic', 'Steady', 'Tranquil', 'Upbeat'];
$nouns = ['Computer', 'Laptop', 'Tablet', 'Server', 'Router', 'Software', 'Hardware', 'Pixel', 'Byte', 'App', 'Network', 'Cloud', 'Firewall', 'Email', 'Database', 'Folder', 'Document', 'Interface', 'Program', 'Gadget', 'Dinosaur', 'Tiger', 'Elephant', 'Kangaroo', 'Monkey', 'Unicorn', 'Dragon', 'Puppy', 'Kitten', 'Parrot', 'Lion', 'Bear', 'Fox', 'Wolf', 'Rabbit', 'Deer', 'Owl', 'Hedgehog', 'Turtle', 'Frog', 'Butterfly', 'Panda', 'Giraffe', 'Zebra', 'Peacock', 'Koala', 'Raccoon', 'Squirrel', 'Hippo', 'Rhino', 'Book', "Monitor"];
$verbs = ['Connects', 'Runs', 'Processes', 'Secures', 'Encrypts', 'Saves', 'Updates', 'Boots', 'Scans', 'Compiles', 'Executes', 'Restores', 'Installs', 'Configures', 'Downloads', 'Streams', 'BacksUp', 'Syncs', 'Browses', 'Navigates', 'Runs', 'Jumps', 'Flies', 'Swims', 'Dances', 'Sings', 'Hops', 'Skips', 'Races', 'Climbs', 'Crawls', 'Glides', 'Twirls', 'Swings', 'Sprints', 'Gallops', 'Trots', 'Wanders', 'Strolls', 'Marches'];
$adverbs = ['Quickly', 'Slowly', 'Gracefully', 'Wildly', 'Loudly', 'Silently', 'Cheerfully', 'Eagerly', 'Gently', 'Happily', 'Jovially', 'Kindly', 'Lazily', 'Merrily', 'Neatly', 'Politely', 'Quietly', 'Rapidly', 'Smoothly', 'Tightly', 'Swiftly', 'Securely', 'Efficiently', 'Rapidly', 'Smoothly', 'Reliably', 'Safely', 'Wirelessly', 'Instantly', 'Silently', 'Automatically', 'Seamlessly', 'Digitally', 'Virtually', 'Continuously', 'Regularly', 'Intelligently', 'Logically'];
// Randomly select words from arrays
$adj = $adjectives[array_rand($adjectives)];
$noun = $nouns[array_rand($nouns)];
$verb = $verbs[array_rand($verbs)];
$adv = $adverbs[array_rand($adverbs)];
// Combine to create a base password
$password = $adj . $noun . $verb . $adv;
// Select an article randomly
$article = $articles[array_rand($articles)];
// Determine if we should use 'An' instead of 'A'
if ($article == 'A' && preg_match('/^[aeiouAEIOU]/', $adj)) {
$article = 'An';
}
// Add the article to the password
$password = $article . $password;
// Mapping of letters to special characters and numbers
$mappings = [
'A' => '@', 'a' => '@',
'E' => '3', 'e' => '3',
'I' => '!', 'i' => '!',
'O' => '0', 'o' => '0',
'S' => '$', 's' => '$',
'T' => '+', 't' => '+',
'B' => '8', 'b' => '8'
];
// Generate an array of indices based on the password length
$indices = range(0, strlen($password) - 1);
// Randomly shuffle the indices
shuffle($indices);
// Iterate through the shuffled indices and replace characters based on the security level
for ($i = 0; $i < min($security_level, strlen($password)); $i++) {
$index = $indices[$i]; // Get a random index
$currentChar = $password[$index]; // Get the character at this index
// Check if the current character has a mapping and replace it
if (array_key_exists($currentChar, $mappings)) {
$password[$index] = $mappings[$currentChar];
}
}
// Add as many random numbers as the security level
$password .= rand(pow(10, $security_level - 1), pow(10, $security_level) - 1);
return $password;
}
function addToMailQueue($mysqli, $data) {
foreach ($data as $email) {
$from = strval($email['from']);
$from_name = strval($email['from_name']);
$recipient = strval($email['recipient']);
$recipient_name = strval($email['recipient_name']);
$subject = strval($email['subject']);
$body = strval($email['body']);
$cal_str = '';
if (isset($email['cal_str'])) {
$cal_str = mysqli_escape_string($mysqli, $email['cal_str']);
}
// Check if 'email_queued_at' is set and not empty
if (isset($email['queued_at']) && !empty($email['queued_at'])) {
$queued_at = "'" . sanitizeInput($email['queued_at']) . "'";
} else {
// Use the current date and time if 'email_queued_at' is not set or empty
$queued_at = 'CURRENT_TIMESTAMP()';
}
mysqli_query($mysqli, "INSERT INTO email_queue SET email_recipient = '$recipient', email_recipient_name = '$recipient_name', email_from = '$from', email_from_name = '$from_name', email_subject = '$subject', email_content = '$body', email_queued_at = $queued_at, email_cal_str = '$cal_str'");
}
return true;
}
function calculateInvoiceBalance($mysqli, $invoice_id)
{
$invoice_id_int = intval($invoice_id);
$sql_invoice = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_id = $invoice_id_int");
$row = mysqli_fetch_array($sql_invoice);
$invoice_amount = floatval($row['invoice_amount']);
$sql_payments = mysqli_query(
$mysqli,
"SELECT SUM(payment_amount) AS total_payments FROM payments
WHERE payment_invoice_id = $invoice_id
"
);
$row = mysqli_fetch_array($sql_payments);
$total_payments = floatval($row['total_payments']);
$balance = $invoice_amount - $total_payments;
if ($balance == '') {
$balance = '0.00';
}
return $balance;
}
function createiCalStr($datetime, $title, $description, $location)
{
require_once "plugins/zapcal/zapcallib.php";
// Create the iCal object
$cal_event = new ZCiCal();
$event = new ZCiCalNode("VEVENT", $cal_event->curnode);
// Set the method to REQUEST to indicate an invite
$event->addNode(new ZCiCalDataNode("METHOD:REQUEST"));
$event->addNode(new ZCiCalDataNode("SUMMARY:" . $title));
$event->addNode(new ZCiCalDataNode("DTSTART:" . ZCiCal::fromSqlDateTime($datetime)));
// Assuming the end time is the same as start time.
// Todo: adjust this for actual duration
$event->addNode(new ZCiCalDataNode("DTEND:" . ZCiCal::fromSqlDateTime($datetime)));
$event->addNode(new ZCiCalDataNode("DTSTAMP:" . ZCiCal::fromSqlDateTime()));
$uid = date('Y-m-d-H-i-s') . "@" . $_SERVER['SERVER_NAME'];
$event->addNode(new ZCiCalDataNode("UID:" . $uid));
$event->addNode(new ZCiCalDataNode("LOCATION:" . $location));
$event->addNode(new ZCiCalDataNode("DESCRIPTION:" . $description));
// Todo: add organizer details
// $event->addNode(new ZCiCalDataNode("ORGANIZER;CN=Organizer Name:MAILTO:organizer@example.com"));
// Return the iCal string
return $cal_event->export();
}
function isMobile()
{
// Check if the user agent is a mobile device
return preg_match('/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|opera mini|palm|phone|pie|tablet|up.browser|up.link|webos|wos)/i', $_SERVER['HTTP_USER_AGENT']);
}
function createiCalStrCancel($originaliCalStr) {
require_once "plugins/zapcal/zapcallib.php";
// Import the original iCal string
$cal_event = new ZCiCal($originaliCalStr);
// Iterate through the iCalendar object to find VEVENT nodes
foreach($cal_event->tree->child as $node) {
if($node->getName() == "VEVENT") {
// Check if STATUS node exists, update it, or add a new one
$statusFound = false;
foreach($node->data as $key => $value) {
if($key == "STATUS") {
$value->setValue("CANCELLED");
$statusFound = true;
break; // Exit the loop once the STATUS is updated
}
}
// If STATUS node is not found, add a new STATUS node
if (!$statusFound) {
$node->addNode(new ZCiCalDataNode("STATUS:CANCELLED"));
}
}
}
// Return the modified iCal string
return $cal_event->export();
}
function getTicketStatusColor($ticket_status) {
global $mysqli;
$status_id = intval($ticket_status);
$row = mysqli_fetch_array(mysqli_query($mysqli, "SELECT ticket_status_color FROM ticket_statuses WHERE ticket_status_id = $status_id LIMIT 1"));
if ($row) {
return nullable_htmlentities($row['ticket_status_color']);
}
// Default return
return "Unknown";
}
function getTicketStatusName($ticket_status) {
global $mysqli;
$status_id = intval($ticket_status);
$row = mysqli_fetch_array(mysqli_query($mysqli, "SELECT * FROM ticket_statuses WHERE ticket_status_id = $status_id LIMIT 1"));
if ($row) {
return nullable_htmlentities($row['ticket_status_name']);
}
// Default return
return "Unknown";
}
function fetchUpdates() {
global $repo_branch;
// Fetch the latest code changes but don't apply them
exec("git fetch", $output, $result);
$latest_version = exec("git rev-parse origin/$repo_branch");
$current_version = exec("git rev-parse HEAD");
if ($current_version == $latest_version) {
$update_message = "No Updates available";
} else {
$update_message = "New Updates are Available [$latest_version]";
}
$updates = new stdClass();
$updates->output = $output;
$updates->result = $result;
$updates->current_version = $current_version;
$updates->latest_version = $latest_version;
$updates->update_message = $update_message;
return $updates;
}
function getDomainExpirationDate($domain) {
// Execute the whois command
$result = shell_exec("whois " . escapeshellarg($domain));
if (!$result) {
return null; // Return null if WHOIS query fails
}
$expireDate = '';
// Regular expressions to match different date formats
$patterns = [
'/Expiration Date: (.+)/',
'/Registry Expiry Date: (.+)/',
'/expires: (.+)/',
'/Expiry Date: (.+)/',
'/renewal date: (.+)/',
'/Expires On: (.+)/',
'/paid-till: (.+)/',
'/Expiration Time: (.+)/',
'/\[Expires on\]\s+(.+)/',
'/expire: (.+)/',
'/validity: (.+)/',
'/Expires on.*: (.+)/i',
'/Expiry on.*: (.+)/i',
'/renewal: (.+)/i',
'/Expir\w+ Date: (.+)/i',
'/Valid Until: (.+)/i',
'/Valid until: (.+)/i',
'/expire-date: (.+)/i',
'/Expiration Date: (.+)/i',
'/Registry Expiry Date: (.+)/i',
'/Expire Date: (.+)/i',
'/expiry: (.+)/i',
'/expires: (.+)/i',
'/Registry Expiry Date: (.+)/i',
'/Expiration Time: (.+)/i',
'/validity: (.+)/i',
'/expires: (.+)/i',
'/paid-till: (.+)/i',
'/Expire Date: (.+)/i',
'/Expiration Date: (.+)/i',
'/expire: (.+)/i',
'/expiry: (.+)/i',
'/renewal date: (.+)/i',
'/Expiration Date: (.+)/i',
'/Expiration Time: (.+)/i',
'/Expires: (.+)/i',
];
// Known date formats
$knownFormats = [
"d-M-Y",
"d-F-Y",
"d-m-Y",
"Y-m-d",
"d.m.Y",
"Y.m.d",
"Y/m/d",
"Y/m/d H:i:s",
"Ymd",
"Ymd H:i:s",
"d/m/Y",
"Y. m. d.",
"Y.m.d H:i:s",
"d-M-Y H:i:s",
"D M d H:i:s T Y",
"D M d Y",
"Y-m-d\TH:i:s",
"Y-m-d\TH:i:s\Z",
"Y-m-d H:i:s\Z",
"Y-m-d H:i:s",
"d M Y H:i:s",
"d/m/Y H:i:s",
"d/m/Y H:i:s T",
"B d Y",
"d.m.Y H:i:s",
"before M-Y",
"before Y-m-d",
"before Ymd",
"Y-m-d H:i:s (\T\Z\Z)",
"Y-M-d.",
];
// Check each pattern to find a match
foreach ($patterns as $pattern) {
if (preg_match($pattern, $result, $matches)) {
$expireDate = trim($matches[1]);
break;
}
}
if ($expireDate) {
// Try parsing with known formats
foreach ($knownFormats as $format) {
$parsedDate = DateTime::createFromFormat($format, $expireDate);
if ($parsedDate && $parsedDate->format($format) === $expireDate) {
return $parsedDate->format('Y-m-d');
}
}
// If none of the formats matched, try to parse it directly
$parsedDate = date_create($expireDate);
if ($parsedDate) {
return $parsedDate->format('Y-m-d');
}
}
return null; // Return null if expiration date is not found
}
function validateWhitelabelKey($key)
{
$public_key = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0k+4ZJudkdGMCFLx5b9
H/sOozvWphFJsjVIF0vPVx9J0bTdml65UdS+32JagIHfPtEUTohaMnI3IAxxCDzl
655qmtjL7RHHdx9UMIKCmtAZOtd2u6rEyZH7vB7cKA49ysKGIaQSGwTQc8DCgsrK
uxRuX04xq9T7T+zuzROw3Y9WjFy9RwrONqLuG8LqO0j7bk5LKYeLAV7u3E/QiqNx
lEljN2UVJ3FZ/LkXeg8ORkV+IHs/toRIfPs/4VQnjEwk5BU6DX2STOvbeZnTqwP3
zgjRYR/zGN5l+az6RB3+0mJRdZdv/y2aRkBlwTxx2gOrPbQAco4a/IOmkE3EbHe7
6wIDAQAP
-----END PUBLIC KEY-----";
if (openssl_public_decrypt(base64_decode($key), $decrypted, $public_key)) {
$key_info = json_decode($decrypted, true);
if ($key_info['expires'] > date('Y-m-d H:i:s', strtotime('-7 day'))) {
return $key_info;
}
}
return false;
}