" Internet Explorer", '/firefox/i' => " Firefox", '/safari/i' => " Safari", '/chrome/i' => " Chrome", '/edg/i' => " Edge", '/opr/i' => " Opera", '/ddg/i' => " DuckDuckGo" ); foreach ($browser_array as $regex => $value) { if (preg_match($regex, $user_browser)) { $browser = $value; } } return $browser; } function getOS($user_os) { $os_platform = "-"; $os_array = array( '/windows/i' => " Windows", '/macintosh|mac os x/i' => " MacOS", '/linux/i' => " Linux", '/ubuntu/i' => " Ubuntu", '/fedora/i' => " Fedora", '/iphone/i' => " iPhone", '/ipad/i' => " iPad", '/android/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, $country_code = '', $show_country_code = false) { // Remove all non-digit characters $digits = preg_replace('/\D/', '', $phoneNumber ?? ''); $formatted = ''; // If no digits at all, fallback early if (strlen($digits) === 0) { return $phoneNumber; } // Helper function to safely check the first digit $startsWith = function($str, $char) { return isset($str[0]) && $str[0] === $char; }; switch ($country_code) { case '1': // USA/Canada if (strlen($digits) === 10) { $formatted = '(' . substr($digits, 0, 3) . ') ' . substr($digits, 3, 3) . '-' . substr($digits, 6); } break; case '44': // UK if ($startsWith($digits, '0')) { $digits = substr($digits, 1); } if (strlen($digits) === 10) { $formatted = '0' . substr($digits, 0, 4) . ' ' . substr($digits, 4, 3) . ' ' . substr($digits, 7); } break; case '61': // Australia if ($startsWith($digits, '0')) { $digits = substr($digits, 1); } if (strlen($digits) === 9) { $formatted = '0' . substr($digits, 0, 4) . ' ' . substr($digits, 4, 3) . ' ' . substr($digits, 7); } break; case '91': // India if (strlen($digits) === 10) { $formatted = substr($digits, 0, 5) . ' ' . substr($digits, 5); } break; case '81': // Japan if ($startsWith($digits, '0')) { $digits = substr($digits, 1); } if (strlen($digits) >= 9 && strlen($digits) <= 10) { $formatted = '0' . substr($digits, 0, 2) . '-' . substr($digits, 2, 4) . '-' . substr($digits, 6); } break; case '49': // Germany if ($startsWith($digits, '0')) { $digits = substr($digits, 1); } if (strlen($digits) >= 10) { $formatted = '0' . substr($digits, 0, 3) . ' ' . substr($digits, 3); } break; case '33': // France if ($startsWith($digits, '0')) { $digits = substr($digits, 1); } if (strlen($digits) === 9) { $formatted = '0' . implode(' ', str_split($digits, 2)); } break; case '34': // Spain if (strlen($digits) === 9) { $formatted = substr($digits, 0, 3) . ' ' . substr($digits, 3, 3) . ' ' . substr($digits, 6); } break; case '39': // Italy if ($startsWith($digits, '0')) { $digits = substr($digits, 1); } $formatted = '0' . implode(' ', str_split($digits, 3)); break; case '55': // Brazil if (strlen($digits) === 11) { $formatted = '(' . substr($digits, 0, 2) . ') ' . substr($digits, 2, 5) . '-' . substr($digits, 7); } break; case '7': // Russia if ($startsWith($digits, '8')) { $digits = substr($digits, 1); } if (strlen($digits) === 10) { $formatted = '8 (' . substr($digits, 0, 3) . ') ' . substr($digits, 3, 3) . '-' . substr($digits, 6, 2) . '-' . substr($digits, 8); } break; case '86': // China if (strlen($digits) === 11) { $formatted = substr($digits, 0, 3) . ' ' . substr($digits, 3, 4) . ' ' . substr($digits, 7); } break; case '82': // South Korea if (strlen($digits) === 11) { $formatted = substr($digits, 0, 3) . '-' . substr($digits, 3, 4) . '-' . substr($digits, 7); } break; case '62': // Indonesia if (!$startsWith($digits, '0')) { $digits = '0' . $digits; } if (strlen($digits) === 12) { $formatted = substr($digits, 0, 4) . ' ' . substr($digits, 4, 4) . ' ' . substr($digits, 8); } break; case '63': // Philippines if (strlen($digits) === 11) { $formatted = substr($digits, 0, 4) . ' ' . substr($digits, 4, 3) . ' ' . substr($digits, 7); } break; case '234': // Nigeria if (!$startsWith($digits, '0')) { $digits = '0' . $digits; } if (strlen($digits) === 11) { $formatted = substr($digits, 0, 4) . ' ' . substr($digits, 4, 3) . ' ' . substr($digits, 7); } break; case '27': // South Africa if (strlen($digits) >= 9 && strlen($digits) <= 10) { $formatted = substr($digits, 0, 3) . ' ' . substr($digits, 3, 3) . ' ' . substr($digits, 6); } break; case '971': // UAE if (strlen($digits) === 9) { $formatted = substr($digits, 0, 3) . ' ' . substr($digits, 3, 3) . ' ' . substr($digits, 6); } break; default: // fallback — do nothing, use raw digits later break; } if (!$formatted) { $formatted = $digits ?: $phoneNumber; } return $show_country_code && $country_code ? "+$country_code $formatted" : $formatted; } 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 credentials), returns it as a string function decryptCredentialEntry($credential_password_ciphertext) { // Split the credential into IV and Ciphertext $credential_iv = substr($credential_password_ciphertext, 0, 16); $credential_ciphertext = $salt = substr($credential_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 credential password using the master key return openssl_decrypt($credential_ciphertext, 'aes-128-cbc', $site_encryption_master_key, 0, $credential_iv); } // Encrypts a website/asset credential password function encryptCredentialEntry($credential_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 credential using the master key $ciphertext = openssl_encrypt($credential_password_cleartext, 'aes-128-cbc', $site_encryption_master_key, 0, $iv); return $iv . $ciphertext; } function apiDecryptCredentialEntry($credential_ciphertext, $api_key_decrypt_hash, #[\SensitiveParameter]$api_key_decrypt_password) { // Split the Credential entry (username/password) into IV and Ciphertext $credential_iv = substr($credential_ciphertext, 0, 16); $credential_ciphertext = $salt = substr($credential_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 credential password using the master key return openssl_decrypt($credential_ciphertext, 'aes-128-cbc', $site_encryption_master_key, 0, $credential_iv); } function apiEncryptCredentialEntry(#[\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) || !checkdnsrr($name, 'SOA')) { $records['a'] = ''; $records['ns'] = ''; $records['mx'] = ''; $records['whois'] = ''; return $records; } $domain = escapeshellarg(str_replace('www.', '', $name)); // Get A, NS, MX, TXT, and WHOIS records $records['a'] = trim(strip_tags(shell_exec("dig +short $domain"))); $records['ns'] = trim(strip_tags(shell_exec("dig +short NS $domain"))); $records['mx'] = trim(strip_tags(shell_exec("dig +short MX $domain"))); $records['txt'] = trim(strip_tags(shell_exec("dig +short TXT $domain"))); $records['whois'] = substr(trim(strip_tags(shell_exec("whois -H $domain | head -30 | sed 's/ //g'"))), 0, 254); // Sort A records (if multiple records exist) if (!empty($records['a'])) { $a_records = explode("\n", $records['a']); array_walk($a_records, function(&$record) { $record = trim($record); }); sort($a_records); $records['a'] = implode("\n", $a_records); } // Sort NS records (if multiple records exist) if (!empty($records['ns'])) { $ns_records = explode("\n", $records['ns']); array_walk($ns_records, function(&$record) { $record = trim($record); }); sort($ns_records); $records['ns'] = implode("\n", $ns_records); } // Sort MX records (if multiple records exist) if (!empty($records['mx'])) { $mx_records = explode("\n", $records['mx']); array_walk($mx_records, function(&$record) { $record = trim($record); }); sort($mx_records); $records['mx'] = implode("\n", $mx_records); } // Sort TXT records (if multiple records exist) if (!empty($records['txt'])) { $txt_records = explode("\n", $records['txt']); array_walk($txt_records, function(&$record) { $record = trim($record); }); sort($txt_records); $records['txt'] = implode("\n", $txt_records); } 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(); } } /* * LEGACY Role validation * Admin - 3 * Tech - 2 * Accountant - 1 */ function validateAdminRole() { global $session_user_role; 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(); } } // LEGACY // 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() { global $session_user_role; 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(); } } // LEGACY // 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() { global $session_user_role; 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 if ($config_smtp_encryption == 'None') { $mail->SMTPOptions = array( 'ssl' => array( 'verify_peer' => false, 'verify_peer_name' => false, )); $mail->SMTPSecure = false; $mail->SMTPAutoTLS = false; } else { $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 = "
$body
"; // 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') { $device_icon = "network-wired"; } elseif ($asset_type == 'Firewall/Router') { $device_icon = "fire-alt"; } 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 == 'Display') { $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('md5', $fileContent); // Generate a secure filename using the hashed content $secureFilename = $hashedContent . randomString(2) . '.' . $extension; return $secureFilename; } function sanitizeInput($input) { global $mysqli; if (!empty($input)) { // Only convert encoding if it's NOT valid UTF-8 if (!mb_check_encoding($input, 'UTF-8')) { // Try converting from Windows-1252 as a safe default fallback $input = mb_convert_encoding($input, 'UTF-8', 'Windows-1252'); } } // Remove HTML and PHP tags $input = strip_tags((string) $input); // Trim white space $input = trim($input); // Escape for SQL $input = mysqli_real_escape_string($mysqli, $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); } 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; } 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($data) { global $mysqli; 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 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 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 || !checkdnsrr($domain, 'SOA')) { 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; } // When provided a module name (e.g. module_support), returns the associated permission level (false=none, 1=read, 2=write, 3=full) function lookupUserPermission($module) { global $mysqli, $session_is_admin, $session_user_role; if (isset($session_is_admin) && $session_is_admin === true) { return 3; } $module = sanitizeInput($module); $sql = mysqli_query( $mysqli, "SELECT user_role_permissions.user_role_permission_level FROM modules JOIN user_role_permissions ON modules.module_id = user_role_permissions.module_id WHERE module_name = '$module' AND user_role_permissions.user_role_id = $session_user_role" ); $row = mysqli_fetch_array($sql); if (isset($row['user_role_permission_level'])) { return intval($row['user_role_permission_level']); } // Default return for no module permission return false; } // Ensures a user has access to a module (e.g. module_support) with at least the required permission level provided (defaults to read) function enforceUserPermission($module, $check_access_level = 1) { $permitted_access_level = lookupUserPermission($module); if (!$permitted_access_level || $permitted_access_level < $check_access_level) { $_SESSION['alert_type'] = "danger"; $_SESSION['alert_message'] = WORDING_ROLECHECK_FAILED; $map = [ "1" => "read", "2" => "write", "3" => "full" ]; exit(WORDING_ROLECHECK_FAILED . "
Tell your admin: $map[$check_access_level] access to $module is not permitted for your role."); } } // TODO: Probably remove this function enforceAdminPermission() { global $session_is_admin; if (!isset($session_is_admin) || !$session_is_admin) { exit(WORDING_ROLECHECK_FAILED . "
Tell your admin: Your role does not have admin access."); } return true; } function customAction($trigger, $entity) { $original_dir = getcwd(); // Save chdir(dirname(__FILE__)); if (file_exists(__DIR__ . "/custom/custom_action_handler.php")) { include_once __DIR__ . "/custom/custom_action_handler.php"; } chdir($original_dir); // Restore original working directory } function appNotify($type, $details, $action = null, $client_id = 0, $entity_id = 0) { global $mysqli; if (is_null($action)) { $action = "NULL"; // Without quotes for SQL NULL } $sql = mysqli_query($mysqli, "SELECT user_id FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL "); while ($row = mysqli_fetch_array($sql)) { $user_id = intval($row['user_id']); mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = '$type', notification = '$details', notification_action = '$action', notification_client_id = $client_id, notification_entity_id = $entity_id, notification_user_id = $user_id"); } } function logAction($type, $action, $description, $client_id = 0, $entity_id = 0) { global $mysqli, $session_user_agent, $session_ip, $session_user_id; if (empty($session_user_id)) { $session_user_id = 0; } mysqli_query($mysqli, "INSERT INTO logs SET log_type = '$type', log_action = '$action', log_description = '$description', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $entity_id"); } function logApp($category, $type, $details) { global $mysqli; mysqli_query($mysqli, "INSERT INTO app_logs SET app_log_category = '$category', app_log_type = '$type', app_log_details = '$details'"); } function logAuth($status, $details) { global $mysqli, $session_user_agent, $session_ip, $session_user_id; if (empty($session_user_id)) { $session_user_id = 0; } mysqli_query($mysqli, "INSERT INTO auth_logs SET auth_log_status = $status, auth_log_details = '$details', auth_log_ip = '$session_ip', auth_log_user_agent = '$session_user_agent', auth_log_user_id = $session_user_id"); } // Helper function for missing data fallback function getFallback($data) { return !empty($data) ? $data : '-'; } /** * Retrieves a specified field's value from a table based on the record's id. * It validates the table and field names, automatically determines the primary key (or uses the first column as fallback), * and returns the field value with an appropriate escaping method. * * @param string $table The name of the table. * @param int $id The record's id. * @param string $field The field (column) to retrieve. * @param string $escape_method The escape method: 'sql' (default, auto-detects int), 'html', 'json', or 'int'. * * @return mixed The escaped field value, or null if not found or invalid input. */ function getFieldById($table, $id, $field, $escape_method = 'sql') { global $mysqli; // Use the global MySQLi connection // Validate table and field names to allow only letters, numbers, and underscores if (!preg_match('/^[a-zA-Z0-9_]+$/', $table) || !preg_match('/^[a-zA-Z0-9_]+$/', $field)) { return null; // Invalid table or field name } // Sanitize id as an integer $id = (int)$id; // Get the list of columns and their details from the table $columns_result = mysqli_query($mysqli, "SHOW COLUMNS FROM `$table`"); if (!$columns_result || mysqli_num_rows($columns_result) == 0) { return null; // Table not found or has no columns } // Build an associative array with column details $columns = []; while ($row = mysqli_fetch_assoc($columns_result)) { $columns[$row['Field']] = [ 'type' => $row['Type'], 'key' => $row['Key'] ]; } // Find the primary key field if available $id_field = null; foreach ($columns as $col => $details) { if ($details['key'] === 'PRI') { $id_field = $col; break; } } // Fallback: if no primary key is found, use the first column if (!$id_field) { reset($columns); $id_field = key($columns); } // Ensure the requested field exists; if not, default to the id field if (!array_key_exists($field, $columns)) { $field = $id_field; } // Build and execute the query to fetch the specified field value $query = "SELECT `$field` FROM `$table` WHERE `$id_field` = $id"; $sql = mysqli_query($mysqli, $query); if ($sql && mysqli_num_rows($sql) > 0) { $row = mysqli_fetch_assoc($sql); $value = $row[$field]; // Apply the desired escaping method or auto-detect integer type if using SQL escaping switch ($escape_method) { case 'raw': return $value; // Return as-is from the database case 'html': return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8'); // Escape for HTML case 'json': return json_encode($value); // Escape for JSON case 'int': return (int)$value; // Explicitly cast value to integer case 'sql': default: // Auto-detect if the field type is integer if (stripos($columns[$field]['type'], 'int') !== false) { return (int)$value; } else { return sanitizeInput($value); // Escape for SQL using a custom function } } } return null; // Return null if no record was found } // Recursive function to display folder options - Used in folders files and documents function display_folder_options($parent_folder_id, $client_id, $folder_location = 0, $indent = 0) { global $mysqli; $folder_location = intval($folder_location); // 0 = Document Folders // 1 = File Folders $sql_folders = mysqli_query($mysqli, "SELECT * FROM folders WHERE parent_folder = $parent_folder_id AND folder_location = $folder_location AND folder_client_id = $client_id ORDER BY folder_name ASC"); while ($row = mysqli_fetch_array($sql_folders)) { $folder_id = intval($row['folder_id']); $folder_name = nullable_htmlentities($row['folder_name']); // Indentation for subfolders $indentation = str_repeat(' ', $indent * 4); // Check if this folder is selected $selected = ''; if ((isset($_GET['folder_id']) && intval($_GET['folder_id']) === $folder_id) || (isset($_POST['folder']) && intval($_POST['folder']) === $folder_id)) { $selected = 'selected'; } echo ""; // Recursively display subfolders display_folder_options($folder_id, $client_id, $folder_location, $indent + 1); } } function sanitize_url($url) { $allowed = ['http', 'https', 'file', 'ftp', 'ftps', 'sftp', 'dav', 'webdav', 'caldav', 'carddav', 'ssh', 'telnet', 'smb', 'rdp', 'vnc', 'rustdesk', 'anydesk', 'connectwise', 'splashtop', 'sip', 'sips', 'ldap', 'ldaps']; $parts = parse_url($url ?? ''); if (isset($parts['scheme']) && !in_array(strtolower($parts['scheme']), $allowed)) { // Remove the scheme and colon $pos = strpos($url, ':'); $without_scheme = $url; if ($pos !== false) { $without_scheme = substr($url, $pos + 1); // This keeps slashes (e.g. //pizza.com) } // Prepend 'unsupported://' (strip any leading slashes from $without_scheme to avoid triple slashes) $unsupported = 'unsupported://' . ltrim($without_scheme, '/'); return htmlspecialchars($unsupported, ENT_QUOTES, 'UTF-8'); } // Safe schemes: return escaped original URL return htmlspecialchars($url ?? '', ENT_QUOTES, 'UTF-8'); } // Redirect Function function redirect($url = null, $permanent = false) { // Use referer if no URL is provided if (!$url) { $url = $_SERVER['HTTP_REFERER'] ?? 'index.php'; } if (!headers_sent()) { header('Location: ' . $url, true, $permanent ? 301 : 302); exit; } else { // Fallback for headers already sent echo ""; echo ''; exit; } } //Flash Alert Function function flash_alert(string $message, string $type = 'success'): void { $_SESSION['alert_type'] = $type; $_SESSION['alert_message'] = $message; } // Sanitize File Names function sanitize_filename($filename, $strict = false) { // Remove path information and dots around the filename $filename = basename($filename); // Replace spaces and underscores with dashes $filename = str_replace([' ', '_'], '-', $filename); // Remove anything which isn't a word, number, dot, or dash $filename = preg_replace('/[^A-Za-z0-9\.\-]/', '', $filename); // Optionally make filename strict alphanumeric (keep dot and dash) if ($strict) { $filename = preg_replace('/[^A-Za-z0-9\.\-]/', '', $filename); } // Avoid multiple consecutive dashes $filename = preg_replace('/-+/', '-', $filename); // Remove leading/trailing dots and dashes $filename = trim($filename, '.-'); // Ensure it’s not empty if (empty($filename)) { $filename = 'file'; } return $filename; }