<?php
/**
 * @package    Proxim
 * @author     Dominic Tanui <dominic@codeworldweb.com | https://codeworldweb.com>
 * @copyright  2019 Proxim
 * @version    1.5.0
 * @since      File available since Release 1.0.0
 */

namespace Proxim;

use Proxim\Cache\Cache;
use Proxim\Exception\ProximException;
use Slim\Http\Request;

/**
 * Tools
 *
 */
class Tools {
    protected static $file_exists_cache = array();
    protected static $_user_platform;
    protected static $_user_browser;
    protected static $request;

    public static $round_mode = null;

    public function __construct(Request $request = null) {
        if ($request) {
            self::$request = $request;
        }
    }

    /**
     * Reset the request set during the first new Tools($request) call
     */
    public static function resetRequest() {
        self::$request = null;
    }

    /**
    * Random generator
    *
    * @param int $length Desired length (optional)
    * @param string $flag Output type (NUMERIC, ALPHANUMERIC, NO_NUMERIC, RANDOM)
    * @return bool|string Random strin
    */
    public static function randomGen($length = 8, $flag = 'ALPHANUMERIC') {
        $length = (int)$length;

        if ($length <= 0) {
            return false;
        }

        switch ($flag) {
            case 'NUMERIC':
                $str = '0123456789';
                break;
            case 'NO_NUMERIC':
                $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
                break;
            case 'RANDOM':
                $num_bytes = ceil($length * 0.75);
                $bytes = self::getBytes($num_bytes);
                return substr(rtrim(base64_encode($bytes), '='), 0, $length);
            case 'ALPHANUMERIC':
            default:
                $str = 'abcdefghijkmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
                break;
        }

        $bytes = Tools::getBytes($length);
        $position = 0;
        $result = '';

        for ($i = 0; $i < $length; $i++) {
            $position = ($position + ord($bytes[$i])) % strlen($str);
            $result .= $str[$position];
        }

        return $result;
    }

    /**
     * Random bytes generator
     *
     * @param int $length Desired length of random bytes
     *
     * @return bool|string Random bytes
     */
    public static function getBytes($length) {
        $length = (int) $length;

        if ($length <= 0) {
            return false;
        }

        $bytes = openssl_random_pseudo_bytes($length, $cryptoStrong);

        if ($cryptoStrong === true) {
            return $bytes;
        }

        return false;
    }

    /**
     * Another replacement for rand() using OpenSSL.
     * Note that a solution where the result is truncated using the modulo operator ( % ) is not cryptographically secure, as the generated numbers are not equally distributed, i.e. some numbers may occur more often than others.
     * A better solution than using the modulo operator is to drop the result if it is too large and generate a new one.
     *
     * @param int $min | $max create a random number between $min and $max.
     * @return String $min and $rnd
     * 
     * @see http://us1.php.net/manual/en/function.openssl-random-pseudo-bytes.php#104322
     */
    public static function cryptoRandSecure($min, $max) {
        $range = $max - $min;
        if ($range < 1) return $min; // not so random...
        $log = ceil(log($range, 2));
        $bytes = (int) ($log / 8) + 1; // length in bytes
        $bits = (int) $log + 1; // length in bits
        $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
        do {
            $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
            $rnd = $rnd & $filter; // discard irrelevant bits
        } while ($rnd > $range);
        return $min + $rnd;
    }

    public static function getUserPlatform()
    {
        if (isset(self::$_user_platform)) {
            return self::$_user_platform;
        }

        $user_agent = $_SERVER['HTTP_USER_AGENT'];
        self::$_user_platform = 'unknown';

        if (preg_match('/linux/i', $user_agent)) {
            self::$_user_platform = 'Linux';
        } elseif (preg_match('/macintosh|mac os x/i', $user_agent)) {
            self::$_user_platform = 'Mac';
        } elseif (preg_match('/windows|win32/i', $user_agent)) {
            self::$_user_platform = 'Windows';
        }

        return self::$_user_platform;
    }

    public static function getUserBrowser()
    {
        if (isset(self::$_user_browser)) {
            return self::$_user_browser;
        }

        $user_agent = $_SERVER['HTTP_USER_AGENT'];
        self::$_user_browser = 'unknown';

        if (preg_match('/MSIE/i', $user_agent) && !preg_match('/Opera/i', $user_agent)) {
            self::$_user_browser = 'Internet Explorer';
        } elseif (preg_match('/Firefox/i', $user_agent)) {
            self::$_user_browser = 'Mozilla Firefox';
        } elseif (preg_match('/Chrome/i', $user_agent)) {
            self::$_user_browser = 'Google Chrome';
        } elseif (preg_match('/Safari/i', $user_agent)) {
            self::$_user_browser = 'Apple Safari';
        } elseif (preg_match('/Opera/i', $user_agent)) {
            self::$_user_browser = 'Opera';
        } elseif (preg_match('/Netscape/i', $user_agent)) {
            self::$_user_browser = 'Netscape';
        }

        return self::$_user_browser;
    }

    /**
    * Generate date form
    *
    * @param int $year Year to select
    * @param int $month Month to select
    * @param int $day Day to select
    * @return array $tab html data with 3 cells :['days'], ['months'], ['years']
    *
    */
    public static function dateYears()
    {
        $tab = array();
        for ($i = date('Y'); $i >= 1900; $i--) {
            $tab[] = $i;
        }
        return $tab;
    }

    public static function dateDays()
    {
        $tab = array();
        for ($i = 1; $i != 32; $i++) {
            $tab[] = $i;
        }
        return $tab;
    }

    public static function dateMonths()
    {
        $tab = array();
        for ($i = 1; $i != 13; $i++) {
            $tab[$i] = date('F', mktime(0, 0, 0, $i, date('m'), date('Y')));
        }
        return $tab;
    }

    public static function hourGenerate($hours, $minutes, $seconds)
    {
        return implode(':', array($hours, $minutes, $seconds));
    }

    public static function dateFrom($date)
    {
        $tab = explode(' ', $date);
        if (!isset($tab[1])) {
            $date .= ' '.Tools::hourGenerate(0, 0, 0);
        }
        return $date;
    }

    public static function dateTo($date)
    {
        $tab = explode(' ', $date);
        if (!isset($tab[1])) {
            $date .= ' '.Tools::hourGenerate(23, 59, 59);
        }
        return $date;
    }

    public static function strtolower($str)
    {
        if (is_array($str)) {
            return false;
        }
        if (function_exists('mb_strtolower')) {
            return mb_strtolower($str, 'utf-8');
        }
        return strtolower($str);
    }

    public static function strlen($str, $encoding = 'UTF-8')
    {
        if (is_array($str)) {
            return false;
        }
        $str = html_entity_decode($str, ENT_COMPAT, 'UTF-8');
        if (function_exists('mb_strlen')) {
            return mb_strlen($str, $encoding);
        }
        return strlen($str);
    }

    public static function stripslashes($string)
    {
        if (PROX_MAGIC_QUOTES_GPC) {
            $string = stripslashes($string);
        }
        return $string;
    }

    public static function strtoupper($str)
    {
        if (is_array($str)) {
            return false;
        }
        if (function_exists('mb_strtoupper')) {
            return mb_strtoupper($str, 'utf-8');
        }
        return strtoupper($str);
    }

    public static function substr($str, $start, $length = false, $encoding = 'utf-8')
    {
        if (is_array($str)) {
            return false;
        }
        if (function_exists('mb_substr')) {
            return mb_substr($str, (int)$start, ($length === false ? Tools::strlen($str) : (int)$length), $encoding);
        }
        return substr($str, $start, ($length === false ? Tools::strlen($str) : (int)$length));
    }

    public static function strpos($str, $find, $offset = 0, $encoding = 'UTF-8')
    {
        if (function_exists('mb_strpos')) {
            return mb_strpos($str, $find, $offset, $encoding);
        }
        return strpos($str, $find, $offset);
    }

    public static function strrpos($str, $find, $offset = 0, $encoding = 'utf-8')
    {
        if (function_exists('mb_strrpos')) {
            return mb_strrpos($str, $find, $offset, $encoding);
        }
        return strrpos($str, $find, $offset);
    }

    public static function ucfirst($str)
    {
        return Tools::strtoupper(Tools::substr($str, 0, 1)).Tools::substr($str, 1);
    }

    public static function ucwords($str)
    {
        if (function_exists('mb_convert_case')) {
            return mb_convert_case($str, MB_CASE_TITLE);
        }
        return ucwords(Tools::strtolower($str));
    }

    /**
     * @return bool true if php-cli is used
     */
    public static function isPHPCLI()
    {
        return defined('STDIN') || (Tools::strtolower(php_sapi_name()) == 'cli' && (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])));
    }

    /**
     * Fix native uasort see: http://php.net/manual/en/function.uasort.php#114535.
     *
     * @param $array
     * @param $cmp_function
     */
    public static function uasort(&$array, $cmp_function)
    {
        if (count($array) < 2) {
            return;
        }
        $halfway = count($array) / 2;
        $array1 = array_slice($array, 0, $halfway, true);
        $array2 = array_slice($array, $halfway, null, true);

        self::uasort($array1, $cmp_function);
        self::uasort($array2, $cmp_function);
        if (call_user_func($cmp_function, end($array1), reset($array2)) < 1) {
            $array = $array1 + $array2;

            return;
        }
        $array = array();
        reset($array1);
        reset($array2);
        while (current($array1) && current($array2)) {
            if (call_user_func($cmp_function, current($array1), current($array2)) < 1) {
                $array[key($array1)] = current($array1);
                next($array1);
            } else {
                $array[key($array2)] = current($array2);
                next($array2);
            }
        }
        while (current($array1)) {
            $array[key($array1)] = current($array1);
            next($array1);
        }
        while (current($array2)) {
            $array[key($array2)] = current($array2);
            next($array2);
        }

        return;
    }

    /**
     * Hash password
     *
     * @param string $passwd String to hash
     *
     * @return string Hashed password
     */
    public static function encrypt($passwd) {
        return self::hash($passwd);
    }

    /**
    * Hash password
    *
    * @param string $passwd String to has
     *
     * @return string Hashed password
     *
     */
    public static function hash($passwd) {
        return md5(PROX_COOKIE_KEY.$passwd);
    }

    /**
     * Hash data string
     *
     * @param string $data String to encrypt
     *
     * @return string Hashed IV
     */
    public static function encryptIV($data) {
        return self::hashIV($data);
    }

    /**
     * Hash data string
     *
     * @param string $data String to encrypt
     *
     * @return string Hashed IV
     *
     */
    public static function hashIV($data) {
        return md5(PROX_COOKIE_IV.$data);
    }


    /**
     * Convert \n and \r\n and \r to <br />
     *
     * @param string $string String to transform
     * @return string New string
     */
    public static function nl2br($str)
    {
        return str_replace(array("\r\n", "\r", "\n"), '<br />', $str);
    }

    /**
     * Delete unicode class from regular expression patterns
     * @param string $pattern
     * @return string pattern
     */
    public static function cleanNonUnicodeSupport($pattern)
    {
        if (!defined('PREG_BAD_UTF8_OFFSET')) {
            return $pattern;
        }
        return preg_replace('/\\\[px]\{[a-z]{1,2}\}|(\/[a-z]*)u([a-z]*)$/i', '$1$2', $pattern);
    }

    /**
     * getProtocol return the set protocol according to configuration (http[s])
     * @param bool $use_ssl true if require ssl
     * @return String (http|https)
     */
    public static function getProtocol($use_ssl = null) {
        return (!is_null($use_ssl) && $use_ssl ? 'https://' : 'http://');
    }

    /**
    * Get the server variable SERVER_NAME
    *
    * @return string server name
    */
    public static function getServerName() {
        if (isset($_SERVER['HTTP_X_FORWARDED_SERVER']) && $_SERVER['HTTP_X_FORWARDED_SERVER']) {
            return $_SERVER['HTTP_X_FORWARDED_SERVER'];
        }
        return $_SERVER['SERVER_NAME'];
    }

    /**
    * Get the server variable REMOTE_ADDR, or the first ip of HTTP_X_FORWARDED_FOR (when using proxy)
    *
    * @return string $remote_addr ip of client
    */
    public static function getRemoteAddr() {
        if (function_exists('apache_request_headers')) {
            $headers = apache_request_headers();
        } else {
            $headers = $_SERVER;
        }

        if (array_key_exists('X-Forwarded-For', $headers)) {
            $_SERVER['HTTP_X_FORWARDED_FOR'] = $headers['X-Forwarded-For'];
        }

        if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] && (!isset($_SERVER['REMOTE_ADDR'])
            || preg_match('/^127\..*/i', trim($_SERVER['REMOTE_ADDR'])) || preg_match('/^172\.16.*/i', trim($_SERVER['REMOTE_ADDR']))
            || preg_match('/^192\.168\.*/i', trim($_SERVER['REMOTE_ADDR'])) || preg_match('/^10\..*/i', trim($_SERVER['REMOTE_ADDR'])))) {
            if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')) {
                $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
                return $ips[0];
            } else {
                return $_SERVER['HTTP_X_FORWARDED_FOR'];
            }
        } else {
            return $_SERVER['REMOTE_ADDR'];
        }
    }

    /**
     * getHttpHost return the <b>current</b> host used, with the protocol (http or https) if $http is true
     * This function should not be used to choose http or https domain name.
     *
     * @param bool $http
     * @param bool $entities
     * @return string host
     */
    public static function getHttpHost($http = false, $entities = false, $ignore_port = false) {
        $httpHost = '';
        if (array_key_exists('HTTP_HOST', $_SERVER)) {
            $httpHost = $_SERVER['HTTP_HOST'];
        }

        $host = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $httpHost);
        if ($ignore_port && $pos = strpos($host, ':')) {
            $host = substr($host, 0, $pos);
        }
        if ($entities) {
            $host = htmlspecialchars($host, ENT_COMPAT, 'UTF-8');
        }
        
        return $host;
    }

     /**
     * Get a value from $_POST / $_GET
     * if unavailable, take a default value.
     *
     * @param string $key Value key
     * @param mixed $default_value (optional)
     *
     * @return mixed Value
     */
    public static function getValue($key, $default_value = false)
    {
        if (!isset($key) || empty($key) || !is_string($key)) {
            return false;
        }

        if (getenv('kernel.environment') === 'test' && self::$request instanceof Request) {
            $value = self::$request->request->get($key, self::$request->query->get($key, $default_value));
        } else {
            $value = (isset($_POST[$key]) ? $_POST[$key] : (isset($_GET[$key]) ? $_GET[$key] : $default_value));
        }

        if (is_string($value)) {
            return stripslashes(urldecode(preg_replace('/((\%5C0+)|(\%00+))/i', '', urlencode($value))));
        }

        return $value;
    }

    /**
     * Get all values from $_POST/$_GET
     * @return mixed
     */
    public static function getAllValues() {
        return $_POST + $_GET;
    }

    public static function getIsset($key) {
        if (!isset($key) || empty($key) || !is_string($key)) {
            return false;
        }
        return isset($_POST[$key]) || isset($_GET[$key]);
    }

    /**
     * Use json_decode instead
     * jsonDecode convert json string to php array / object
     *
     * @param string $data
     * @param bool $assoc if true, convert to associativ array
     *
     * @return array
     */
    public static function jsonDecode($data, $assoc = false, $depth = 512, $options = 0)
    {
        return json_decode($data, $assoc, $depth, $options);
    }

    /**
     * Use json_encode instead
     * Convert an array to json string
     *
     * @param array $data
     *
     * @return string json
     */
    public static function jsonEncode($data, $options = 0, $depth = 512)
    {
        if (PHP_VERSION_ID < 50500) { /* PHP version < 5.5.0 */
            return json_encode($data, $options);
        }

        return json_encode($data, $options, $depth);
    }

    protected static function throwDeprecated($error, $message, $class)
    {
        @trigger_error($error, E_USER_DEPRECATED);
    }


    /**
     * Display a warning message indicating that the parameter is deprecated.
     */
    public static function displayParameterAsDeprecated($parameter)
    {
        $backtrace = debug_backtrace();
        $callee = next($backtrace);
        $error = 'Parameter <b>' . $parameter . '</b> in function <b>' . (isset($callee['function']) ? $callee['function'] : '') . '()</b> is deprecated in <b>' . $callee['file'] . '</b> on line <b>' . (isset($callee['line']) ? $callee['line'] : '(undefined)') . '</b><br />';
        $message = 'The parameter ' . $parameter . ' in function ' . $callee['function'] . ' (Line ' . (isset($callee['line']) ? $callee['line'] : 'undefined') . ') is deprecated and will be removed in the next major version.';
        $class = isset($callee['class']) ? $callee['class'] : null;

        Tools::throwDeprecated($error, $message, $class);
    }

    /**
     * Depending on PROX_DEBUG throws an exception or returns a error message.
     *
     * @param string|null $errorMessage Error message (defaults to "Fatal error")
     * @param bool $htmlentities DEPRECATED since 1.7.4.0
     *
     * @return string
     *
     * @throws ProximException If PROX_DEBUG is enabled
     */
    public static function displayError($errorMessage = null, $htmlentities = null)
    {
        if (null !== $htmlentities) {
            self::displayParameterAsDeprecated('htmlentities');
        }

        if (null === $errorMessage) {
            $errorMessage = 'Fatal error';
        }

        if (PROX_DEBUG) {
            throw new ProximException($errorMessage);
        }

        return $errorMessage;
    }

    /**
     * Get token to prevent CSRF.
     *
     * @param string $token token to encrypt
     */
    public static function getToken($page = true)
    {
        $app = Application::getInstance();

        if ($page === true) {
            return Tools::hash($app->getFingerprint() . $_SERVER['SCRIPT_NAME']);
        } else {
            return Tools::hash($app->getFingerprint());
        }
    }

    /**
     * Redirect URLs already containing HP_BASE_URI.
     *
     * @param string $url Desired URL
     */
    public static function redirectLink($url)
    {
        header('Location: ' . $url);
        exit;
    }
    
    public static function removePlusSign( $string ) {
        return preg_replace('/\+/', '', $string);
    }

    /**
     * Truncate strings.
     *
     * @param string $str
     * @param int $max_length Max length
     * @param string $suffix Suffix optional
     *
     * @return string $str truncated
     */
    /* CAUTION : Use it only on module hookEvents.
    ** For other purposes use the smarty function instead */
    public static function truncate($str, $max_length, $suffix = '...')
    {
        if (Tools::strlen($str) <= $max_length) {
            return $str;
        }
        $str = utf8_decode($str);

        return utf8_encode(substr($str, 0, $max_length - Tools::strlen($suffix)) . $suffix);
    }

    /*Copied from CakePHP String utility file*/
    public static function truncateString($text, $length = 120, $options = array())
    {
        $default = array(
            'ellipsis' => '...', 'exact' => true, 'html' => true,
        );

        $options = array_merge($default, $options);
        extract($options);
        /**
         * @var string
         * @var bool $exact
         * @var bool $html
         */
        if ($html) {
            if (Tools::strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
                return $text;
            }

            $total_length = Tools::strlen(strip_tags($ellipsis));
            $open_tags = array();
            $truncate = '';
            preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);

            foreach ($tags as $tag) {
                if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
                    if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
                        array_unshift($open_tags, $tag[2]);
                    } elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $close_tag)) {
                        $pos = array_search($close_tag[1], $open_tags);
                        if ($pos !== false) {
                            array_splice($open_tags, $pos, 1);
                        }
                    }
                }
                $truncate .= $tag[1];
                $content_length = Tools::strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3]));

                if ($content_length + $total_length > $length) {
                    $left = $length - $total_length;
                    $entities_length = 0;

                    if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) {
                        foreach ($entities[0] as $entity) {
                            if ($entity[1] + 1 - $entities_length <= $left) {
                                --$left;
                                $entities_length += Tools::strlen($entity[0]);
                            } else {
                                break;
                            }
                        }
                    }

                    $truncate .= Tools::substr($tag[3], 0, $left + $entities_length);
                    break;
                } else {
                    $truncate .= $tag[3];
                    $total_length += $content_length;
                }

                if ($total_length >= $length) {
                    break;
                }
            }
        } else {
            if (Tools::strlen($text) <= $length) {
                return $text;
            }

            $truncate = Tools::substr($text, 0, $length - Tools::strlen($ellipsis));
        }

        if (!$exact) {
            $spacepos = Tools::strrpos($truncate, ' ');
            if ($html) {
                $truncate_check = Tools::substr($truncate, 0, $spacepos);
                $last_open_tag = Tools::strrpos($truncate_check, '<');
                $last_close_tag = Tools::strrpos($truncate_check, '>');

                if ($last_open_tag > $last_close_tag) {
                    preg_match_all('/<[\w]+[^>]*>/s', $truncate, $last_tag_matches);
                    $last_tag = array_pop($last_tag_matches[0]);
                    $spacepos = Tools::strrpos($truncate, $last_tag) + Tools::strlen($last_tag);
                }

                $bits = Tools::substr($truncate, $spacepos);
                preg_match_all('/<\/([a-z]+)>/', $bits, $dropped_tags, PREG_SET_ORDER);

                if (!empty($dropped_tags)) {
                    if (!empty($open_tags)) {
                        foreach ($dropped_tags as $closing_tag) {
                            if (!in_array($closing_tag[1], $open_tags)) {
                                array_unshift($open_tags, $closing_tag[1]);
                            }
                        }
                    } else {
                        foreach ($dropped_tags as $closing_tag) {
                            $open_tags[] = $closing_tag[1];
                        }
                    }
                }
            }

            $truncate = Tools::substr($truncate, 0, $spacepos);
        }

        $truncate .= $ellipsis;

        if ($html) {
            foreach ($open_tags as $tag) {
                $truncate .= '</' . $tag . '>';
            }
        }

        return $truncate;
    }

     /**
     * Allows to display string without HTML tags and slashes.
     *
     * @return string
     */
    public static function getStringClean($string)
    {
        return strip_tags(stripslashes($string));
    }

    public static function ipInfo($ip = NULL, $deep_detect = TRUE) {
        $output = array();

        if (filter_var($ip, FILTER_VALIDATE_IP) === FALSE) {
            $ip = self::getRemoteAddr();
            if ($deep_detect) {
                if (filter_var(@$_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP))
                    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
                if (filter_var(@$_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP))
                    $ip = $_SERVER['HTTP_CLIENT_IP'];
            }
        }

        if (filter_var($ip, FILTER_VALIDATE_IP)) {
            $ipdat = @json_decode(file_get_contents("http://www.geoplugin.net/json.gp?ip=" . $ip));
            if (@strlen(trim($ipdat->geoplugin_countryCode)) == 2) {
                $output = [
                    "request"               => @$ipdat->geoplugin_request,
                    "status"                => @$ipdat->geoplugin_status,
                    "city"                  => @$ipdat->geoplugin_city,
                    "region"                => @$ipdat->geoplugin_region,
                    "region_code"           => @$ipdat->geoplugin_regionCode,
                    "region_name"           => @$ipdat->geoplugin_regionName,
                    "area_code"             => @$ipdat->geoplugin_areaCode,   
                    "country_code"          => @$ipdat->geoplugin_countryCode,
                    "country_name"          => @$ipdat->geoplugin_countryName,
                    "continent_code"        => @$ipdat->geoplugin_continentCode,
                    "continent_name"        => @$ipdat->geoplugin_continentName,
                    "latitude"              => @$ipdat->geoplugin_latitude,
                    "longitude"             => @$ipdat->geoplugin_longitude,
                    "location_radius"       => @$ipdat->geoplugin_locationAccuracyRadius,
                    "timezone"              => @$ipdat->geoplugin_timezone,
                    "currency_code"         => @$ipdat->geoplugin_currencyCode,
                    "currency_symbol"       => @$ipdat->geoplugin_currencySymbol,
                    "currency_symbol"       => @$ipdat->geoplugin_currencySymbol_UTF8,
                    "currency_converter"    => @$ipdat->geoplugin_currencyConverter
                ];
            }
        }
        
        return $output;
    }

    /**
     * Convert a shorthand byte value from a PHP configuration directive to an integer value.
     *
     * @param string $value value to convert
     *
     * @return int
     */
    public static function convertBytes($value)
    {
        if (is_numeric($value)) {
            return $value;
        } else {
            $value_length = strlen($value);
            $qty = (int) substr($value, 0, $value_length - 1);
            $unit = Tools::strtolower(substr($value, $value_length - 1));
            switch ($unit) {
                case 'k':
                    $qty *= 1024;
                    break;
                case 'm':
                    $qty *= 1048576;
                    break;
                case 'g':
                    $qty *= 1073741824;
                    break;
            }

            return $qty;
        }
    }

    /**
     * Get max file upload size considering server settings and optional max value.
     *
     * @param int $max_size optional max file size
     *
     * @return int max file size in bytes
     */
    public static function getMaxUploadSize($max_size = 0)
    {
        $values = array(Tools::convertBytes(ini_get('upload_max_filesize')));

        if ($max_size > 0) {
            $values[] = $max_size;
        }

        $post_max_size = Tools::convertBytes(ini_get('post_max_size'));
        if ($post_max_size > 0) {
            $values[] = $post_max_size;
        }

        return min($values);
    }

    /**
     * file_exists() wrapper with cache to speedup performance.
     *
     * @param string $filename File name
     *
     * @return bool Cached result of file_exists($filename)
     */
    public static function file_exists_cache($filename)
    {
        if (!isset(self::$file_exists_cache[$filename])) {
            self::$file_exists_cache[$filename] = file_exists($filename);
        }

        return self::$file_exists_cache[$filename];
    }

    /**
     * file_exists() wrapper with a call to clearstatcache prior.
     *
     * @param string $filename File name
     *
     * @return bool Cached result of file_exists($filename)
     */
    public static function file_exists_no_cache($filename)
    {
        clearstatcache();

        return file_exists($filename);
    }

    /**
     * refresh a local cacert file.
     */
    public static function refreshCACertFile()
    {
        if ((time() - @filemtime(_PS_CACHE_CA_CERT_FILE_) > 1296000)) {
            $stream_context = @stream_context_create(
                array(
                    'http' => array('timeout' => 3),
                    'ssl' => array(
                        'cafile' => CaBundle::getBundledCaBundlePath(),
                    ),
                )
            );

            $ca_cert_content = @file_get_contents(Tools::CACERT_LOCATION, false, $stream_context);
            if (empty($ca_cert_content)) {
                $ca_cert_content = @file_get_contents(CaBundle::getBundledCaBundlePath());
            }

            if (
                preg_match('/(.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----){50}$/Uims', $ca_cert_content) &&
                substr(rtrim($ca_cert_content), -1) == '-'
            ) {
                file_put_contents(_PS_CACHE_CA_CERT_FILE_, $ca_cert_content);
            }
        }
    }

    private static function file_get_contents_curl(
        $url,
        $curl_timeout,
        $opts
    ) {
        $content = false;

        if (function_exists('curl_init')) {
            Tools::refreshCACertFile();
            $curl = curl_init();

            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($curl, CURLOPT_URL, $url);
            curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
            curl_setopt($curl, CURLOPT_TIMEOUT, $curl_timeout);
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
            curl_setopt($curl, CURLOPT_CAINFO, _PS_CACHE_CA_CERT_FILE_);
            curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($curl, CURLOPT_MAXREDIRS, 5);

            if ($opts != null) {
                if (isset($opts['http']['method']) && Tools::strtolower($opts['http']['method']) == 'post') {
                    curl_setopt($curl, CURLOPT_POST, true);
                    if (isset($opts['http']['content'])) {
                        parse_str($opts['http']['content'], $post_data);
                        curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
                    }
                }
            }
            $content = curl_exec($curl);
            curl_close($curl);
        }

        return $content;
    }

    private static function file_get_contents_fopen(
        $url,
        $use_include_path,
        $stream_context
    ) {
        $content = false;

        if (in_array(ini_get('allow_url_fopen'), array('On', 'on', '1'))) {
            $content = @file_get_contents($url, $use_include_path, $stream_context);
        }

        return $content;
    }

    /**
     * This method allows to get the content from either a URL or a local file.
     *
     * @param string $url the url to get the content from
     * @param bool $use_include_path second parameter of http://php.net/manual/en/function.file-get-contents.php
     * @param resource $stream_context third parameter of http://php.net/manual/en/function.file-get-contents.php
     * @param int $curl_timeout
     * @param bool $fallback whether or not to use the fallback if the main solution fails
     *
     * @return bool|string false or the string content
     */
    public static function file_get_contents(
        $url,
        $use_include_path = false,
        $stream_context = null,
        $curl_timeout = 5,
        $fallback = false
    ) {
        $is_local_file = !preg_match('/^https?:\/\//', $url);
        $require_fopen = false;
        $opts = null;

        if ($stream_context) {
            $opts = stream_context_get_options($stream_context);
            if (isset($opts['http'])) {
                $require_fopen = true;
                $opts_layer = array_diff_key($opts, array('http' => null));
                $http_layer = array_diff_key($opts['http'], array('method' => null, 'content' => null));
                if (empty($opts_layer) && empty($http_layer)) {
                    $require_fopen = false;
                }
            }
        } elseif (!$is_local_file) {
            $stream_context = @stream_context_create(
                array(
                    'http' => array('timeout' => $curl_timeout),
                    'ssl' => array(
                        'verify_peer' => true,
                        'cafile' => CaBundle::getBundledCaBundlePath(),
                    ),
                )
            );
        }

        if ($is_local_file) {
            $content = @file_get_contents($url, $use_include_path, $stream_context);
        } else {
            if ($require_fopen) {
                $content = Tools::file_get_contents_fopen($url, $use_include_path, $stream_context);
            } else {
                $content = Tools::file_get_contents_curl($url, $curl_timeout, $opts);
                if (empty($content) && $fallback) {
                    $content = Tools::file_get_contents_fopen($url, $use_include_path, $stream_context);
                }
            }
        }

        return $content;
    }

    /**
     * Create a local file from url
     * required because ZipArchive is unable to extract from remote files.
     *
     * @param string $url the remote location
     *
     * @return bool|string false if failure, else the local filename
     */
    public static function createFileFromUrl($url)
    {
        $remoteFile = fopen($url, 'r');
        if (!$remoteFile) {
            return false;
        }
        $localFile = fopen(basename($url), 'w');
        if (!$localFile) {
            return false;
        }

        while (!feof($remoteFile)) {
            $data = fread($remoteFile, 1024);
            fwrite($localFile, $data, 1024);
        }

        fclose($remoteFile);
        fclose($localFile);

        return basename($url);
    }

    public static function simplexml_load_file($url, $class_name = null)
    {
        $cache_id = 'Tools::simplexml_load_file' . $url;
        if (!Cache::isStored($cache_id)) {
            $result = @simplexml_load_string(Tools::file_get_contents($url), $class_name);
            Cache::store($cache_id, $result);

            return $result;
        }

        return Cache::retrieve($cache_id);
    }

    public static function copy($source, $destination, $stream_context = null)
    {
        if (is_null($stream_context) && !preg_match('/^https?:\/\//', $source)) {
            return @copy($source, $destination);
        }

        return @file_put_contents($destination, Tools::file_get_contents($source, false, $stream_context));
    }

    /**
     * Sanitize a string.
     *
     * @param string $string String to sanitize
     * @param bool $full String contains HTML or not (optional)
     *
     * @return string Sanitized string
     */
    public static function safeOutput($string, $html = false)
    {
        if (!$html) {
            $string = strip_tags($string);
        }

        return @Tools::htmlentitiesUTF8($string, ENT_QUOTES);
    }

    public static function htmlentitiesUTF8($string, $type = ENT_QUOTES)
    {
        if (is_array($string)) {
            return array_map(array('Tools', 'htmlentitiesUTF8'), $string);
        }

        return htmlentities((string) $string, $type, 'utf-8');
    }

    public static function htmlentitiesDecodeUTF8($string)
    {
        if (is_array($string)) {
            $string = array_map(array('Proxim\Tools', 'htmlentitiesDecodeUTF8'), $string);

            return (string) array_shift($string);
        }

        return html_entity_decode((string) $string, ENT_QUOTES, 'utf-8');
    }

    /**
     * Display error and dies or silently log the error.
     *
     * @param string $msg
     * @param bool $die
     *
     * @return bool success of logging
     */
    public static function dieOrLog($msg, $die = true)
    {
        if ($die || (defined('PROX_DEBUG') && PROX_DEBUG)) {
            die($msg);
        }

        // return ProximLogger::addLog($msg);
    } 

    public static function getExchangeRateData( $currency_codes = array() ) {
        $api_key = Configuration::get('CURRENCY_LAYER_API_KEY');
        if (!$api_key) {
            return null;
        }

        if (empty($currency_codes)) {
            return array();
        }

        $currency_codes = implode(',', $currency_codes);

        $url = "http://apilayer.net/api/live?access_key=".$api_key."&currencies=".$currency_codes."&source=USD&format=1";
        $ch = curl_init();
        curl_setopt ($ch, CURLOPT_URL, $url);
        curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch,  CURLOPT_USERAGENT , "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)");
        curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 0);

        $data = curl_exec($ch);
        curl_close($ch);

        return self::jsonDecode($data, true);
    }

    /**
     * Replace all accented chars by their equivalent non accented chars.
     *
     * @param string $str
     *
     * @return string
     */
    public static function replaceAccentedChars($str)
    {
        /* One source among others:
            http://www.tachyonsoft.com/uc0000.htm
            http://www.tachyonsoft.com/uc0001.htm
            http://www.tachyonsoft.com/uc0004.htm
        */
        $patterns = array(
            /* Lowercase */
            /* a  */ '/[\x{00E0}\x{00E1}\x{00E2}\x{00E3}\x{00E4}\x{00E5}\x{0101}\x{0103}\x{0105}\x{0430}\x{00C0}-\x{00C3}\x{1EA0}-\x{1EB7}]/u',
            /* b  */ '/[\x{0431}]/u',
            /* c  */ '/[\x{00E7}\x{0107}\x{0109}\x{010D}\x{0446}]/u',
            /* d  */ '/[\x{010F}\x{0111}\x{0434}\x{0110}\x{00F0}]/u',
            /* e  */ '/[\x{00E8}\x{00E9}\x{00EA}\x{00EB}\x{0113}\x{0115}\x{0117}\x{0119}\x{011B}\x{0435}\x{044D}\x{00C8}-\x{00CA}\x{1EB8}-\x{1EC7}]/u',
            /* f  */ '/[\x{0444}]/u',
            /* g  */ '/[\x{011F}\x{0121}\x{0123}\x{0433}\x{0491}]/u',
            /* h  */ '/[\x{0125}\x{0127}]/u',
            /* i  */ '/[\x{00EC}\x{00ED}\x{00EE}\x{00EF}\x{0129}\x{012B}\x{012D}\x{012F}\x{0131}\x{0438}\x{0456}\x{00CC}\x{00CD}\x{1EC8}-\x{1ECB}\x{0128}]/u',
            /* j  */ '/[\x{0135}\x{0439}]/u',
            /* k  */ '/[\x{0137}\x{0138}\x{043A}]/u',
            /* l  */ '/[\x{013A}\x{013C}\x{013E}\x{0140}\x{0142}\x{043B}]/u',
            /* m  */ '/[\x{043C}]/u',
            /* n  */ '/[\x{00F1}\x{0144}\x{0146}\x{0148}\x{0149}\x{014B}\x{043D}]/u',
            /* o  */ '/[\x{00F2}\x{00F3}\x{00F4}\x{00F5}\x{00F6}\x{00F8}\x{014D}\x{014F}\x{0151}\x{043E}\x{00D2}-\x{00D5}\x{01A0}\x{01A1}\x{1ECC}-\x{1EE3}]/u',
            /* p  */ '/[\x{043F}]/u',
            /* r  */ '/[\x{0155}\x{0157}\x{0159}\x{0440}]/u',
            /* s  */ '/[\x{015B}\x{015D}\x{015F}\x{0161}\x{0441}]/u',
            /* ss */ '/[\x{00DF}]/u',
            /* t  */ '/[\x{0163}\x{0165}\x{0167}\x{0442}]/u',
            /* u  */ '/[\x{00F9}\x{00FA}\x{00FB}\x{00FC}\x{0169}\x{016B}\x{016D}\x{016F}\x{0171}\x{0173}\x{0443}\x{00D9}-\x{00DA}\x{0168}\x{01AF}\x{01B0}\x{1EE4}-\x{1EF1}]/u',
            /* v  */ '/[\x{0432}]/u',
            /* w  */ '/[\x{0175}]/u',
            /* y  */ '/[\x{00FF}\x{0177}\x{00FD}\x{044B}\x{1EF2}-\x{1EF9}\x{00DD}]/u',
            /* z  */ '/[\x{017A}\x{017C}\x{017E}\x{0437}]/u',
            /* ae */ '/[\x{00E6}]/u',
            /* ch */ '/[\x{0447}]/u',
            /* kh */ '/[\x{0445}]/u',
            /* oe */ '/[\x{0153}]/u',
            /* sh */ '/[\x{0448}]/u',
            /* shh*/ '/[\x{0449}]/u',
            /* ya */ '/[\x{044F}]/u',
            /* ye */ '/[\x{0454}]/u',
            /* yi */ '/[\x{0457}]/u',
            /* yo */ '/[\x{0451}]/u',
            /* yu */ '/[\x{044E}]/u',
            /* zh */ '/[\x{0436}]/u',
            /* Uppercase */
            /* A  */ '/[\x{0100}\x{0102}\x{0104}\x{00C0}\x{00C1}\x{00C2}\x{00C3}\x{00C4}\x{00C5}\x{0410}]/u',
            /* B  */ '/[\x{0411}]/u',
            /* C  */ '/[\x{00C7}\x{0106}\x{0108}\x{010A}\x{010C}\x{0426}]/u',
            /* D  */ '/[\x{010E}\x{0110}\x{0414}\x{00D0}]/u',
            /* E  */ '/[\x{00C8}\x{00C9}\x{00CA}\x{00CB}\x{0112}\x{0114}\x{0116}\x{0118}\x{011A}\x{0415}\x{042D}]/u',
            /* F  */ '/[\x{0424}]/u',
            /* G  */ '/[\x{011C}\x{011E}\x{0120}\x{0122}\x{0413}\x{0490}]/u',
            /* H  */ '/[\x{0124}\x{0126}]/u',
            /* I  */ '/[\x{0128}\x{012A}\x{012C}\x{012E}\x{0130}\x{0418}\x{0406}]/u',
            /* J  */ '/[\x{0134}\x{0419}]/u',
            /* K  */ '/[\x{0136}\x{041A}]/u',
            /* L  */ '/[\x{0139}\x{013B}\x{013D}\x{0139}\x{0141}\x{041B}]/u',
            /* M  */ '/[\x{041C}]/u',
            /* N  */ '/[\x{00D1}\x{0143}\x{0145}\x{0147}\x{014A}\x{041D}]/u',
            /* O  */ '/[\x{00D3}\x{014C}\x{014E}\x{0150}\x{041E}]/u',
            /* P  */ '/[\x{041F}]/u',
            /* R  */ '/[\x{0154}\x{0156}\x{0158}\x{0420}]/u',
            /* S  */ '/[\x{015A}\x{015C}\x{015E}\x{0160}\x{0421}]/u',
            /* T  */ '/[\x{0162}\x{0164}\x{0166}\x{0422}]/u',
            /* U  */ '/[\x{00D9}\x{00DA}\x{00DB}\x{00DC}\x{0168}\x{016A}\x{016C}\x{016E}\x{0170}\x{0172}\x{0423}]/u',
            /* V  */ '/[\x{0412}]/u',
            /* W  */ '/[\x{0174}]/u',
            /* Y  */ '/[\x{0176}\x{042B}]/u',
            /* Z  */ '/[\x{0179}\x{017B}\x{017D}\x{0417}]/u',
            /* AE */ '/[\x{00C6}]/u',
            /* CH */ '/[\x{0427}]/u',
            /* KH */ '/[\x{0425}]/u',
            /* OE */ '/[\x{0152}]/u',
            /* SH */ '/[\x{0428}]/u',
            /* SHH*/ '/[\x{0429}]/u',
            /* YA */ '/[\x{042F}]/u',
            /* YE */ '/[\x{0404}]/u',
            /* YI */ '/[\x{0407}]/u',
            /* YO */ '/[\x{0401}]/u',
            /* YU */ '/[\x{042E}]/u',
            /* ZH */ '/[\x{0416}]/u',
        );
        // ö to oe
        // å to aa
        // ä to ae
        $replacements = array(
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 'ss', 't', 'u', 'v', 'w', 'y', 'z', 'ae', 'ch', 'kh', 'oe', 'sh', 'shh', 'ya', 'ye', 'yi', 'yo', 'yu', 'zh',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'Y', 'Z', 'AE', 'CH', 'KH', 'OE', 'SH', 'SHH', 'YA', 'YE', 'YI', 'YO', 'YU', 'ZH',
        );
        return preg_replace($patterns, $replacements, $str);
    }
    
    public static function unSerialize($serialized, $object = false)
    {
        if (is_string($serialized) && (strpos($serialized, 'O:') === false || !preg_match('/(^|;|{|})O:[0-9]+:"/', $serialized)) && !$object || $object) {
            return @unserialize($serialized);
        }

        return false;
    }

    public static function purifyHTML($html, $uri_unescape = null, $allow_style = false)
    {
        $config = \HTMLPurifier_Config::createDefault();

        $config->set('Attr.EnableID', true);
        $config->set('Attr.AllowedRel', array('nofollow'));
        $config->set('HTML.Trusted', true);
        if(!is_dir(PROX_DIR_CACHE . 'purifier')) {
            mkdir(PROX_DIR_CACHE . 'purifier');
        }
        $config->set('Cache.SerializerPath', PROX_DIR_CACHE . 'purifier');
        $config->set('Attr.AllowedFrameTargets', array('_blank', '_self', '_parent', '_top'));
        if (is_array($uri_unescape)) {
            $config->set('URI.UnescapeCharacters', implode('', $uri_unescape));
        }

        $config->set('HTML.SafeIframe', true);
        $config->set('HTML.SafeObject', true);
        $config->set('URI.SafeIframeRegexp', '/.*/');

        /** @var HTMLPurifier_HTMLDefinition|HTMLPurifier_HTMLModule $def */
        // http://developers.whatwg.org/the-video-element.html#the-video-element
        if ($def = $config->getHTMLDefinition(true)) {
            $def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array(
                'src' => 'URI',
                'type' => 'Text',
                'width' => 'Length',
                'height' => 'Length',
                'poster' => 'URI',
                'preload' => 'Enum#auto,metadata,none',
                'controls' => 'Bool',
            ));
            $def->addElement('source', 'Block', 'Flow', 'Common', array(
                'src' => 'URI',
                'type' => 'Text',
            ));
            if ($allow_style) {
                $def->addElement('style', 'Block', 'Flow', 'Common', array('type' => 'Text'));
            }
        }

        $purifier = new \HTMLPurifier($config);
        
        if (PROX_MAGIC_QUOTES_GPC) {
            $html = stripslashes($html);
        }

        $html = $purifier->purify($html);

        if (PROX_MAGIC_QUOTES_GPC) {
            $html = addslashes($html);
        }

    
        return $html;
    }

    /**
     * getMemoryLimit allow to get the memory limit in octet.
     *
     * @since 1.4.5.0
     *
     * @return int the memory limit value in octet
     */
    public static function getMemoryLimit()
    {
        $memory_limit = @ini_get('memory_limit');

        return Tools::getOctets($memory_limit);
    }

    /**
     * getOctet allow to gets the value of a configuration option in octet.
     *
     * @since 1.5.0
     *
     * @return int the value of a configuration option in octet
     */
    public static function getOctets($option)
    {
        if (preg_match('/[0-9]+k/i', $option)) {
            return 1024 * (int) $option;
        }

        if (preg_match('/[0-9]+m/i', $option)) {
            return 1024 * 1024 * (int) $option;
        }

        if (preg_match('/[0-9]+g/i', $option)) {
            return 1024 * 1024 * 1024 * (int) $option;
        }

        return $option;
    }

    /**
     * @return bool true if the server use 64bit arch
     */
    public static function isX86_64arch()
    {
        return PHP_INT_MAX == '9223372036854775807';
    }

    /**
     * @desc extract a zip file to the given directory
     *
     * @return bool success
     */
    public static function ZipExtract($from_file, $to_dir)
    {
        if (!file_exists($to_dir)) {
            mkdir($to_dir, FileSystem::DEFAULT_MODE_FOLDER);
        }

        $zip = new \ZipArchive();
        if ($zip->open($from_file) === true && $zip->extractTo($to_dir) && $zip->close()) {
            return true;
        }

        return false;
    }
}