mirror of https://github.com/itflow-org/itflow
1322 lines
67 KiB
PHP
1322 lines
67 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",
|
|
'/edge/i' => "<i class='fab fa-fw fa-edge text-secondary'></i> Edge",
|
|
'/opera/i' => "<i class='fab fa-fw fa-opera text-secondary'></i> Opera"
|
|
);
|
|
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 nt 10/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows 10",
|
|
'/windows nt 6.3/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows 8.1",
|
|
'/windows nt 6.2/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows 8",
|
|
'/windows nt 6.1/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows 7",
|
|
'/windows nt 6.0/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows Vista",
|
|
'/windows nt 5.2/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows Server 2003/XP x64",
|
|
'/windows nt 5.1/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows XP",
|
|
'/windows xp/i' => "<i class='fab fa-fw fa-windows text-secondary'></i> Windows XP",
|
|
'/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",
|
|
'/iphone/i' => "<i class='fab fa-fw fa-apple text-secondary'></i> iPhone",
|
|
'/ipod/i' => "<i class='fab fa-fw fa-apple text-secondary'></i> iPod",
|
|
'/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
|
|
* 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 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;
|
|
}
|
|
|
|
// 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, 150) . "...";
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
$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;
|
|
|
|
}
|
|
|
|
// Get domain expiration date -- Remove in the future Replace with PHP function
|
|
function getDomainExpirationDateOLD($name)
|
|
{
|
|
|
|
// Only run if we think the domain is valid
|
|
if (!filter_var($name, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
|
|
return "NULL";
|
|
}
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, "http://lookup.itflow.org:8080/$name");
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
$response = json_decode(curl_exec($ch), 1);
|
|
|
|
if ($response) {
|
|
if (is_array($response['expiration_date'])) {
|
|
$expiry = new DateTime($response['expiration_date'][1]);
|
|
} elseif (isset($response['expiration_date'])) {
|
|
$expiry = new DateTime($response['expiration_date']);
|
|
} else {
|
|
return "NULL";
|
|
}
|
|
|
|
return $expiry->format('Y-m-d');
|
|
}
|
|
|
|
// Default return
|
|
return "NULL";
|
|
}
|
|
|
|
function getDomainExpirationDate($domain) {
|
|
// Execute the whois command
|
|
$result = shell_exec("whois " . escapeshellarg($domain));
|
|
$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+(.+)/', // New pattern 1 for "Expires on"
|
|
'/expire: (.+)/', // New pattern 2 for "expire"
|
|
'/validity: (.+)/', // New pattern 3 for "validity"
|
|
'/Expires on.*: (.+)/i', // New pattern 4 for "Expires on" (case insensitive)
|
|
'/Expiry on.*: (.+)/i', // New pattern 5 for "Expiry on" (case insensitive)
|
|
'/renewal: (.+)/i', // New pattern 6 for "renewal"
|
|
'/Expir\w+ Date: (.+)/i', // New pattern 7 for "Expir Date" (case insensitive)
|
|
'/Valid Until: (.+)/i', // New pattern 8 for "Valid Until"
|
|
'/Valid until: (.+)/i', // New pattern 9 for "Valid until"
|
|
'/expire-date: (.+)/i', // New pattern 10 for "expire-date"
|
|
'/Expiration Date: (.+)/i', // from WhoisKg
|
|
'/Registry Expiry Date: (.+)/i', // from WhoisID
|
|
'/Expire Date: (.+)/i', // from WhoisIt
|
|
'/expiry: (.+)/i', // from WhoisChLi
|
|
'/expires: (.+)/i', // from WhoisSe
|
|
'/Registry Expiry Date: (.+)/i', // from WhoisJobs
|
|
'/Expiration Time: (.+)/i', // from WhoisMx
|
|
'/validity: (.+)/i', // from WhoisIl
|
|
'/expires: (.+)/i', // from WhoisDk
|
|
'/paid-till: (.+)/i', // from WhoisRu
|
|
'/Expire Date: (.+)/i', // from WhoisSa
|
|
'/Expiration Date: (.+)/i', // from WhoisAe
|
|
'/expire: (.+)/i', // from WhoisIt
|
|
'/expiry: (.+)/i', // from WhoisChLi
|
|
'/renewal date: (.+)/i', // from WhoisCat
|
|
'/Expiration Date: (.+)/i', // from WhoisHr
|
|
'/Expiration Time: (.+)/i', // from WhoisZhongGuo
|
|
'/Expires: (.+)/i', // from WhoisEdu
|
|
];
|
|
|
|
// Known date formats
|
|
$knownFormats = [
|
|
"d-M-Y", // 02-Jan-2000
|
|
"d-F-Y", // 11-February-2000
|
|
"d-m-Y", // 20-10-2000
|
|
"Y-m-d", // 2000-01-02
|
|
"d.m.Y", // 2.1.2000
|
|
"Y.m.d", // 2000.01.02
|
|
"Y/m/d", // 2000/01/02
|
|
"Y/m/d H:i:s", // 2011/06/01 01:05:01
|
|
"Ymd", // 20170209
|
|
"Ymd H:i:s", // 20110908 14:44:51
|
|
"d/m/Y", // 02/01/2013
|
|
"Y. m. d.", // 2000. 01. 02.
|
|
"Y.m.d H:i:s", // 2014.03.08 10:28:24
|
|
"d-M-Y H:i:s", // 24-Jul-2009 13:20:03 UTC
|
|
"D M d H:i:s T Y", // Tue Jun 21 23:59:59 GMT 2011
|
|
"D M d Y", // Tue Dec 12 2000
|
|
"Y-m-d\TH:i:s", // 2007-01-26T19:10:31
|
|
"Y-m-d\TH:i:s\Z", // 2007-01-26T19:10:31Z
|
|
"Y-m-d H:i:s\Z", // 2000-08-22 18:55:20Z
|
|
"Y-m-d H:i:s", // 2000-08-22 18:55:20
|
|
"d M Y H:i:s", // 08 Apr 2013 05:44:00
|
|
"d/m/Y H:i:s", // 23/04/2015 12:00:07 EEST
|
|
"d/m/Y H:i:s T", // 23/04/2015 12:00:07 EEST
|
|
"B d Y", // August 14 2017
|
|
"d.m.Y H:i:s", // 08.03.2014 10:28:24
|
|
"before M-Y", // before aug-1996
|
|
"before Y-m-d", // before 1996-01-01
|
|
"before Ymd", // before 19960821
|
|
"Y-m-d H:i:s (\T\Z\Z)", // 2017-09-26 11:38:29 (GMT+00:00)
|
|
"Y-M-d.", // 2024-Apr-02.
|
|
];
|
|
|
|
// Check each pattern to find a match
|
|
foreach ($patterns as $pattern) {
|
|
if (preg_match($pattern, $result, $matches)) {
|
|
$expireDate = $matches[1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($expireDate) {
|
|
// Try parsing with known formats
|
|
foreach ($knownFormats as $format) {
|
|
$parsedDate = DateTime::createFromFormat($format, $expireDate);
|
|
if ($parsedDate) {
|
|
$expireDate = $parsedDate->format('Y-m-d');
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
$expireDate = 'Expiration date not found';
|
|
}
|
|
|
|
return $expireDate;
|
|
} |