HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/app/Helpers/namespaced_helpers.php
<?php

namespace App\Helpers;

use App\Library\StringHelper;
use Exception;
use Closure;
use Carbon\Carbon;
use SimpleXMLElement;
use Mika56\SPFCheck\SPFCheck;
use Mika56\SPFCheck\DNSRecordGetterDirect;
use Mika56\SPFCheck\DNSRecordGetter;
use DB;
use Illuminate\Support\Facades\File;

function updateTranslationFile($targetFile, $sourceFile, $overwriteTargetPhrases = false, $deleteTargetKeys = true, $sort = false)
{
    $source = include $sourceFile;
    $target = include $targetFile; // to be written to

    if ($overwriteTargetPhrases) {
        // Overwrite $target
        $merged = $source + $target;
    } else {
        // Respect $target
        $merged = $target + $source;
    }

    if ($deleteTargetKeys) {
        // Find keys in $target that are that not available in $source
        $diff = array_diff_key($target, $source);

        // Delete those keys in the final result
        $merged = array_diff_key($merged, $diff);
    }

    if ($sort) {
        ksort($merged);
    }

    $out = '<?php return '.var_export(\Yaml::parse(\Yaml::dump($merged)), true)." ?>\n";
    \Illuminate\Support\Facades\File::put($targetFile, $out);
}

// Copy and:
// + Remove the destination first
// + Create parent folders if not exist
function pcopy($src, $dst)
{
    if (!\Illuminate\Support\Facades\File::exists($src)) {
        throw new Exception("File `{$src}` does not exist");
    }

    if (\Illuminate\Support\Facades\File::exists($dst)) {
        // Delete the file or link or directory
        if (is_link($dst) || is_file($dst)) {
            \Illuminate\Support\Facades\File::delete($dst);
        } else {
            \Illuminate\Support\Facades\File::deleteDirectory($dst);
        }
    } else {
        // Make sure the PARENT directory exists
        $dirname = pathinfo($dst)['dirname'];
        if (!\Illuminate\Support\Facades\File::exists($dirname)) {
            \Illuminate\Support\Facades\File::makeDirectory($dirname, 0777, true, true);
        }
    }

    // if source is a file, just copy it
    if (\Illuminate\Support\Facades\File::isFile($src)) {
        \Illuminate\Support\Facades\File::copy($src, $dst);
    } else {
        \Illuminate\Support\Facades\File::copyDirectory($src, $dst);
    }
}

function ptouch($filepath)
{
    $dirname = dirname($filepath);
    if (!\Illuminate\Support\Facades\File::exists($dirname)) {
        \Illuminate\Support\Facades\File::makeDirectory($dirname, 0777, true, true);
    }

    touch($filepath);
}

function xml_to_array(SimpleXMLElement $xml)
{
    $parser = function (SimpleXMLElement $xml, array $collection = []) use (&$parser) {
        $nodes = $xml->children();
        $attributes = $xml->attributes();

        if (0 !== count($attributes)) {
            foreach ($attributes as $attrName => $attrValue) {
                $collection['attributes'][$attrName] = html_entity_decode(strval($attrValue));
            }
        }

        if (0 === $nodes->count()) {
            // $collection['value'] = strval($xml);
            // return $collection;
            return html_entity_decode(strval($xml));
        }

        foreach ($nodes as $nodeName => $nodeValue) {
            if (count($nodeValue->xpath('../' . $nodeName)) < 2) {
                $collection[$nodeName] = $parser($nodeValue);
                continue;
            }

            $collection[$nodeName][] = $parser($nodeValue);
        }

        return $collection;
    };

    return [
        $xml->getName() => $parser($xml)
    ];
}

function spfcheck($ipOrHostname, $domain)
{
    $checker = new SPFCheck(new DNSRecordGetterDirect('8.8.8.8'));

    // $checker = new SPFCheck(new DNSRecordGetter());
    $result = $checker->isIPAllowed($ipOrHostname, $domain);

    if (SPFCheck::RESULT_PASS != $result) {
        // try again with another method
        $checker = new SPFCheck(new DNSRecordGetter());
        $result = $checker->isIPAllowed($ipOrHostname, $domain);
    }

    return $result;
}

function isValidPublicHostnameOrIpAddress($host)
{
    if ($host == '127.0.0.1' || $host == 'localhost') {
        return false;
    }

    $isValidIpAddress = filter_var($host, FILTER_VALIDATE_IP);
    $getHostByName = gethostbyname($host);

    if ($isValidIpAddress) {
        return true;
    } elseif (filter_var($getHostByName, FILTER_VALIDATE_IP)) {
        return true;
    } else {
        return false;
    }
}

function write_env($key, $value, $overwrite = true)
{
    // Important, make the new environment var available
    // Otherwise, this method may failed if called twice (in a loop for example) in the same process
    \Artisan::call('config:clear');

    // In case config:clear does not work
    if (file_exists(base_path('bootstrap/cache/config.php'))) {
        unlink(base_path('bootstrap/cache/config.php'));
    }

    $envs = load_env_from_file(app()->environmentFilePath());

    // Set the value if overwrite is set to true or the key value is empty
    if ($overwrite || !array_key_exists($key, $envs) || empty($envs[$key])) {
        // Quote if there is at least one space or # or any suspected char!
        if (preg_match('/[\s\#!\$]/', $value)) {
            // Escape single quote
            $value = addcslashes($value, '"');
            $value = "\"$value\"";
        }

        $envs[$key] = $value;
    } else {
        return;
    }

    $out = [];
    foreach ($envs as $k => $v) {
        $out[] = "$k=$v";
    }

    $out = implode("\n", $out);

    // Actually write to file .env
    file_put_contents(app()->environmentFilePath(), $out);
}

function write_envs($params)
{
    foreach ($params as $key => $value) {
        write_env($key, $value);
    }
}

function reset_app_url($force = false)
{
    $envs = load_env_from_file(app()->environmentFilePath());
    if (!array_key_exists('APP_URL', $envs) || $force) {
        $url = url('/');
        write_env('APP_URL', $url);
    }
}

function url_get_contents_ssl_safe($url)
{
    // Check if $url is a URL
    if (!preg_match('/^https{0,1}:\/\//', $url)) {
        throw new \Exception('url_get_contents_ssl_safe() requires a URL as input. Received: '.$url);
    }

    $client = curl_init();
    curl_setopt_array($client, array(
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER => false,
        CURLOPT_SSL_VERIFYPEER => false
    ));

    $result = curl_exec($client);
    curl_close($client);

    return $result;
}

function is_non_web_link($url)
{
    // $preserved = [ '#', 'mailto:', 'tel:', 'file:', 'ftp:', 'rss:', 'feed:', 'telnet:', 'gopher:', 'ssh:', 'nntp:', 'data:'];

    if (empty($url)) {
        return true;
    }

    if (str_starts_with($url, '#')) {
        return true;
    }

    // Check for template variables like {VAR} or [VAR]
    $url = trim($url);
    if (preg_match('/^[\{\[].*[\}\]]$/', $url)) {
        return true;
    }

    // Match scheme:pattern
    if (preg_match('/^([a-zA-Z][a-zA-Z0-9+\-.]*):/', $url, $matches)) {
        $scheme = strtolower($matches[1]);
        // Exclude http and https
        return !in_array($scheme, ['http', 'https']);
    }

    return false;
}

// IMPORTANT
// + This function does not purify values, it will load raw content like: [ DB => "'mydb'", OTHER => '""']
// + Allow only a-zA-Z_ in key name
function load_env_from_file($path)
{
    $content = file_get_contents($path);
    $lines = preg_split("/(\r\n|\n|\r)/", $content);
    $lines = array_where($lines, function ($value, $key) {
        if (is_null($value)) {
            return false;
        }

        if (preg_match('/^[a-zA-Z0-9_]+=/', $value)) {
            return true;
        } else {
            return false;
        }
    });

    $output = [];
    foreach ($lines as $line) {
        list($key, $value) = explode('=', $line, 2);

        if (is_null($value)) {
            $value = '';
        } else {
            $value = trim($value);
        }

        $output[ $key ] = $value;
    }

    return $output;
}

/*
 * Execute a task and count credits
 * Roll back credits if failure
 * @important: only rollback credits count. Do not rollback rate count because
 *             even a failed operation attempt is counted
 *
 * Parameter 1: \App\Library\RateTracker[]       : all trackers in this array must be available (AND condition)
 * Parameter 2: \App\Library\RateTrackersPool[]  : at least one of the tracker in this array is available
 * Parameter 2: \App\Library\Contracts\CreditTrackerInterface[]
 */
function execute_with_limits(array $rateTrackers, $rateTrackersPool, array $creditTrackers, ?Closure $task = null, $rateExceedingCallback = null)
{
    // Remove null tracker from array
    $creditTrackers = array_values(array_filter($creditTrackers));

    // Check credits first, because credits can be rolled back
    // Check rate after that
    $creditCounted = [];
    try {
        foreach ($creditTrackers as $creditTracker) {
            // Might throw \App\Library\Exception\OutOfCredits
            $creditTracker->count();
            $creditCounted[] = $creditTracker;
        }
    } catch (\App\Library\Exception\OutOfCredits $exception) {
        // Rollback 1: when OutOfCredits
        // @important: rate is counted even for a failed operation attempt
        // So there is no need to roll it back (that's why the rollback() method is @deprecated)
        // However, credits must be rolled back before exit
        foreach ($creditCounted as $creditTracker) {
            $creditTracker->rollback();
        }

        throw $exception;
    }

    // Evaluate Rate
    $selectedServer = \App\Helpers\with_rate_limits($rateTrackers, $rateTrackersPool, $rateExceedingCallback, $creditCounted, $testMode = false);

    try {
        // Return null if task is null, i.e. count credits but do not actually do anything
        if (is_null($task)) {
            return;
        }

        // Execute task
        return $task($selectedServer);
    } catch (\Throwable $exception) {
        // Rollback 2: when task error
        // @important: rate is counted even for a failed operation attempt
        // So there is no need to roll it back (that's why the rollback() method is @deprecated)
        // However, credits must be rolled back before exit
        foreach ($creditCounted as $creditTracker) {
            $creditTracker->rollback();
        }

        throw $exception;
    }
}

function with_rate_limits(array $rateTrackers, $rateTrackersPool, $rateExceedingCallback, $creditCounted = [], $testMode = false)
{
    $rateTrackers = array_values(array_filter($rateTrackers));
    $rateCounted = [];
    $selectedServer = null;

    try {
        foreach ($rateTrackers as $t) {
            if ($testMode == true) {
                $t->test(now(), $rateExceedingCallback);
            } else {
                $t->count(now(), $rateExceedingCallback);
            }

            $rateCounted[] = $t;
        }

        // Return null here is acceptable if $rateTrackersPool is not passed
        if (is_null($rateTrackersPool)) {
            return;
        }

        // However, if the $rateTrackersPool is available but no sending server selected ==> throw
        $selectedServer = null;
        $notes = "All {$rateTrackersPool->count()} server(s) exceeded limits";
        $lastCheckMsg = null;
        do {
            $selectedServer = $rateTrackersPool->select($removeFromList = true);

            if (is_null($selectedServer)) {
                throw new \App\Library\Exception\RateLimitExceeded($notes);
            }

            $serverRateLimitTracker = $selectedServer->getRateLimitTracker();

            try {
                if ($testMode == true) {
                    $serverRateLimitTracker->test(now());
                } else {
                    $serverRateLimitTracker->count(now());
                }

                $passed = true;
            } catch (\App\Library\Exception\RateLimitExceeded $rateException) {
                $notes = $notes." | EXCEEDED ".$serverRateLimitTracker->getLimitsDescription();
                $passed = false;
            }
        } while (false == $passed);

        // MUST return
        return $selectedServer;

    } catch (\App\Library\Exception\RateLimitExceeded $exception) {
        if (!$testMode) {
            // Credits must not be counted
            foreach ($creditCounted as $creditTracker) {
                $creditTracker->rollback();
            }

            // In case of more than one rate trackers
            // + Tracker A works just fine
            // + Tracker B works just fine
            // + Tracker C fails
            // Rollback tracker A and B
            foreach ($rateCounted as $rateTracker) {
                $rateTracker->rollback();
            }
        }

        // This exception is safely handled in SendMessage ( i.e. catch (RateLimitExceeded $ex) )
        throw $exception;
    }
}


// Load the list of connection settings from config/sharding.php and set default Laravel database connection
function set_userdb_connection($name)
{
    \Config::set('database.default', $name);
    \DB::purge($name);
    \DB::reconnect($name);
}

function maskEmail($email)
{
    // Split the email address into parts
    list($username, $domain) = explode('@', $email);

    // Get the length of the username
    $usernameLength = strlen($username);

    // Keep the first and last character of the username
    $maskedUsername = substr($username, 0, 1) . str_repeat('*', $usernameLength - 2) . substr($username, -1);

    // Mask the domain
    $maskedDomain = substr($domain, 0, 1) . str_repeat('*', strlen($domain) - 2) . substr($domain, -1);

    // Concatenate the masked username and domain
    $maskedEmail = $maskedUsername . '@' . $maskedDomain;

    return $maskedEmail;
}

function read_csv($file, $headerLine = false, $ignoreEmptyHeader = false)
{
    if (!file_exists($file)) {
        throw new \Exception("File {$file} not found");
    }

    try {
        // Fix the problem with MAC OS's line endings
        if (!ini_get('auto_detect_line_endings')) {
            ini_set('auto_detect_line_endings', '1');
        }

        // return false or an encoding name
        $encoding = StringHelper::detectEncoding($file);

        if ($encoding == false) {
            // Cannot detect file's encoding
        } elseif ($encoding != 'UTF-8') {
            // Convert from {$encoding} to UTF-8";
            StringHelper::toUTF8($file, $encoding);
        } else {
            // File encoding is UTF-8
            StringHelper::checkAndRemoveUTF8BOM($file);
        }

        // Run this method anyway
        // to make sure mb_convert_encoding($content, 'UTF-8', 'UTF-8') is always called
        // which helps resolve the issue of
        //     "Error executing job. SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\x83??s k...' for column 'company' at row 2562 (SQL: insert into `dlk__tmp_subscribers..."
        StringHelper::toUTF8($file, 'UTF-8');

        // Read CSV files
        $reader = \League\Csv\Reader::createFromPath($file);
        if ($headerLine) {
            $reader->setHeaderOffset(0);
            $headers = $reader->getHeader();

            // Make sure the headers are present
            // In case of duplicate column headers, an exception shall be thrown by League
            if (!$ignoreEmptyHeader) {
                foreach ($headers as $index => $header) {
                    if (is_null($header) || empty(trim($header))) {
                        throw new \Exception(trans('messages.list.import.error.header_empty', ['index' => $index]));
                    }
                }
            }

            // Remove leading/trailing spaces in headers, keep letter case
            // get the headers, using array_filter to strip empty/null header
            // to avoid the error of "InvalidArgumentException: Use a flat array with unique string values in /home/nghi/mailixa/vendor/league/csv/src/Reader.php:305"
            $headers = array_map(function ($r) {
                return trim($r);
            }, $headers);

            $results = $reader->getRecords($headers);
        } else {
            $headers = null;
            $results = $reader->getRecords();
        }

        return [$headers, iterator_count($results), $results];
    } catch (\Exception $ex) {
        // @todo: translation here
        // Common errors that will be catched: duplicate column, empty column
        throw new \Exception('Invalid headers. Original error message is: '.$ex->getMessage());
    }
}

function plogger($name = null)
{
    $formatter = new \Monolog\Formatter\LineFormatter("[%datetime%] %channel%.%level_name%: %message%\n");
    $pid = getmypid();
    $logfile = storage_path(join_paths('logs', php_sapi_name(), '/process-'.$pid.'.log'));
    $stream = new \Monolog\Handler\RotatingFileHandler($logfile, 0, config('custom.log_level'));
    $stream->setFormatter($formatter);

    $logger = new \Monolog\Logger($name ?: 'process');
    $logger->pushHandler($stream);
    return $logger;
}

function dkim_sign(\Symfony\Component\Mime\Email $email, \App\Library\Domain $domain): \Symfony\Component\Mime\Email
{
    $privateKey = $domain->getPrivateKey();
    $domainName = $domain->getName();
    $selector = $domain->getDkimSelector();

    if (empty($privateKey)) {
        \Log::warning("Skipping DKIM signing for domain '{$domainName}': no private key configured. Configure DKIM keys via the sending-domain admin UI to enable signing.");
        return $email;
    }

    $parsed = @openssl_pkey_get_private($privateKey);
    if ($parsed === false) {
        \Log::warning("Skipping DKIM signing for domain '{$domainName}': private key is not a valid PEM (openssl rejected it). Re-generate keys via the sending-domain admin UI.");
        return $email;
    }
    unset($parsed);

    // PRECONDITION: $email must already have Date and Message-ID set explicitly.
    // Symfony's Message::getPreparedHeaders() auto-generates both per call when absent —
    // once during sign (committed into DKIM b=) and again at wire-serialize time (different
    // values), which breaks DKIM at the receiver. Callers must set Date + Message-ID at
    // Email build time.
    //
    // DkimSigner::sign() returns a frozen Symfony\Mime\Message (clone with DKIM-Signature
    // header appended). $email stays Email so downstream API drivers can use Email-specific
    // accessors (getHtmlBody/getFrom/getAttachments). 'DKIM-Signature' in headers_to_ignore
    // so the second call (default domain after identity domain) doesn't canonicalize the
    // first signature into its own b= hash.
    $signer = new \Symfony\Component\Mime\Crypto\DkimSigner($privateKey, $domainName, $selector);
    $signed = $signer->sign($email, ['headers_to_ignore' => ['Return-Path', 'DKIM-Signature']]);

    // DkimSigner appends the new DKIM-Signature at the end. Take only the last one,
    // leaving any prior DKIM-Signature already on $email untouched.
    $newDkimSignature = null;
    foreach ($signed->getHeaders()->all('DKIM-Signature') as $h) {
        $newDkimSignature = $h;
    }
    if ($newDkimSignature !== null) {
        $email->getHeaders()->add($newDkimSignature);
    }

    return $email;
}

function dkim_sign_with_default_domain(\Symfony\Component\Mime\Email $email): \Symfony\Component\Mime\Email
{
    if (empty(config('default_auth_domain'))) {
        throw new \Exception('"sign_with_default_domain" is set to TRUE but no default domain found');
    }

    $name = config('default_auth_domain.domain');
    $selector = config('default_auth_domain.selector');
    $publicKey = config('default_auth_domain.public_key');
    $privateKey = config('default_auth_domain.private_key');

    $domain = new \App\Library\Domain($name, $selector, $publicKey, $privateKey);
    return \App\Helpers\dkim_sign($email, $domain);
}

function generate_supervisor_config($templateFile)
{
    // Replace the templates with actual plugin names
    $loader = new \Twig\Loader\FilesystemLoader(resource_path('documents/'));
    // $file = 'supervisor_master_config.tmpl';
    $twig = new \Twig\Environment($loader);

    $phpBinPath = \App\Model\Setting::get('php_bin_path');
    $artisanPath = join_paths(base_path(), 'artisan');
    $cronjob  = "{$phpBinPath} -q {$artisanPath}";

    $params = [
        'php_artisan' => $cronjob, // The only parameter
    ];

    echo $twig->render($templateFile, $params);
}

function curl_download($url, $filepath)
{
    set_time_limit(0);

    // File to save the contents to
    $fp = fopen($filepath, 'w+');

    // Here is the file we are downloading, replace spaces with %20
    $ch = curl_init(str_replace(" ", "%20", $url));

    // give curl the file pointer so that it can write to it
    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

    $data = curl_exec($ch);// get curl response

    if (curl_errno($ch)) {
        // Something went wrong
        throw new Exception(curl_error($ch));
    }

    //done
    curl_close($ch);
}

function create_temp_db_table($fields, $data, Closure $callback)
{
    $name = "__tmp_from_array_".uniqid();
    $nameWithPrefix = table($name);
    $fieldsString = implode(', ', $fields);

    try {
        DB::statement("DROP TABLE IF EXISTS `{$nameWithPrefix}`;");
        DB::statement("CREATE TABLE `{$nameWithPrefix}` ({$fieldsString}) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;");
        DB::table($name)->insert($data);

        // For example, execute an SQL with the newly created table
        $callback($name);
    } finally {
        DB::statement("DROP TABLE IF EXISTS `{$nameWithPrefix}`;");
    }


    // DB::statement("CREATE INDEX `_index_email_{$nameWithPrefix}` ON `{$name}`(`email`);");
    // DB::statement("UPDATE `{$nameWithPrefix}` AS dup INNER JOIN `subscribers` s ON dup.email = s.email SET dup.selected_id = s.id WHERE s.mail_list_id = {$this->defaultMailList->id}");
}

function loadAppTemplate($name)
{
    $filePath = database_path('app_templates/' . $name . '.html');

    if (File::exists($filePath)) {
        return File::get($filePath);
        // Do something with the $content
    } else {
        // Handle file not found scenario
        abort(404, 'File not found');
    }
}


function convertPrice($amount, $fromCurrency, $toCurrency)
{
    $exchangeRates = config('exchange');

    // Check if the currencies exist in the exchange rates array
    if (!isset($exchangeRates[$fromCurrency])) {
        throw new Exception('From currency code ['.$fromCurrency.'] is not in exchange rates array. Update the exchange rates.');
    }

    // Check if the currencies exist in the exchange rates array
    if (!isset($exchangeRates[$toCurrency])) {
        throw new Exception('To currency code ['.$toCurrency.'] is not in exchange rates array. Update the exchange rates.');
    }

    // Convert the amount to the base currency (USD in this case)
    $amountInUSD = $amount / $exchangeRates[$fromCurrency];

    // Convert from USD to the target currency
    $convertedAmount = $amountInUSD * $exchangeRates[$toCurrency];

    return $convertedAmount;
}

function has_column($tableWithoutPrefix, $fieldName)
{
    try {
        return \Schema::hasColumn($tableWithoutPrefix, $fieldName);
    } catch (\Exception $ex) {
        // MariaDB 11 may return an error with Schema::hasColumn
    }

    $rawTableName = \DB::getTablePrefix().$tableWithoutPrefix;
    return count(DB::select("SHOW COLUMNS FROM `{$rawTableName}` LIKE '{$fieldName}'")) > 0;
}

function geoip_resolve($ipAddress)
{
    // Might throw exception like:
    // - GeoIp2\Exception\AddressNotFoundException  The address 127.0.0.1 is not in the database.
    // - InvalidArgumentException  The value "1.2.3.43333" is not a valid IP address.

    $dbsource = base_path(\App\Model\Setting::get('geoip.sqlite.dbname'));

    if (!file_exists($dbsource)) {
        throw new \Exception("GeoIP DB does not exist at ".$dbsource);
    }

    $geoip = new \GeoIp2\Database\Reader($dbsource);
    $city = $geoip->city($ipAddress);

    // GeoIp2\Model\City
    return $city;
}

function handlehtmlentities($string)
{
    return mb_encode_numericentity(htmlspecialchars_decode(htmlentities($string, ENT_NOQUOTES, 'UTF-8', false), ENT_NOQUOTES), [0x80, 0x10FFFF, 0, ~0], 'UTF-8');
}

function transform_tags(array $tags, $text)
{
    $search = [];
    $replace = [];

    foreach ($tags as $tag => $value) {
        if (!empty($tag)) {
            $search[] = '{'.$tag.'}';
            $replace[] = $value ?? '#';
        }
    }

    return str_replace($search, $replace, $text);
}