<?php

/**
 * for checking the ajax request
 * @return <type>
 */
function is_ajax()
{
    return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
}

/**
 *
 * @param <type> $message string  to be displayed
 * @return <type> html string
 */
function set_success_limiters($message)
{
    return '<p class="success">' . $message . '</p>';
}

/**
 *
 * @param int $page as page number of current page
 * @param <type> $rp as rows per page
 * @param <type> $total as total number of rows
 * @param int $limit as limit of pages to be displayed
 * @return <type>
 */
function paging($page, $rp, $total, $limit)
{
    $limit -= 1;

    $mid = floor($limit / 2);

    // if row per page == 0 - set it to 1
    if (empty($rp)) {
        $rp = 1;
    }

    if ($total > $rp) {
        $numpages = ceil($total / $rp);
    } else {
        $numpages = 1;
    }

    if ($page > $numpages) {
        $page = $numpages;
    }

    $npage = $page;

    while ($npage - 1 > 0 && $npage > $page - $mid && $npage > 0) {
        $npage -= 1;
    }

    $lastpage = $npage + $limit;

    if ($lastpage > $numpages) {
        $npage = $numpages - $limit + 1;
        if ($npage < 0) {
            $npage = 1;
        }
        $lastpage = $npage + $limit;
        if ($lastpage > $numpages) {
            $lastpage = $numpages;
        }
    }

    while ($lastpage - $npage < $limit) {
        $npage -= 1;
    }

    if ($npage < 1) {
        $npage = 1;
    }

    //echo $npage; exit;

    $paging['first'] = 1;
    if ($page > 1) {
        $paging['prev'] = $page - 1;
    } else {
        $paging['prev'] = 1;
    }
    $paging['start'] = $npage;
    $paging['end'] = $lastpage;
    $paging['page'] = $page;
    if ($page + 1 < $numpages) {
        $paging['next'] = $page + 1;
    } else {
        $paging['next'] = $numpages;
    }
    $paging['last'] = $numpages;
    $paging['total'] = $total;
    $paging['iend'] = $page * $rp;
    $paging['istart'] = $page * $rp - $rp + 1;

    $paging['nextSet'] = $lastpage + 1;
    $paging['prevSet'] = $npage - 1;

    if ($page * $rp > $total) {
        $paging['iend'] = $total;
    }

    $paging['rp'] = $rp;
    return $paging;
}

/**
 * Return UTC timestamp
 *
 * @return <int> timestamp
 */
function getUtcTimestamp()
{
    $script_tz = date_default_timezone_get(); // backup the default timezone
    date_default_timezone_set('UTC');
    $time = time();
    date_default_timezone_set($script_tz);
    return $time;
}

/**
 * Formats a timestamp and then gives it a color code
 * amber when time difference is greater than 6 hours
 * red when older than 7 days
 * @param int $timestamp
 * @param bool $noColor HTML with no color class
 * @param bool $onlyDate no html output
 * @return <string> datetime with wrapped in span with colorclass
 *
 */
function getDateStatus($timestamp, $noColor = false, $onlyDate = false, $timezone = 'local', $format = 'Y-m-d H:i')
{
    $timestamp = intval($timestamp, 10);
    // check for 0 val
    if ($timestamp <= 0) {
        if ($onlyDate) {
            return 0;
        }
        return '<span class="red">(never)</span>';
    }

    $colorClass = '';
    $now = time();
    // 6 hours
    if ($now - $timestamp > 6 * 3600) {
        $colorClass = 'amber';
        $iconClass = 'yellowDateStatus';
    }
    // 7 days
    if ($now - $timestamp > 7 * 24 * 3600) {
        $colorClass = 'red';
        $iconClass = 'redDateStatus';
    }
    if ($noColor) {
        $colorClass = '';
    }

    /**
     * return the date with respect to users time zone set on the profile
     * @todo: get rid of session dependency, because session does not exist in case of CLI - task ENT-5412
     *
     */
    if (CLI == false) {
        $CI = &get_instance();
        $CI->load->model('ProfileModel');
        $timezone = $CI->ProfileModel->getCurrentTimeZone($CI->session->userdata('username'))['value'];
    }

    date_default_timezone_set($timezone);

    $formattedDate = date($format, $timestamp);
    // add time zone info
    $timeZoneinfo = date('P', $timestamp);
    $formattedDate = $formattedDate . $timeZoneinfo;
    if ($onlyDate) {
        return $formattedDate;
    }

    return '<span class="localtime ' . $colorClass . '">' . $formattedDate . '</span>';
}

/**
 * Format seconds to day , hour min and seconds
 * @param type $s no of seconds
 * @return string
 */
function formatSeconds($s)
{
    $d = intval($s / 86400);
    $s -= $d * 86400;
    if ($s <= 0) {
        return '0s';
    }
    $str = '';
    $h = intval($s / 3600);
    $s -= $h * 3600;

    $m = intval($s / 60);
    $s -= $m * 60;

    if ($d) {
        $str = $d . 'd ';
    }
    if ($h) {
        $str .= $h . 'h ';
    }
    if ($m) {
        $str .= $m . 'm ';
    }
    if ($s) {
        $str .= $s . 's';
    }

    return $str;
}

/**
 * Returrns second offset from UTC of the timezone
 * @param string $timezone gmt or local
 * @param string $formatter any valid time zone formatter used by date function
 * @return string
 */
function getTimeZoneOffset($timezone = 'local', $formatter = 'Z', $userTimeZone = null)
{
    /**
     * return the date with respect to users time zone set on the session
     *
     */
    $script_tz = date_default_timezone_get(); // backup the default timezone

    if (empty($userTimeZone)) {
        $CI = &get_instance();
        $userTimeZone = $CI->session->userdata('user_timezone');
    }

    if ($timezone == 'local') {
        //if to be converted to local time.
        $tz_string = $userTimeZone;
        if (!date_default_timezone_set($tz_string)) {
            date_default_timezone_set($script_tz); // if not invalid timezone identifier set it.
        }
    } elseif ($timezone == 'gmt') {
        date_default_timezone_set('UTC');
    }

    $timezone = date($formatter, time()); // seconds offset
    return $timezone;
}

function is_execptional_body($body)
{
    $list = ['usebundle', 'edit_line'];
    if (array_search($body, $list) !== false) {
        return true;
    }
    return false;
}

function array_msort($array, $cols, $natsort = false)
{
    $colarr = [];
    foreach ($cols as $col => $order) {
        $colarr[$col] = [];
        foreach ($array as $k => $row) {
            $colarr[$col]['_' . $k] = strtolower($row[$col]);
        }
    }
    $eval = 'array_multisort(';
    foreach ($cols as $col => $order) {
        $eval .= '$colarr[\'' . $col . '\'],' . $order . ',';
    }
    $eval = substr($eval, 0, -1) . ');';
    eval($eval);
    if ($natsort) {
        foreach ($cols as $col => $order) {
            natsort($colarr[$col]);
        }
    }
    $ret = [];
    foreach ($colarr as $col => $arr) {
        foreach ($arr as $k => $v) {
            $k = substr($k, 1);
            if (!isset($ret[$k])) {
                $ret[$k] = $array[$k];
            }
            $ret[$k][$col] = $array[$k][$col];
        }
    }
    return $ret;
}

/**
 * Lang
 *
 * Fetches a language variable and optionally outputs a form label for tooltips
 *
 * @access	public
 * @param	string	the language line
 * @param	string	the id of the form element
 * @return	string
 */
function tooltip($line, $id = '', $forinput = false)
{
    $CI = &get_instance();

    $line = $CI->lang->line($line);

    if ($id != '') {
        $line = '<label for="' . $id . '">' . $line . '</label>';
    } elseif ($forinput) {
        $line = '<span class="hint">' . $line . '</span>';
    }

    return $line;
}

/**
 * time conversion
 *
 * converts time difference into readable format (like: "12d 05h 10m")
 *
 * @access	public
 * @param	string   delta timestamp
 * @return	string
 */
function time_diff_conv($s)
{
    $string = '';
    $t = [
        //suffixes
        'days' => 86400,
        'hours' => 3600,
        'minutes' => 60,
    ];
    $s = abs($s);
    foreach ($t as $key => &$val) {
        $$key = floor($s / $val);
        $s -= $$key * $val;
        $string .= $$key == 0 ? '' : $$key . " $key ";
    }

    return $string;
}

/**
 *
 * @param type $exception
 * @return string
 */
if (!function_exists('generate_errormessage')) {
    function generate_errormessage($exception)
    {
        $message = '';
        $CI = &get_instance();

        switch (ENVIRONMENT) {
            case 'development':
                $message =
                    '<div style="border:1px solid #990000;padding-left:20px;margin:0 0 10px 0;">
            <h1>Error Occured: ' .
                    $exception->getMessage() .
                    '</h1><pre>' .
                    $exception->getTraceAsString() .
                    '<pre></div>';
                log_message('error', $exception->getMessage() . 'Trace: ' . $exception->getTraceAsString());
                break;

            case 'testing':
                $message = '<h1>' . $exception->getMessage() . '</h1><pre>' . $exception->getTraceAsString() . '<pre>';
                log_message('error', $exception->getMessage() . 'Trace: ' . $exception->getTraceAsString());

            // no break
            case 'production':
                $message = '<div class="error">' . $CI->lang->line('cf_mod_expection_error') . '</div>';
                log_message(
                    'error',
                    $exception->getMessage() . 'File: ' . $exception->getFile() . ' Line: ' . $exception->getLine(),
                );
                break;

            default:
                log_message('error', $exception->getMessage() . 'Trace: ' . $exception->getTraceAsString());
        }
        return $message;
    }
}

/**
 * Error Handler with custom template
 *
 * This function lets us invoke the exception class and
 * display errors using the standard error template located
 * in application/errors/errors.php
 * This function will send the error page directly to the
 * browser and exit.
 *
 * @access	public
 * @return	void
 */
function show_error_custom(
    $message,
    $status_code = 500,
    $heading = 'An Error Was Encountered',
    $template = 'error_plain',
) {
    $_error = &load_class('Exceptions', 'core');
    echo $_error->show_error($heading, $message, $template, $status_code);
    exit();
}

/**
 * Deep merge two arrays.
 * - Keys only in $b are added to $a.
 * - When both sides are arrays, the values are merged recursively.
 * - When there is a scalar/type conflict, values are combined into a
 *   deduplicated array; a single-element result is unwrapped back to a scalar.
 *
 * @param array $a Base array
 * @param array $b Array to merge into $a
 * @return array
 */
function arrayDeepMerge(array $a, array $b): array
{
    foreach ($b as $key => $value) {
        if (!array_key_exists($key, $a)) {
            $a[$key] = $value;
        } elseif (is_array($value) && is_array($a[$key])) {
            $a[$key] = arrayDeepMerge($a[$key], $value);
        } else {
            // Both are scalars (or mixed) — combine into a deduplicated array,
            // unwrap back to scalar when only one unique value remains.
            $merged = array_values(array_unique(array_merge((array) $a[$key], (array) $value)));
            $a[$key] = count($merged) === 1 ? $merged[0] : $merged;
        }
    }
    return $a;
}

/**
 * Diff arrays
 * @param array $aArray1
 * @param array $aArray2
 * @return array
 */
function arrayRecursiveDiff($aArray1, $aArray2)
{
    $aReturn = [];

    foreach ($aArray1 as $mKey => $mValue) {
        if (array_key_exists($mKey, $aArray2)) {
            if (is_array($mValue)) {
                $aRecursiveDiff = arrayRecursiveDiff($mValue, $aArray2[$mKey]);
                if (count($aRecursiveDiff)) {
                    $aReturn[$mKey] = $aRecursiveDiff;
                }
            } else {
                if ($mValue != $aArray2[$mKey]) {
                    $aReturn[$mKey] = $mValue;
                }
            }
        } else {
            $aReturn[$mKey] = $mValue;
        }
    }
    return $aReturn;
}

function getCuttedText($text, $maxwords = 30, $maxchar = 300)
{
    $sep = "'/ /'";
    $words = preg_split($sep, $text);

    $char = iconv_strlen($text, 'utf-8');
    $bigger = false;

    if (count($words) > $maxwords) {
        $text = join($sep, array_slice($words, 0, $maxwords));
        $bigger = true;
    }

    if ($char > $maxchar) {
        $text = iconv_substr($text, 0, $maxchar, 'utf-8');
        $bigger = true;
    }

    if ($bigger == true) {
        $text .= '...';
    }

    return $text;
}

/**
 * Return application which is avalable for user
 *
 * @return type
 */
function getAvailableApplications()
{
    $CI = &get_Cf_instance();

    $app_tmp = $CI->apps_model->getFrontendApps();

    return $app_tmp;
}

function getApplicationBadge($id)
{
    $CI = &get_Cf_instance();
    $badge = '';

    switch ($id) {
        case 'policy':
            $CI->load->model('policy/policy_model');
            $CI->policy_model->setUsername($CI->session->userdata('username'));
            $data = $CI->policy_model->getPolicyStatistics(['NOTKEPT']);
            $notKeptCount = 0;
            array_walk($data, function ($item) use (&$notKeptCount) {
                $notKeptCount += (int) $item['notkept_count'];
            });
            if (is_int($notKeptCount) && $notKeptCount > 0) {
                $badge = sprintf(
                    '<span class="app-badge badge badge-important">%s</span>',
                    $notKeptCount > 99 ? '99+' : $notKeptCount,
                );
            }
            break;
    }

    return $badge;
}

/**
 * Set application, usually used in controller __construct
 *
 * @param type $name
 */
function setCurrentApplication($name = '')
{
    if (!empty($name)) {
        $CI = &get_instance();
        $CI->session->set_userdata('current_application', $name);
    }
}

/**
 * Return current application, like hosts, advancedreport ...
 *
 * @return type
 */
function getCurrentApplication()
{
    $CI = &get_instance();
    return $CI->session->userdata('current_application');
}

/**
 * generate link to app
 */
function generateLink($appId, $html, $text)
{
    $CI = &get_Cf_instance();
    if ($CI->apps_model->isActive($appId)) {
        return $html;
    } else {
        return '<span>' . $text . '</span>';
    }
}

/**
 *  function push_file($path, $name)
 * This function pushes a file out to a user for download.
 * Native download is memory inefficient for large files
 * @param    STRING    $path    The full absolute path to the file to be pushed.
 * @param    STRING    $name    The file name of the file to be pushed.
 */
function push_file($path, $name)
{
    $CI = &get_instance();
    // make sure it's a file before doing anything!
    if (is_file($path)) {
        // required for IE
        if (ini_get('zlib.output_compression')) {
            ini_set('zlib.output_compression', 'Off');
        }

        // get the file mime type using the file extension
        $CI->load->helper('file');

        /**
         * This uses a pre-built list of mime types compiled by Codeigniter found at
         * /system/application/config/mimes.php
         * Codeigniter says this is prone to errors and should not be dependant upon
         * However it has worked for me so far.
         * You can also add more mime types as needed.
         */
        $mime = get_mime_by_extension($path);

        if (!is_readable($path)) {
            $template = is_ajax() ? 'error_plain' : 'error_general';
            $message = "Unable to read file: $path. Permission denied";
            show_error_custom($message, 500, 'Permission denied', $template);
        }
        @ob_end_clean();
        // Build the headers to push out the file properly.
        header('Pragma: public'); // required
        header('Expires: 0'); // no cache
        header('Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0');
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($path)) . ' GMT');
        header('Cache-Control: private', false);
        header('Content-Type: ' . $mime); // Add the mime type from Code igniter.
        header('Content-Disposition: attachment; filename="' . basename($name) . '"'); // Add the file name
        header('Content-Transfer-Encoding: binary');
        header('Content-Length: ' . filesize($path)); // provide file size
        header('Connection: close');
        readfile($path); // push it out
        exit();
    }
}

function push_file_remote($url, $filename = 'report.csv')
{
    set_time_limit(0);
    header('Content-Type: application/octet-stream');
    header(sprintf('Content-Disposition: attachment; filename="%s"', $filename));
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Pragma: no-cache');

    readfile($url); // is there a better function ?
    exit();
}

/**
 * Element object
 *
 * Lets you determine whether an element index is set and whether it has a value.
 * If the element is empty it returns FALSE (or whatever you specify as the default value.)
 *
 * @access	public
 * @param	string
 * @param	array
 * @param	mixed
 * @return	mixed	depends on what the object contains
 */
if (!function_exists('element_object')) {
    function element_object($item, $obj, $default = false)
    {
        if (!isset($obj->$item) or $obj->$item == '') {
            return $default;
        }

        return $obj->$item;
    }
}

/**
 * Quick debug function
 *
 * @param type $data - variable to debug
 * @param type $str - additional comment
 */
function debug2($data, $str = '')
{
    if ($str != '') {
        echo "--- $str ---<br>";
    }
    //echo "LINE=".__LINE__.__FILE__;
    echo '<pre style="font-size: 9px">';
    print_r($data);
    echo '</pre>';
}

/* this function is used to return an array of icons for application and
 * MUST be deleted once we have proper icons for all applications */

function applicationsIconsToCSS_Stub()
{
    $css_apps_classes = [];
    $css_apps_classes['engineering'] = 'icon-sitemap';
    $css_apps_classes['advancedreports'] = 'icon-table';
    $css_apps_classes['docs'] = 'icon-book';
    $css_apps_classes['policies'] = 'icon-policy';
    $css_apps_classes['policy'] = 'icon-file';
    $css_apps_classes['build'] = 'icon-build';
    $css_apps_classes['knowledge'] = 'icon-exclamation-sign';
    $css_apps_classes['monitoring'] = 'icon-stethoscope';
    $css_apps_classes['notifications'] = 'icon-check';
    $css_apps_classes['hub_management'] = 'icon-rocket';
    $css_apps_classes['build_app'] = 'icon-build';
    $css_apps_classes['groups_app'] = 'group-icon';

    $css_apps_classes['settings'] = 'icon-cogs ';
    $css_apps_classes['dashboard'] = 'icon-dashboard';

    return $css_apps_classes;
}

/**
 * Search SHA=  on the beggining of the string and convert it to PK_SHA_
 * leave string as is if not found.
 *
 *
 * @param <string> $key - host key
 * @return <string> host key started with PK_SHA_
 */
function convertSHAToPK_SHA($key)
{
    if (substr(strtolower($key), 0, 4) === 'sha=') {
        return substr_replace($key, 'PK_SHA_', 0, 4);
    }
    return $key;
}

function secs_to_h($secs)
{
    $units = [
        'week' => 7 * 24 * 3600,
        'day' => 24 * 3600,
        'hour' => 3600,
        'minute' => 60,
        'second' => 1,
    ];

    // specifically handle zero
    if ($secs == 0) {
        return '0 seconds';
    }

    $s = '';

    foreach ($units as $name => $divisor) {
        if ($quot = intval($secs / $divisor)) {
            $s .= "$quot $name";
            $s .= (abs($quot) > 1 ? 's' : '') . ', ';
            $secs -= $quot * $divisor;
        }
    }

    return substr($s, 0, -2);
}

/**
 * Returns the PHP Array for PGSQL array type
 * For single dimension array only
 * @param  [string] $arrayString [Pgsql array string returned from DB]
 * @return [array]               [PHP array representation of the string]
 */
function PgsqlArrayToPhpArray($literal = '')
{
    $phpArray = [];
    if (empty($literal)) {
        return $phpArray;
    }
    $matchValue = trim($literal);
    preg_match('/^{(.*)}$/i', $matchValue, $matches);
    if (!empty($matches[1])) {
        $phpArray = str_getcsv($matches[1]);
    }
    return $phpArray;
}

/**
 * Returns cleaned up comma separated string for PGSQL array type
 * For single dimension array only
 * @param  [string] $arrayString [Pgsql array string returned from DB]
 * @return [string]              [cleaned comma separated list]
 */
function PgsqlArrayToCommaSeparatedList($arrayString)
{
    $array = PgsqlArrayToPhpArray($arrayString);
    // remove quotes if found
    array_walk($array, function (&$value, $key) {
        $value = trim($value, "'"); // trim by quotes
        $value = preg_replace('/^&quot;|&quot;$/', '', $value);
    });
    $list = implode(', ', $array);
    return $list;
}

function PhpArrayToPgsql($set)
{
    if (empty($set)) {
        return '{}';
    }
    settype($set, 'array'); // can be called with a scalar or array
    $result = [];

    foreach ($set as $t) {
        if (is_array($t)) {
            $result[] = PhpArrayToPgsql($t);
        } else {
            $t = str_replace('"', '\\"', $t); // escape double quote
            if (!is_numeric($t)) {
                // quote only non-numeric values
                $t = '"' . $t . '"';
            }
            $result[] = $t;
        }
    }
    return '{' . implode(',', $result) . '}'; // format
}

/**
 * Encode html special characters to prevent JS injections
 * @param array $array
 */
function htmlSpecialCharsArrayEncode(array &$array)
{
    $keysToSkip = ['query'];

    foreach ($array as $key => $value) {
        if (is_array($value)) {
            htmlSpecialCharsArrayEncode($array[$key]);
        } else {
            $jsonDecoded = @json_decode($value, JSON_OBJECT_AS_ARRAY);
            if (is_array($jsonDecoded)) {
                //if json_decode returns an array than each value should be processed individually
                //instead of html special chars encoding of whole json object
                $array[$key] = $jsonDecoded;
                htmlSpecialCharsArrayEncode($array[$key]);
                $array[$key] = json_encode($array[$key]);
            } else {
                $array[$key] =
                    is_string($value) && !in_array(strval($key), $keysToSkip) ? htmlspecialchars($value) : $value;
            }
        }
    }
}

/**
 * Whether API responses proxied through Http_client should be html-encoded.
 *
 * Legacy AngularJS views inject API values as HTML and rely on the encoding,
 * React clients escape on render and send `Cf-Html-Encode: false` to opt out.
 */
function shouldHtmlEncodeApiResponses(): bool
{
    $header = strtolower(trim($_SERVER['HTTP_CF_HTML_ENCODE'] ?? 'true'));
    return $header !== 'false';
}

function getComplianceColor($score)
{
    if ($score < 50) {
        $color = 'red';
    } elseif ($score > 50 && $score < 75) {
        $color = 'orange';
    } else {
        $color = 'green';
    }
    return $color;
}

function getThemeClass()
{
    return isDarkMode() === true ? 'dark' : 'light';
}

function isDarkMode()
{
    $CI = &get_instance();
    $darkMode = get_cookie('dark_mode');
    if (!isset($darkMode) || empty($darkMode)) {
        $CI->load->model('ProfileModel');
        $darkMode = $CI->ProfileModel->getDarkMode($CI->session->userdata('username'));
        set_cookie('dark_mode', $darkMode, 7200);
    }
    return $darkMode == 1;
}

/**
 * @param $url
 * @return array|mixed
 */
function getCertificateInfo($url)
{
    $curlHandler = curl_init($url);

    curl_setopt_array($curlHandler, [
        CURLOPT_VERBOSE => true,
        CURLOPT_CERTINFO => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER => false,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
    ]);

    curl_exec($curlHandler);
    $result = curl_getinfo($curlHandler);
    curl_close($curlHandler);

    return $result['certinfo'] ?? [];
}

/**
 * @param $url
 * @return string scheme://host
 */
function getSchemeHostFromUrl($url)
{
    $parsedURL = parse_url($url);
    return "{$parsedURL['scheme']}://{$parsedURL['host']}";
}

if (!function_exists('fileTotalLines')) {
    function fileTotalLines($filePath)
    {
        $file = new SplFileObject($filePath);
        $file->seek($file->getSize()); // seek to the latest line
        return $file->key(); // return the current line number
    }
}

if (!function_exists('isInternetConnectionAvailable')) {
    function isInternetConnectionAvailable(): bool
    {
        return (bool) @fsockopen(
            CHECK_INTERNET_CONNECTION_HOSTNAME,
            CHECK_INTERNET_CONNECTION_PORT,
            $errno,
            $errstr,
            CHECK_INTERNET_CONNECTION_TIMEOUT_SECONDS,
        );
    }
}

if (!function_exists('escapeArrayValues')) {
    function escapeArrayValues(array $array): array
    {
        $ci = &get_instance();
        return array_map(function ($item) use ($ci) {
            return $ci->db->escape_str($item);
        }, $array);
    }
}

if (!function_exists('isSuperHub')) {
    function isSuperHub(): bool
    {
        $ci = &get_instance();
        $ci->load->model('hub_management/setup_hub_model');
        return $ci->setup_hub_model->isSuperHub();
    }
}
if (!function_exists('recursiveHtmlEntityDecode')) {
    function recursiveHtmlEntityDecode($data)
    {
        if (is_array($data)) {
            foreach ($data as $key => $value) {
                $data[$key] = recursiveHtmlEntityDecode($value);
            }
        } elseif (is_string($data)) {
            $data = html_entity_decode($data, ENT_QUOTES);
        }
        return $data;
    }
}

if (!function_exists('log_level_for_exception')) {
    /**
     * Determines the appropriate log level based on the type of exception.
     *
     * If the exception is an instance of `HttpClient_Forbidden|CFAPIException`, the log level will be set to `debug`.
     * Otherwise, it will use the provided fallback log level.
     */
    function log_level_for_exception($exception, $fallback = 'error'): string
    {
        return in_array(true, [
            $exception instanceof HttpClient_Forbidden,
            $exception instanceof HttpClient_InvalidRecord,
            $exception instanceof HttpClient_NotFound,
            $exception instanceof HttpClient_Unauthorized,
            $exception instanceof CFAPIException,
        ])
            ? 'debug'
            : $fallback;
    }
}
