<?php

###############################################################################
# Util.php
#
# Util.php is a library of helper functions for a number of common tasks.
# Many functions are from
#
# @author Brandon Wamboldt <brandon.wamboldt@gmail.com>
# @link   http://github.com/brandonwamboldt/utilphp/
#
# @author Anil Kumar <akumar@codepunch.com>
# @link   https://codepunch.com
#
############################################################################### 

namespace CodePunch\Base;
//require_once('idna_convert.class.php');

###############################################################################

class Util {
	
	###########################################################################
	# Start : # @author Brandon Wamboldt <brandon.wamboldt@gmail.com>
	###########################################################################

	public static function str_to_bool($string, $default = false)
	{
		$yes_words = 'affirmative|all right|aye|indubitably|most assuredly|ok|of course|okay|sure thing|y|yes+|yea|yep|sure|yeah|true|t|on|1|oui|vrai';
		$no_words = 'no*|no way|nope|nah|na|never|absolutely not|by no means|negative|never ever|false|f|off|0|non|faux';

		if (preg_match('/^(' . $yes_words . ')$/i', $string)) {
			return true;
		} elseif (preg_match('/^(' . $no_words . ')$/i', $string)) {
			return false;
		}
		return $default;
	}

	###########################################################################
		
	public static function starts_with($string, $starts_with)
	{
		//return strpos($string, $starts_with) === 0;
		return strncmp($string, $starts_with, strlen($starts_with)) === 0;
	}

	###########################################################################

	public static function ends_with($string, $ends_with)
	{
		return substr($string, -strlen($ends_with)) === $ends_with;
	}

	###########################################################################

	public static function str_contains($haystack, $needle)
	{
		return strpos($haystack, $needle) !== false;
	}
	
	###########################################################################
	
	public static function strip_space($string)
    {
        return preg_replace('/\s+/', '', $string);
    }
	
	###########################################################################
	
	public static function zero_pad($number, $length)
    {
        return str_pad($number, $length, '0', STR_PAD_LEFT);
    }

	###########################################################################

	public static function get_file_ext($filename)
	{
		return pathinfo($filename, PATHINFO_EXTENSION);
	}
	
	###########################################################################
    # Truncate a string to a specified length without cutting a word off.
    #
    # @param   string  $string  The string to truncate
    # @param   integer $length  The length to truncate the string to
    # @param   string  $append  Text to append to the string IF it gets
    #                           truncated, defaults to '...'
    # @return  string
    public static function safe_truncate($string, $length, $append = '...')
    {
        $ret        = substr($string, 0, $length);
        $last_space = strrpos($ret, ' ');

        if ($last_space !== false && $string != $ret) {
            $ret     = substr($ret, 0, $last_space);
        }

        if ($ret != $string) {
            $ret .= $append;
        }

        return $ret;
    }
	
	###########################################################################
    # Truncate the string to given length of charactes.
    #
    # @param $string
    # @param $limit
    # @param string $append
    # @return string
    public static function limit_characters($string, $limit = 100, $append = '...')
    {
        if (mb_strlen($string) <= $limit) {
            return $string;
        }

        return rtrim(mb_substr($string, 0, $limit, 'UTF-8')) . $append;
    }

    ###########################################################################
    # Truncate the string to given length of words.
    #
    # @param $string
    # @param $limit
    # @param string $append
    # @return string
    public static function limit_words($string, $limit = 100, $append = '...')
    {
        preg_match('/^\s*+(?:\S++\s*+){1,' . $limit . '}/u', $string, $matches);

        if (!isset($matches[0]) || strlen($string) === strlen($matches[0])) {
            return $string;
        }

        return rtrim($matches[0]).$append;
    }
	
	###########################################################################
	
	public static function type_exists($type, $autoload = false)
    {
        return class_exists($type, $autoload)
            || interface_exists($type, $autoload)
            || trait_exists($type, $autoload);
    }
	
	###########################################################################
	
	public static function is_https()
    {
		global $cfg_http_protocol;
		if(isset($cfg_http_protocol) && $cfg_http_protocol != "detect")
			return !strcasecmp($cfg_http_protocol, "https") ? true : false;
        return isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off';
    }
	
	###########################################################################
    # Returns the IP address of the client.
    #
    # @param   boolean $trust_proxy_headers Whether or not to trust the
    #                                       proxy headers HTTP_CLIENT_IP
    #                                       and HTTP_X_FORWARDED_FOR. ONLY
    #                                       use if your server is behind a
    #                                       proxy that sets these values
    # @return  string
    public static function get_client_ip($trust_proxy_headers = false)
    {
        if (!$trust_proxy_headers) {
            return $_SERVER['REMOTE_ADDR'];
        }

        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } else {
            $ip = $_SERVER['REMOTE_ADDR'];
        }

        return $ip;
    }
	
	###########################################################################
    # Turns all of the links in a string into HTML links.
    #
    # Part of the LinkifyURL Project <https://github.com/jmrware/LinkifyURL>
    #
    # @param  string $text The string to parse
    # @return string
    public static function linkify($text)
    {
        $text = preg_replace('/&apos;/', '&#39;', $text); // IE does not handle &apos; entity!
        $section_html_pattern = '%# Rev:20100913_0900 github.com/jmrware/LinkifyURL
            # Section text into HTML <A> tags  and everything else.
              (                             # $1: Everything not HTML <A> tag.
                [^<]+(?:(?!<a\b)<[^<]*)*     # non A tag stuff starting with non-"<".
              |      (?:(?!<a\b)<[^<]*)+     # non A tag stuff starting with "<".
             )                              # End $1.
            | (                             # $2: HTML <A...>...</A> tag.
                <a\b[^>]*>                   # <A...> opening tag.
                [^<]*(?:(?!</a\b)<[^<]*)*    # A tag contents.
                </a\s*>                      # </A> closing tag.
             )                              # End $2:
            %ix';

        return preg_replace_callback($section_html_pattern, array(__CLASS__, 'linkifyCallback'), $text);
    }

    ###########################################################################
    # Callback for the preg_replace in the linkify() method.
    #
    # Part of the LinkifyURL Project <https://github.com/jmrware/LinkifyURL>
    #
    # @param  array  $matches Matches from the preg_ function
    # @return string
    protected static function linkifyRegex($text)
    {
        $url_pattern = '/# Rev:20100913_0900 github.com\/jmrware\/LinkifyURL
            # Match http & ftp URL that is not already linkified.
            # Alternative 1: URL delimited by (parentheses).
            (\() # $1 "(" start delimiter.
            ((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]+) # $2: URL.
            (\)) # $3: ")" end delimiter.
            | # Alternative 2: URL delimited by [square brackets].
            (\[) # $4: "[" start delimiter.
            ((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]+) # $5: URL.
            (\]) # $6: "]" end delimiter.
            | # Alternative 3: URL delimited by {curly braces}.
            (\{) # $7: "{" start delimiter.
            ((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]+) # $8: URL.
            (\}) # $9: "}" end delimiter.
            | # Alternative 4: URL delimited by <angle brackets>.
            (<|&(?:lt|\#60|\#x3c);) # $10: "<" start delimiter (or HTML entity).
            ((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]+) # $11: URL.
            (>|&(?:gt|\#62|\#x3e);) # $12: ">" end delimiter (or HTML entity).
            | # Alternative 5: URL not delimited by (), [], {} or <>.
            (# $13: Prefix proving URL not already linked.
            (?: ^ # Can be a beginning of line or string, or
            | [^=\s\'"\]] # a non-"=", non-quote, non-"]", followed by
           ) \s*[\'"]? # optional whitespace and optional quote;
            | [^=\s]\s+ # or... a non-equals sign followed by whitespace.
           ) # End $13. Non-prelinkified-proof prefix.
            (\b # $14: Other non-delimited URL.
            (?:ht|f)tps?:\/\/ # Required literal http, https, ftp or ftps prefix.
            [a-z0-9\-._~!$\'()*+,;=:\/?#[\]@%]+ # All URI chars except "&" (normal*).
            (?: # Either on a "&" or at the end of URI.
            (?! # Allow a "&" char only if not start of an...
            &(?:gt|\#0*62|\#x0*3e); # HTML ">" entity, or
            | &(?:amp|apos|quot|\#0*3[49]|\#x0*2[27]); # a [&\'"] entity if
            [.!&\',:?;]? # followed by optional punctuation then
            (?:[^a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]|$) # a non-URI char or EOS.
           ) & # If neg-assertion true, match "&" (special).
            [a-z0-9\-._~!$\'()*+,;=:\/?#[\]@%]* # More non-& URI chars (normal*).
           )* # Unroll-the-loop (special normal*)*.
            [a-z0-9\-_~$()*+=\/#[\]@%] # Last char can\'t be [.!&\',;:?]
           ) # End $14. Other non-delimited URL.
            /imx';

        $url_replace = '$1$4$7$10$13<a href="$2$5$8$11$14">$2$5$8$11$14</a>$3$6$9$12';

        return preg_replace($url_pattern, $url_replace, $text);
    }

    ###########################################################################
    # Callback for the preg_replace in the linkify() method.
    #
    # Part of the LinkifyURL Project <https://github.com/jmrware/LinkifyURL>
    #
    # @param  array  $matches Matches from the preg_ function
    # @return string
    protected static function linkifyCallback($matches)
    {
        if (isset($matches[2])) {
            return $matches[2];
        }

        return self::linkifyRegex($matches[1]);
    }
	
	###########################################################################
    # Generates a string of random characters.
    #
    # @throws  LengthException  If $length is bigger than the available
    #                           character pool and $no_duplicate_chars is
    #                           enabled
    #
    # @param   integer $length             The length of the string to
    #                                      generate
    # @param   boolean $human_friendly     Whether or not to make the
    #                                      string human friendly by
    #                                      removing characters that can be
    #                                      confused with other characters (
    #                                      O and 0, l and 1, etc)
    # @param   boolean $include_symbols    Whether or not to include
    #                                      symbols in the string. Can not
    #                                      be enabled if $human_friendly is
    #                                      true
    # @param   boolean $no_duplicate_chars Whether or not to only use
    #                                      characters once in the string.
    # @return  string
    public static function random_string($length = 16, $human_friendly = true, $include_symbols = false, $no_duplicate_chars = false)
    {
        $nice_chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefhjkmnprstuvwxyz23456789';
        $all_an     = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
        $symbols    = '!@#$%^&*()~_-=+{}[]|:;<>,.?/"\'\\`';
        $string     = '';

        // Determine the pool of available characters based on the given parameters
        if ($human_friendly) {
            $pool = $nice_chars;
        } else {
            $pool = $all_an;

            if ($include_symbols) {
                $pool .= $symbols;
            }
        }

        if (!$no_duplicate_chars) {
            return substr(str_shuffle(str_repeat($pool, $length)), 0, $length);
        }

        // Don't allow duplicate letters to be disabled if the length is
        // longer than the available characters
        if ($no_duplicate_chars && strlen($pool) < $length) {
            throw new \LengthException('$length exceeds the size of the pool and $no_duplicate_chars is enabled');
        }

        // Convert the pool of characters into an array of characters and
        // shuffle the array
        $pool       = str_split($pool);
        $poolLength = count($pool);
        $rand       = mt_rand(0, $poolLength - 1);

        // Generate our string
        for ($i = 0; $i < $length; $i++) {
            $string .= $pool[$rand];

            // Remove the character from the array to avoid duplicates
            array_splice($pool, $rand, 1);

            // Generate a new number
            if (($poolLength - 2 - $i) > 0) {
                $rand = mt_rand(0, $poolLength - 2 - $i);
            } else {
                $rand = 0;
            }
        }

        return $string;
    }

    ###########################################################################
    # Generate secure random string of given length
    # If 'openssl_random_pseudo_bytes' is not available
    # then generate random string using default function
    #
    # Part of the Laravel Project <https://github.com/laravel/laravel>
    #
    # @param int $length length of string
    # @return bool
    public static function secure_random_string($length = 16)
    {
        if (function_exists('openssl_random_pseudo_bytes')) {
            $bytes = openssl_random_pseudo_bytes($length * 2);

            if ($bytes === false) {
                throw new \LengthException('$length is not accurate, unable to generate random string');
            }

            return substr(str_replace(array('/', '+', '='), '', base64_encode($bytes)), 0, $length);
        }

        // @codeCoverageIgnoreStart
        return static::random_string($length);
        // @codeCoverageIgnoreEnd
    }
	
	###########################################################################
	
	public static function randomText($maxlen=16) 
	{
		$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$pass = array(); 
		$alphaLength = strlen($alphabet) - 1;
		for ($i = 0; $i < $maxlen; $i++) {
			$n = rand(0, $alphaLength);
			$pass[] = $alphabet[$n];
		}
		return implode("",$pass); 
	}
	
	###########################################################################
    # Transmit UTF-8 content headers if the headers haven't already been sent.
    #
    # @param  string  $content_type The content type to send out
    # @return boolean
    public static function utf8_headers($content_type = 'text/html')
    {
        // @codeCoverageIgnoreStart
        if (!headers_sent()) {
            header('Content-type: ' . $content_type . '; charset=utf-8');

            return true;
        }

        return false;
        // @codeCoverageIgnoreEnd
    }

    ###########################################################################
    # Transmit headers that force a browser to display the download file
    # dialog. Cross browser compatible. Only fires if headers have not
    # already been sent.
    #
    # @param string $filename The name of the filename to display to
    #                         browsers
    # @param string $content  The content to output for the download.
    #                         If you don't specify this, just the
    #                         headers will be sent
    # @return boolean
    public static function force_download($filename, $content = false)
    {
        // @codeCoverageIgnoreStart
        if (!headers_sent()) {
            // Required for some browsers
            if (ini_get('zlib.output_compression')) {
                @ini_set('zlib.output_compression', 'Off');
            }

            header('Pragma: public');
            header('Expires: 0');
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');

            // Required for certain browsers
            header('Cache-Control: private', false);

            header('Content-Disposition: attachment; filename="' . basename(str_replace('"', '', $filename)) . '";');
            header('Content-Type: application/force-download');
            header('Content-Transfer-Encoding: binary');

            if ($content) {
                header('Content-Length: ' . strlen($content));
            }

            ob_clean();
            flush();

            if ($content) {
                echo $content;
            }

            return true;
        }

        return false;
        // @codeCoverageIgnoreEnd
    }

    ###########################################################################
    # Sets the headers to prevent caching for the different browsers.
    #
    # Different browsers support different nocache headers, so several
    # headers must be sent so that all of them get the point that no
    # caching should occur
    #
    # @return boolean
    public static function nocache_headers()
    {
        // @codeCoverageIgnoreStart
        if (!headers_sent()) {
            header('Expires: Wed, 11 Jan 1984 05:00:00 GMT');
            header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
            header('Cache-Control: no-cache, must-revalidate, max-age=0');
            header('Pragma: no-cache');

            return true;
        }

        return false;
        // @codeCoverageIgnoreEnd
    }

	###########################################################################
	# End : # @author Brandon Wamboldt <brandon.wamboldt@gmail.com>
	###########################################################################

	public static function is_cli()
	{
		if( defined('STDIN')) {
			return true;
		}
		if ( php_sapi_name() === 'cli' ) {
			return true;
		}
		if ( empty($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT']) && isset($_SERVER['argv']) && count($_SERVER['argv']) > 0)  {
			return true;
		} 
		return false;
	}
	
	###########################################################################
	# Returns true if ?debug= is set to the specified key
	# If the key is not specified, debug may be set to any key.
	
	public static function is_in_debug_mode($key=false)
	{
		if($key !== false && $key != "") {
			$debugvalue = self::get_sanitized_request_string('debug', '');
			return ($debugvalue == $key) ? true : false;
		}
		else
			return self::is_request_key_set('debug');
	}
	
	###########################################################################
	
	public static function debug_print($data, $key=false)
	{
		if(self::is_in_debug_mode($key))
			self::print($data);
	}
	
	###########################################################################
	
	public static function debug_cli_print($data)
	{
		if(self::is_cli() && self::is_request_key_set("debug"))
			self::print($data);
	}
	
	###########################################################################
	
	public static function cli_print($data)
	{
		if(self::is_cli())
			self::print($data);
	}

	###########################################################################

	public static function in_array_casei($needle, $haystack) 
	{
		return in_array(strtolower($needle), array_map('strtolower', $haystack));
	}
	
	###########################################################################

	public static function bool_to_int($cv) 
	{
		return is_bool($cv) ? ($cv === true ? 1 : 0) : $cv;
	}
	
	###########################################################################

	public static function array_bool_to_int($array) 
	{
		if(is_array($array)) {
			foreach($array as $ck => &$cv) {
				$cv = self::bool_to_int($cv);
			}
		}
		return $array;
	}
	
	###########################################################################
	
	public static function implode_limited($glue, $array, $max=40, $append="...")
	{
		$count = count($array);
		$str =  implode($glue, array_slice($array, 0, $max));
		if($count > $max)
			$str .= $append;
		return $str;
	}
	
	###########################################################################
	
	public static function array_flatten($array, $old_key=null, $divider="") 
	{
		$return = array();
		foreach ($array as $key => $value) 
		{
			if($old_key)
				$key = "{$old_key}{$divider}{$key}";
			if(is_array($value)) 
				$return = array_merge($return, self::array_flatten($value, $key, $divider));
			else 
				$return[$key] = $value;
		}
		return $return;
	}
	
	###########################################################################
	
	public static function array_xlate_keys(&$array, $xlate_table)
	{
		foreach($array as $key=>$value)
		{
			foreach($xlate_table as $xk=>$xv)
			{
				if($xk == $key)
				{
					$array[$xv] = $value;
					unset($array[$key]);
					break;
				}
			}
		}
		return $array;
	}

	###########################################################################

	public static function get_string_between($string, $start, $end, $includetokens=false)
	{
		$string = " ". $string;
		$startpos = mb_stripos($string, $start);
		if($startpos !== false) {
			if(!$includetokens)
				$startpos += mb_strlen($start);
			$endpos = mb_stripos($string, $end, $startpos);
			if($endpos !== false) {
				$len = $endpos - $startpos;
				if($includetokens)
					$len += mb_strlen($end);
				return mb_substr($string, $startpos, $len);
			}
		}
		return "";
	}

	###########################################################################
	// Abbreviate a set of words using the first $lc letters from each word.

	public static function abbreviate($string, $lc = 1, $convert_underscore=true)
	{
		if($convert_underscore) // treat _ as spaces.
			$string = str_replace("_", " ", $string);
		$abbreviation = "";
		$words = explode(" ", "$string");
		  foreach($words as $word) {
			  $abbreviation .= strtoupper(substr($word, 0, $lc));
		  }
		return $abbreviation;
	}


	###########################################################################

	public static function get_date_difference($pdate, $pivot=null)
	{
		if($pivot == null)
			$pivot = time(); 
		$datediff = $pdate - $pivot;
		return floor($datediff/(60*60*24));
	}

	###########################################################################

	public static function is_a_date( $str ) 
	{ 
		if($str === false || $str == null)
			return FALSE;
		$stamp = strtotime( $str ); 
		if (!is_numeric($stamp)) 
			return FALSE; 
		$month = date( 'm', $stamp ); 
		$day   = date( 'd', $stamp ); 
		$year  = date( 'Y', $stamp ); 
		if (checkdate($month, $day, $year)) 
			return TRUE; 
		return FALSE; 
	}
	
	###########################################################################
	
	public static function expand_date_codes($datestr)
	{
		$pos = strpos($datestr, "[TODAY");
		while($pos !== false) {
			$tleft = substr($datestr, 0, $pos);
			$tright = substr($datestr, $pos+6);
			$pend = strpos($tright, "]");
			if($pend !== false) {
				$days = substr($tright, 0, $pend);
				$tright = substr($tright, $pend+1);
				if(UTIL::starts_with($days, "+")) {
					$days = intval(substr($days,1));
					$dc = date("Y-m-d", time()+($days*24*3600));
				}
				else if(UTIL::starts_with($days, "-")) {
					$days = intval(substr($days,1));
					$dc = date("Y-m-d", time()-($days*24*3600));
				}
				$datestr = $tleft . $dc . $tright;
				$pos = strpos($datestr, "[TODAY");
			}
			else
				break;
		}
		
		return $datestr;
	}
	
	###########################################################################
	# Echo all the variables passed 
	public static function smart_echo_multiple()
	{
		$msg = "";
		$sep = ", ";
		$args = func_get_args();
		foreach($args as $a)
			$msg .= $a . $sep;
		self::smart_echo($msg);
	}
	
	###########################################################################
	
	public static function text_echo($msg)
	{
		echo "<pre>"; 
		echo $msg;
		echo "</pre>";
	}

	###########################################################################

	public static function smart_echo($msg, $prompt="")
	{
		if($prompt != "")
			$msg = "$prompt: $msg";
		if($msg === false)
			$msg = "False";
		else if($msg === true)
			$msg = "True";
		if(self::is_cli()) {
			$msg = html_entity_decode($msg);
			echo strip_tags($msg) . "\n";
		}
		else {
			if($msg == strip_tags($msg) && strpos($msg, "\n") !== false) 
				echo "<pre>$msg</pre>";
			else
				echo "<p>$msg</p>";
		}
	}
	
	###########################################################################

	public static function print($pdata) 
	{
		self::print_data($pdata);
	}
	
	###########################################################################

	public static function print_data($pdata) 
	{
		if(is_array($pdata) || is_object($pdata))
		{
			if(!self::is_cli())
				echo "<pre>"; 
			print_r($pdata); 
			if(!self::is_cli())
				echo "</pre>";
		}
		else
			self::smart_echo($pdata);
	}
	
	###########################################################################
	# check if all the specified keys are set in input array
	
	public static function iskeys_set($input, $keys) 
	{
		if(is_array($input)) {
			if(is_array($keys)) {
				foreach($keys as $k) {
					if(!isset($input[$k]))
						return false;
				}
				return true;
			}
			else
				return isset($input[$keys]);
		}
		return false;
	}

	###########################################################################
	
	public static function parse_command_line(&$parray)
	{
		if(self::is_cli())
		{
			global $argv;
			parse_str(implode('&', array_slice($argv, 1)), $parray);
			return true;
		}
		return false;
	}
	
	###############################################################################

	public static function find_all_matched_files($folder, $filemask)
	{
		$filenames = array();
		if(!self::ends_with($folder, "/"))
			$folder .= "/";
		$matches = glob($folder . $filemask);
		if($matches !== false) {
			if(is_array($matches)) {
				if(count($matches) > 0) {
					foreach($matches as $match)
						$filenames[] = basename($match);
					return $filenames;
				}
			}
		}
		return $filenames;
	}

	###############################################################################

	public static function find_all_matched_folders($folder)
	{
		$folders = array();
		if(!self::ends_with($folder, "/"))
			$folder .= "/";
		$matches = glob($folder . "*", GLOB_ONLYDIR);
		if($matches !== false && is_array($matches)) {
			foreach($matches as $match)
				$folders[] = basename($match);
		}
		return $folders;
	}

	###############################################################################
	# Returns the root path
	# Examples
	# https://www.example.com/wmdsed3/ => /wmdsed3/
	# https://www.example.com/scripts/wmdsed3/ => /scripts/wmdsed3/
	public static function get_install_url_path()
	{
		if(self::is_cli())
			return "";
		if(!isset($_SERVER['PHP_SELF']) || !isset($_SERVER['SCRIPT_NAME']))
			return false;

		$basepath = $_SERVER['PHP_SELF'];
		$pos = strpos($basepath, "/lib/php/");
		if($pos !== false)
			$basepath = substr($basepath, 0, $pos) . "/";
		$pos = strpos($basepath, "/thirdparty/");
		if($pos !== false)
			$basepath = substr($basepath, 0, $pos) . "/";
		$basepath = str_replace(basename($_SERVER['SCRIPT_NAME']), "", $basepath);
		return $basepath;
	}
	
	###############################################################################

	public static function get_root_url($default="http://your/install/path")
	{
		if(isset($_SERVER['HTTP_HOST']))
			$rooturl = (self::is_https() ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
		else
			$rooturl = $default;
		$rooturl .= self::get_install_url_path();
		return $rooturl;
	}
	
	###############################################################################
	
	public static function get_log_folder_path()
	{
		$thelogfolder = "";
		global $cfg_log_folder_path;
		if(isset($cfg_log_folder_path)) {
			if(is_dir($cfg_log_folder_path)) {
				if(substr($cfg_log_folder_path, -1) != DIRECTORY_SEPARATOR) 
					$cfg_log_folder_path .= DIRECTORY_SEPARATOR;
				$thelogfolder = $cfg_log_folder_path;
			}
		}
		if($thelogfolder == "")
			$thelogfolder = self::get_install_folder_path() . "logs" . DIRECTORY_SEPARATOR;
		
		try {
			if(!is_dir($thelogfolder)) {
				$old = umask(0);
				mkdir($thelogfolder, 0777);
				umask($old);
			}
			if(!is_writable($thelogfolder) && is_dir($thelogfolder)) {
				chmod($thelogfolder, 0777);
			}
			if(is_writable($thelogfolder)) {
				$htaccess = $thelogfolder . ".htaccess";
				if(!is_file($htaccess)) {
					$hfp = fopen($htaccess,'a+');
					if($hfp) {
						fwrite($hfp,"Deny from  all\n");
						fclose($hfp);
					}
				}
			}
		}
		catch (\Error $ex)  {
		}
		
		return $thelogfolder;
	}
	
	###############################################################################
	
	public static function get_install_folder_path()
	{
		global $cfg_base_install_path;
		if(isset($cfg_base_install_path))
			return $cfg_base_install_path;
		return realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . "../../../../") . DIRECTORY_SEPARATOR;
	}
	
	###############################################################################
	
	public static function get_default_webshot_script() {
		$datafolder = "websites";
		$nodejsfile = "websites.js";
		$outfolder = self::get_log_folder_path() . $datafolder . DIRECTORY_SEPARATOR;
		if(is_dir($outfolder)) {
			if(!is_file($outfolder . $nodejsfile)) {
				copy(self::get_install_folder_path() . 'lib' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . $nodejsfile, $outfolder . $nodejsfile);
			}
		}
		if(!is_file($outfolder . $nodejsfile)) {
			// Support version 4 webshots settings and folders
			$datafolder = "webshots";
			$outfolder = self::get_log_folder_path() . $datafolder . DIRECTORY_SEPARATOR;
			$nodejsfile = "shot.js";
			if(!is_file($outfolder . $nodejsfile))
				return false;
		}
		return array('folder'=>$outfolder, 'script'=>$nodejsfile, 'path'=>$outfolder . $nodejsfile);
	}
	
	###############################################################################

	public static function get_local_php_log_filename()
	{
		$logfile = self::get_log_folder_path() . "php.log";
		if(is_writable(dirname($logfile))) 
			return $logfile;
		return false;
	}
	
	###############################################################################

	public static function local_php_log_to_file($filename, $msg)
	{ 
		$fd = fopen($filename, "a");
		fwrite($fd, $msg);
		fclose($fd);
	}

	###############################################################################

	public static function local_php_log_truncate($length=0)
	{
		$fd = fopen(self::get_local_php_log_filename(), "r+");
		ftruncate($fd, $length);
		fclose($fd);
	}

	###############################################################################

	public static function local_php_log($msg, $addlf=true)
	{ 
		if($msg != "") {
			if($addlf)
				$msg .= "\n";
			self::local_php_log_to_file(self::get_local_php_log_filename(), $msg);
		}
	}
	
	###############################################################################

	public static function local_php_log_request_data()
	{
		$ldata = self::get_unsafe_request_array_as_string(true);
		self::local_php_log($ldata);
	}
	
	###########################################################################
	
	public static function sanitize_string($str) {
		$str = preg_replace('/\x00|<[^>]*>?/', '', $str);
		return html_entity_decode($str, ENT_QUOTES, 'UTF-8');
	}
	
	###########################################################################
	
	public static function get_from_array(&$entry, $default=null)
	{
		return isset($entry) ? $entry : $default;
	}
	
	###########################################################################
	// Find the first entry from a set of keys in an array

	public static function find_first_in_array($row, $keys, $default=false)
	{
		foreach($keys as $key) {
			if(isset($row[$key]))
				return $row[$key];
		}
		return $default;
	}

	###############################################################################
	
	public static function parse_request_data_in_cli()
	{
		self::parse_command_line($_REQUEST);
	}
	
	###############################################################################
	
	public static function get_unsafe_request_array_as_string($includetimestamp=false)
	{
		$ldata = "";
		if($includetimestamp) {
			$timestamp = strftime("%c",time());
			$ldata = "\n--" . $timestamp . "--\n";
		}
		$rd = self::get_unsafe_request_data_array();
		foreach($rd as $key=>$value) {
			$ldata .= $key . " - " . $value . "\n";
		}
		return $ldata;
	}
	
	###############################################################################
	
	public static function get_unsafe_request_data($key, $default="")
	{
		return self::get_from_array($_REQUEST[$key], $default);
	}
	
	###############################################################################
	# Get all request data except with key from a pre-defined key list
	
	static function get_unsafe_request_data_array($ignorekeys=array())
	{
		$rd = array();
		foreach($_REQUEST as $key=>$value) {
			if(!UTIL::in_array_casei($key, $ignorekeys))
				$rd[$key] = $value;
		}
		return $rd;
	}
	
	###############################################################################
	
	public static function is_request_key_set($key)
	{
		return (self::get_unsafe_request_data($key, false) !== false) ? true : false;
	}
	
	###############################################################################
	
	public static function get_sanitized_request_string($name, $default="")
	{
		$rd = self::get_unsafe_request_data($name, false);
		return ($rd === false) ? $default : self::sanitize_string($rd);
	}
	
	###############################################################################
	
	public static function get_request_data_boolean($name, $default=false)
	{
		$rd = self::get_unsafe_request_data($name, false);
		return ($rd === false) ? $default : self::str_to_bool($rd);
	}
	
	###############################################################################
	
	public static function get_request_data_integer($name, $default, $lowrange=0, $highrange=0)
	{
		$rd = self::get_unsafe_request_data($name, false);
		$ival = ($rd === false) ? $default : intval($rd);
		if(intval($lowrange) < intval($highrange)) {
			$ival = $ival < intval($lowrange) ? intval($lowrange) : $ival;
			$ival = $ival > intval($highrange) ? intval($highrange) : $ival;
		}
		return $ival;
	}
	
	###########################################################################
	
	static function get_sanitized_request_string_array($keylist)
	{
		$data = array();
		$rd = self::get_unsafe_request_data_array();
		foreach($rd as $key=>$value) {
			if(UTIL::in_array_casei($key, $keylist))
				$data[$key] = self::sanitize_string($value);
		}
		return $data;
	}
	
	###########################################################################
	
	public static function curl_get_url($url, $timeout=10, $httpauth=false, $httpheader=false, $useragent=false, $proxy=null, $follow=false)
	{
		$retv = array('result' => "", 'status' => 0);
		if(function_exists('curl_version'))
		{
			$ch = curl_init();
			curl_setopt($ch, CURLOPT_URL, $url);
			curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 
			curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
			if($follow)
				curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
			
			if($httpauth !== false)
				curl_setopt($ch, CURLOPT_HTTPAUTH, $httpauth);
			if($httpheader !== false)
				curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
			if($useragent !== false && $useragent != "") {
				if($useragent == "?" || $useragent == "-")
					$useragent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36';
				curl_setopt($ch,CURLOPT_USERAGENT,$useragent);
			}
			if($proxy) {
				if(strtolower($proxy['type']) == 'socks5')
					curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
				$proxy_url = $proxy['url'] . ':' . $proxy['port'];
				curl_setopt($ch, CURLOPT_PROXY, $proxy_url);
				if($proxy['user'] && strlen($proxy['user'])) {
					$proxy_auth = $proxy['user'] . ':' . $proxy['password'];
					curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_auth);
				}
				# DELETE THIS DEBUG PRINT
				UTIL::print($proxy_url . ': ' . $url);
			}
			
			$retv['result'] = curl_exec ($ch);
			$retv['result'] = ($retv['result'] === false) ? "" : $retv['result'];
			$retv['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
			if (curl_error($ch)) 
				$retv['error'] = curl_error($ch);
			curl_close ($ch);
		}
		return $retv;
	}
	
	###########################################################################
	
	public static function curl_put_url($url, $timeout=10, $httpauth=false, $httpheader=false, $postdata=false)
	{
		$retv = array('result' => "", 'status' => 0);
		if(function_exists('curl_version'))
		{
			$ch = curl_init();
			curl_setopt($ch, CURLOPT_URL, $url);
			curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 
			curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
			
			if($httpauth !== false)
				curl_setopt($ch, CURLOPT_HTTPAUTH, $httpauth);
			if($httpheader !== false)
				curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
			if($postdata !== false && is_array($postdata))
				$postdata = http_build_query($postdata);
			if($postdata !== false)
				curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata);
			
			$retv['result'] = curl_exec ($ch);
			$retv['result'] = ($retv['result'] === false) ? "" : $retv['result'];
			$retv['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
			if (curl_error($ch)) 
				$retv['error'] = curl_error($ch);
			curl_close ($ch);
		}
		return $retv;
	}
	
	###########################################################################
	
	public static function curl_post_url($url, $timeout=10, $httpauth=false, $httpheader=false, $postdata=false, $customrequest=[])
	{
		$retv = array('result' => "", 'status' => 0);
		if(function_exists('curl_version'))
		{
			$ch = curl_init();
			curl_setopt($ch, CURLOPT_URL, $url);
			curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 
			curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
			
			foreach($customrequest as $cr)
				curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $cr);

			if($httpauth !== false)
				curl_setopt($ch, CURLOPT_HTTPAUTH, $httpauth);
			if($httpheader !== false)
				curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
			
			curl_setopt( $ch, CURLOPT_POST, 1 );
			if($postdata !== false && is_array($postdata))
				$postdata = http_build_query($postdata);
			if($postdata !== false)
				curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata);
			
			$retv['result'] = curl_exec ($ch);
			$retv['result'] = ($retv['result'] === false) ? "" : $retv['result'];
			$retv['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
			if (curl_error($ch)) 
				$retv['error'] = curl_error($ch);
			curl_close ($ch);
		}
		return $retv;
	}
	
	###########################################################################
	
	public static function curl_get_url_data($url, $timeout=10, $httpauth=false, $httpheader=false, $redirs=5, $ssl_verify=true, $useragent=false, $proxy=null)
	{
		$retv = array('header' => "", 'body'=>"", 'status' => 0, 'furl'=>"", 'ip'=>"");
		if(function_exists('curl_version'))
		{
			$ch = curl_init();
			if($ssl_verify === false) {
				curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
				curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
			}
			if($useragent !== false && $useragent != "") {
				if($useragent == "?" || $useragent == "-")
					$useragent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13';
				curl_setopt($ch,CURLOPT_USERAGENT,$useragent);
			}
			curl_setopt($ch, CURLOPT_URL, $url);
			curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 
			curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
			curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
			curl_setopt($ch, CURLOPT_MAXREDIRS, $redirs);
			curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
			if($httpauth !== false)
				curl_setopt($ch, CURLOPT_HTTPAUTH, $httpauth);
			if($httpheader !== false)
				curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
			
			if($proxy) {
			}
			
			$headers = array();
			curl_setopt($ch, CURLOPT_HEADERFUNCTION,
				function($curl, $header) use (&$headers)
				{
					$len = strlen($header);
					$header = explode(':', $header, 2);
					if (count($header) < 2) { // Handle the HTTP Status part
						foreach($header as $h) {
							$h = trim($h);
							if($h != "" && UTIL::starts_with($h, "HTTP/"))
								$headers['http'][] = $h;
						}
						return $len;
					}
					$name = strtolower(trim($header[0]));
					if (!array_key_exists($name, $headers))
					  $headers[$name] = [trim($header[1])];
					else
					  $headers[$name][] = trim($header[1]);

					return $len;
				});
			
			$result = curl_exec ($ch);
			if($result !== false) {
				$retv['header'] = $headers;
				$retv['body'] = $result;
				$retv['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
				$retv['furl'] = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
				$retv['ip'] = curl_getinfo($ch,CURLINFO_PRIMARY_IP);
			}
			else if (curl_error($ch))
				$retv['error'] = curl_error($ch);
			curl_close ($ch);
		}
		return $retv;
	}
	
	###############################################################################

	public function curl_download_file($url, $file, $authHeaders=null)
	{
		$retv = array('result' => "", 'status' => 0);
		if(function_exists('curl_version')) {
			$fp = fopen($file, 'w');
			if($fp) {
				$userAgent   =  'Mozilla/5.0';
				$ch = curl_init();
				$options = array(
				   CURLOPT_USERAGENT       => $userAgent,
				   CURLOPT_URL             => $url,
				   CURLOPT_RETURNTRANSFER  => 1,
				   CURLOPT_FILE => $fp,
				   CURLOPT_PROGRESSFUNCTION => array($this, 'curl_progress_callback'),
				   CURLOPT_NOPROGRESS => false,
				   CURLOPT_NOPROGRESS => false,
				);

				if(is_array($authHeaders))
					$options[CURLOPT_HTTPHEADER] = $authHeaders;
				curl_setopt_array($ch, $options);

				$retv['result'] = curl_exec ($ch);
				$retv['result'] = ($retv['result'] === false) ? "" : $retv['result'];
				$retv['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
				if (curl_error($ch))
					$retv['error'] = curl_error($ch);
				curl_close($ch);
			}
			else {
				$retv['error'] = "Error opening file for write";
			}
		}
		return $retv;
	}

	###############################################################################

	public function curl_progress_callback($resource, $download_size, $downloaded, $upload_size, $uploaded)
	{
		 if($download_size > 0 && $download_size && self::is_cli())
			echo "\r\t" . round($downloaded/$download_size*100, 0) . "% downloaded            \r";
	}

	###############################################################################

	public static function get_redirect_url_from_httpheader($headers) 
	{ 
		$url = "";
		$array = $headers; 
		$count = count($array); 
		for ($i=0; $i < $count; $i++) { 
			$pos = strpos($array[$i], "ocation:");
			if($pos !== false) 
					$url = trim(substr($array[$i], $pos+8));  
		} 
		return $url; 
	}
	
	###########################################################################
	
	public static function whois($query, $server, $port=43, $timeout=20)
	{
		$m_sockettimeout = $timeout/2;
		$hip = gethostbyname($server);
		$fp = @fsockopen($hip, $port, $errno, $errstr, $timeout);
		if( $fp )
		{
			$data = "";
			@socket_set_timeout($fp, $m_sockettimeout);
			@fputs($fp, $query."\r\n");
			$start_time = time();
			while( !@feof($fp) ) {
				$chunk = @fread($fp, 4096);
				if($chunk !== false)
					$data .= $chunk;
				if((time()-$start_time) >= $timeout)
					break;
			}
			@fclose($fp);
			return array('status'=>true, 'data'=>$data);
		}
		else 
			return array('status'=>false, 'errno'=>$errno, 'error'=>$errstr);
	}
	
	###########################################################################
	
	public static function ip_asn_lookup($ip) {
		$dataarray = array();
		$blocks = explode('.', trim($ip));
		if(is_array($blocks)) {
			if(count($blocks) == 4) {
				$host = $blocks[3] . "." . $blocks[2] . "." . $blocks[1] . "." . $blocks[0] . ".origin.asn.cymru.com";
				$result = dns_get_record($host , DNS_TXT);
				if(isset($result[0]['txt'])) {
					$entries = $result[0]['txt']; 
					$blocks = explode('|', $entries);
					if(is_array($blocks)) {
						$asnum = "AS" . trim($blocks[0]);
						$dataarray['ip_asnumber'] = $asnum;
						$result = dns_get_record($asnum . ".asn.cymru.com", DNS_TXT);
						if(isset($result[0]['txt'])) {
							$blocks = explode("|", $result[0]['txt']); 
							$count = count($blocks);
							$asname = trim($blocks[$count-1]);
							$dashpos = strpos($asname, " - ");
							if($dashpos > 0) {
								$asname = substr($asname, 0, $dashpos);
							}
							$dataarray['ip_asname'] = $asname;
						}	
					}
				}
			}
		}
		return $dataarray;
	}
	
	###########################################################################
	
	public static function array_to_text($data, $seperator="\n", $maxcount=0)
	{
		$textdata = "";
		if($data != "" && is_array($data)) {
			$count = 0;
			foreach($data as $k=>$v) {
				$textdata .= "$k: ";
				if(is_array($v))
					$textdata .= print_r(implode($seperator, $v), true);
				else
					$textdata .= $v;
				$textdata .= $seperator;
				if($maxcount && $count++ >= $maxcount)
					break;
			}
		}
		return trim($textdata);
	}
	
	###############################################################################
	
	public static function file_get_contents_utf8($fn) 
	{
		$content = file_get_contents($fn);
		return mb_convert_encoding($content, 'UTF-8', mb_detect_encoding($content, 'UTF-8, ISO-8859-1, Windows-1252', true));
	}
	
	###############################################################################
	
	public static function write_utf8_file($filename, $content) 
	{ 
        $f=fopen($filename, "w"); 
        # Now UTF-8 - Add byte order mark 
        fwrite($f, pack("CCC",0xef,0xbb,0xbf)); 
        fwrite($f,$content); 
        fclose($f); 
	} 
	
	###############################################################################

	public static function create_containing_folders($path) 
	{
		if (is_dir($path)) return true;
		$prev_path = substr($path, 0, strrpos($path, '/', -2) + 1 );
		$return = self::create_containing_folders($prev_path);
		return ($return && is_writable($prev_path)) ? mkdir($path) : false;
	}
	
	###############################################################################
	# Based on Code from stanislav dot eckert at vizson dot de
	# @ https://www.php.net/manual/en/ziparchive.extractto.php

	public static function extractZipFolderTo($zipArchive, $destination, $subdir, ?callable $callback=null) {
		$errors = array();
		// Prepare dirs
		$destination = str_replace(array("/", "\\"), DIRECTORY_SEPARATOR, $destination);
		$subdir = str_replace(array("/", "\\"), "/", $subdir);
		if (substr($destination, mb_strlen(DIRECTORY_SEPARATOR, "UTF-8") * -1) != DIRECTORY_SEPARATOR)
			$destination .= DIRECTORY_SEPARATOR;
		if (substr($subdir, -1) != "/")
			$subdir .= "/";
		// Extract files
		for ($i = 0; $i < $zipArchive->numFiles; $i++) {
			$filename = $zipArchive->getNameIndex($i);
			if (substr($filename, 0, mb_strlen($subdir, "UTF-8")) == $subdir) {
				$relativePath = substr($filename, mb_strlen($subdir, "UTF-8"));
				$relativePath = str_replace(array("/", "\\"), DIRECTORY_SEPARATOR, $relativePath);

				if(mb_strlen($relativePath, "UTF-8") > 0) {
					if(substr($filename, -1) == "/") {  # Directory
						// New dir
						if (!is_dir($destination . $relativePath))
							if (!@mkdir($destination . $relativePath, 0755, true))
							$errors[$i] = $filename;
					}
					else {
						if(dirname($relativePath) != ".") {
							if (!is_dir($destination . dirname($relativePath))) {
								// New dir (for file)
								@mkdir($destination . dirname($relativePath), 0755, true);
							}
						}
						// New file
						if(@file_put_contents($destination . $relativePath, $zipArchive->getFromIndex($i)) === false)
							$errors[$i] = $filename;
					}
				}
			}
			if($callback != null) {
				$status = call_user_func($callback, $filename, $errors);
				if($status === false)
					break;
			}
		}
		return $errors;
	}

	###############################################################################
	# Call as array(new CodePunch\Base\Util(), 'zip_extract_callback')

	public static function zip_extract_callback($filename, $errors)
	{
		if(in_array($filename, $errors))
			self::smart_echo("Error: $filename");
		else
			self::smart_echo($filename);
		return true;
	}

	###############################################################################

	public static function extractZip($zipfile, $outfolder, $subdir="", $zipextractcallback=null)
	{
		if(class_exists('ZipArchive') !== false) {
			$zip = new \ZipArchive;
			if($zip->open($zipfile) === TRUE) {
				if($subdir == "")
					$zip->extractTo($outfolder);
				else
					self::extractZipFolderTo($zip, $outfolder, $subdir, $zipextractcallback);
				$zip->close();
				return true;
			} 
		}
		return false;
	}
	
	###############################################################################
	
	public static function get_file_from_zip($zipfile, $filename)
	{
		$contents = false;
		if(class_exists('ZipArchive') !== false)
		{
			$zip = new \ZipArchive;
			if ($zip->open($zipfile) === TRUE) 
			{
				if ($zip->locateName($filename) !== false)
					$contents = $zip->getFromName($filename);
				$zip->close();
			}
		}
		return $contents;
	}
	
	###############################################################################
	
	public static function show_image_from_file($filename)
	{
		if(is_file($filename))
		{
			$type = self::get_mime_type($filename);
			header('Content-Type: '.$type);
			header('Content-Length: ' . filesize($filename));
			readfile($filename);
			return true;
		}
		else
			return false;
	}
	
	###############################################################################

	public static function show_image_from_zip($zip_file, $file_name)
	{
		if(class_exists('ZipArchive') !== false) {
			$z = new \ZipArchive();
			if ($z->open($zip_file) !== true) {
				error_log("Unable to open $zip_file", 0);
				return false;
			}
			$stat = $z->statName($file_name);
			$fp   = $z->getStream($file_name);
			if(!$fp) {
				error_log("Unable to get stream $file_name", 0);
				return false;
			}
			$mtype = self::get_mime_type($file_name);
			ob_clean();
			header("Content-Type: $mtype");
			header('Content-Length: ' . $stat['size']);
			fpassthru($fp);
			return true;
		}
		else 
			error_log("No ZipArchive", 0);
		return false;
	}
	
	###############################################################################
	
	public static function seconds_to_time($seconds) 
	{
		$dtF = new \DateTime("@0");
		$seconds = round($seconds,0,PHP_ROUND_HALF_UP);
		$dtT = new \DateTime("@" . $seconds);
		return $dtF->diff($dtT)->format('%a:%h:%i:%s');
	}
	
	###############################################################################
	# https://stackoverflow.com/questions/35299457/getting-mime-type-from-file-name-in-php
	#
	
	public static function get_mime_type($filename) 
	{
		$idx = explode( '.', $filename );
		$count_explode = count($idx);
		$idx = strtolower($idx[$count_explode-1]);

		$mimet = array( 
			'txt' => 'text/plain',
			'htm' => 'text/html',
			'html' => 'text/html',
			'php' => 'text/html',
			'css' => 'text/css',
			'js' => 'application/javascript',
			'json' => 'application/json',
			'xml' => 'application/xml',
			'swf' => 'application/x-shockwave-flash',
			'flv' => 'video/x-flv',

			// images
			'png' => 'image/png',
			'jpe' => 'image/jpeg',
			'jpeg' => 'image/jpeg',
			'jpg' => 'image/jpeg',
			'gif' => 'image/gif',
			'bmp' => 'image/bmp',
			'ico' => 'image/vnd.microsoft.icon',
			'tiff' => 'image/tiff',
			'tif' => 'image/tiff',
			'svg' => 'image/svg+xml',
			'svgz' => 'image/svg+xml',

			// archives
			'zip' => 'application/zip',
			'rar' => 'application/x-rar-compressed',
			'exe' => 'application/x-msdownload',
			'msi' => 'application/x-msdownload',
			'cab' => 'application/vnd.ms-cab-compressed',

			// audio/video
			'mp3' => 'audio/mpeg',
			'qt' => 'video/quicktime',
			'mov' => 'video/quicktime',

			// adobe
			'pdf' => 'application/pdf',
			'psd' => 'image/vnd.adobe.photoshop',
			'ai' => 'application/postscript',
			'eps' => 'application/postscript',
			'ps' => 'application/postscript',

			// ms office
			'doc' => 'application/msword',
			'rtf' => 'application/rtf',
			'xls' => 'application/vnd.ms-excel',
			'ppt' => 'application/vnd.ms-powerpoint',
			'docx' => 'application/msword',
			'xlsx' => 'application/vnd.ms-excel',
			'pptx' => 'application/vnd.ms-powerpoint',


			// open office
			'odt' => 'application/vnd.oasis.opendocument.text',
			'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
		);

		if (isset( $mimet[$idx] )) {
		 return $mimet[$idx];
		} else {
		 return 'application/octet-stream';
		}
	 }
	 
	 ###########################################################################

	public static function idn_convert($domain)
	{
		if($domain) {
			if(function_exists('idn_to_ascii'))
				return idn_to_ascii($domain,IDNA_NONTRANSITIONAL_TO_ASCII,INTL_IDNA_VARIANT_UTS46);
			else {
				require_once('idna_convert.class.php');
				$IDN = new \idna_convert(array('idn_version' => 2008));
				return strtolower($IDN->encode($domain));
			}
		}
		return $domain;
	}

	###########################################################################

	public static function idn_reconvert($ascii_domain)
	{
		if(function_exists('idn_to_utf8'))
			return idn_to_utf8($ascii_domain,IDNA_NONTRANSITIONAL_TO_ASCII,INTL_IDNA_VARIANT_UTS46);
		else {
			require_once('idna_convert.class.php');
			$IDN = new \idna_convert(array('idn_version' => 2008));
			return $IDN->decode($ascii_domain);
		}
	}

	###########################################################################

	public static function idn_convert_to_host_name($domain, $subdomain)
	{
		$ascii_domain    = self::idn_convert($domain);
		$ascii_subdomain = self::idn_convert($subdomain);
		return ($subdomain == "" ? "" : ($ascii_subdomain . ".")) . $ascii_domain; 
	}
	
	###############################################################################

	public static function parse_hostname_with_tld($hostname, $tld)
	{
		$info = array('domain'=>'', 'subdomain'=>'', 'tld'=>'');
		$tld = "." . $tld;
		if(mb_strlen($hostname) > mb_strlen($tld)) {
			$pos = mb_strrpos($hostname, $tld);
			if($pos !== false) {
				$firstpart = mb_substr($hostname, 0, $pos);
				$lastpart = mb_substr($hostname, $pos);
				if($lastpart == $tld) {
					$pos = mb_strrpos($firstpart, ".");
					if($pos !== false) {
						$info['domain'] = mb_substr($firstpart, $pos+1) . $tld;
						$info['subdomain'] = mb_substr($firstpart, 0, $pos);
						$info['tld'] = mb_substr($tld, 1);
						return $info;
					}
					else {
						$info['domain'] = $firstpart . $tld;
						$info['tld'] = mb_substr($tld, 1);
						$info['subdomain'] = '';
						return $info;
					}
				}
			}						
		}
		return false;
	}
	
	###############################################################################
	
	public static function split_hostname($hostname, $domain_suffix_list)
	{
		$parsed = array('domain'=>"", 'subdomain'=>"", 'tld'=>'');
		$dotcount = substr_count($hostname, ".");
		if($dotcount == 0) {
			$parsed['subdomain'] = trim($hostname);
			return $parsed;
		} 
		else  {
			$pos = mb_strrpos($hostname, ".");
			$thistld = mb_substr($hostname, $pos+1);
			// Look for known second level domains.
			if(count($domain_suffix_list) && isset($domain_suffix_list[$thistld])) {
				$the_tlds = $domain_suffix_list[$thistld];
				// Sort so that the longer ones are at start.
				usort($the_tlds, function($a, $b) {
					return strlen($b) - strlen($a);
				});
			}
			else
				$the_tlds = array("co.uk", "ac.uk", "gov.uk", "ltd.uk", 
					"org.uk", "nic.uk", "plc.uk", "net.uk", "nhs.uk", "co.in", "gov.in", "nic.in",
					"com.au", "gov.au", "edu.au");
			foreach($the_tlds as $tld) {
				$nparsed = self::parse_hostname_with_tld($hostname, $tld);
				if($nparsed !== false)
					return $nparsed;
				if(!mb_check_encoding($tld, 'ASCII'))
				{
					$idntld = self::idn_convert($tld);
					$nparsed = self::parse_hostname_with_tld($hostname, $idntld);
					if($nparsed !== false) {
						$nparsed['tld'] = self::idn_reconvert($nparsed['tld']);
						return $nparsed;
					}
				}
			}
			
			// Can't find any known second level domains. Just pull out
			// the last dotted pair as the domain name and rest as subdomain.
			$pos = mb_strrpos($hostname, ".");
			$isip = preg_match("/^\d+$/", str_replace(".", "", $hostname));
			if($pos !== false && !$isip) {
				$tld = mb_substr($hostname, $pos);
				$firstpart = mb_substr($hostname, 0, $pos);
				$pos = mb_strrpos($firstpart, ".");
				if($pos !== false) {
					$parsed['domain'] = mb_substr($firstpart, $pos+1) . $tld;
					$parsed['subdomain'] = mb_substr($firstpart, 0, $pos);
					$parsed['tld'] = mb_substr($tld, 1);
					return $parsed;
				}
			}
		}
		return $parsed;
	}
	
	###############################################################################

	public static function createDomainSuffixTableData()
	{
		$ts = self::get_public_domain_suffix_list();
		$tdata = "\t\t"; $index = 0;
		foreach($ts as $t) {
			$pos = mb_strrpos($t, ".");
			$thistld = $pos !== false ? mb_substr($t, $pos+1) : $t;
			$tdata .= "$t,$thistld||";
			$index++;
			if($index >= 5) {
				$tdata .= "\n\t\t";
				$index = 0;
			}
		}
		$myfile = fopen(self::get_log_folder_path()."tldlist.txt", "w");
		fwrite($myfile, trim($tdata,"|"));
		fclose($myfile);
	}

	###############################################################################

	public static function get_public_domain_suffix_list($onlyseclevel=false)
	{
		$tlds = array();
		$url = "https://publicsuffix.org/list/public_suffix_list.dat";
		$result = @file_get_contents($url);
		if($result !== false) {
			$parts = explode("\n", $result);
			foreach($parts as $part) {
				$part = trim($part, "\r\n\t ");
				if($part == "")
					continue;
				else if($part[0] == '!')
					continue;
				else if(mb_strpos($part, "===END ICANN DOMAINS===") !== false)
					break;
				else if(strlen($part) >= 2) {
					if($part[0] == '/' && $part[1] == '/')
						continue;
					else if($part[0] == '*' && $part[1] == '.')
						$part = mb_substr($part, 2);
				}
				if($onlyseclevel) {
					if(mb_strpos($part, ".") === false)
						continue;
				}
				$tlds[] = $part;
			}
		}
		return array_unique($tlds);
	}
	
	###############################################################################
	
	public static function clean_subdomain($subdomain)
	{
		$url = strtolower(trim($subdomain));
		
		if ( substr($url, 0, 7) == 'http://')  { $url = substr($url, 7); }
		if ( substr($url, 0, 8) == 'https://') { $url = substr($url, 8); }
		if ( strpos($url, '/') !== false) 
		{
			$ex = explode('/', $url);
			$url = $ex[0];
		}
		$url = str_replace("..", ".", $url);
		$url = str_replace("..", ".", $url);
		$url = strip_tags($url);

		// remove unwanted punctuation
		static $punc = array(
			// remove
			"'" => '', '"' => '', '`' => '', '=' => '', '+' => '', '*' => '', '&' => '', '^' => '',
			'%' => '', '$' => '', '#' => '', '@' => '@', '!' => '', '<' => '', '>' => '', '?' => '',
			'[' => '', ']' => '', '{' => '', '}' => '', '(' => '', ')' => '',
			' ' => '', ',' => '', ';' => '', ':' => '', '/' => '', '|' => '',
			'<' => '', '>' => ''
		);
		$url = strtr($url, $punc);
		return trim($url, ". ");
	}
	
	###############################################################################
	
	public static function clean_domain_name($domain, $suffixlist=array())
	{
		$split_hostname = true;
		$domain = trim($domain);
		if(substr($domain, 0, 1) == '[' && substr($domain, strlen($domain)-1, 1) == ']') {
			$domain = trim($domain, "[ ]");
			$split_hostname = false;
		}

		$url = self::idn_convert($domain);

		if ( substr($url, 0, 7) == 'http://')  { $url = substr($url, 7); }
		if ( substr($url, 0, 8) == 'https://') { $url = substr($url, 8); }
		if ( strpos($url, '/') !== false) 
		{
			$ex = explode('/', $url);
			$url = $ex[0];
		}
		$url = str_replace("..", ".", $url);
		$url = str_replace("..", ".", $url);
		$url = strip_tags($url);
		$tpos = mb_strpos($url, "\t");
		if($tpos !== false)
			$url = mb_substr($url, 0, $tpos);

		// remove unwanted punctuation
		static $punc = array(
			// remove
			"'" => '', '"' => '', '`' => '', '=' => '', '+' => '', '*' => '', '&' => '', '^' => '',
			'%' => '', '$' => '', '#' => '', '@' => '', '!' => '', '<' => '', '>' => '', '?' => '',
			'[' => '', ']' => '', '{' => '', '}' => '', '(' => '', ')' => '',
			' ' => '', ',' => '', ';' => '', ':' => '', '/' => '', '|' => '',
			'<' => '', '>' => ''
		);
		$url = strtr($url, $punc);
		$url = trim($url, ". ");

		$url = self::idn_reconvert($url);
		if($split_hostname)
			$di = self::split_hostname($url, $suffixlist);
		else
			$di = array('domain'=>$url, 'subdomain'=>'', 'tld'=>'');
		return $di;
	}
	
	###############################################################################
	
	public static function strip_punctuation($string, $spacetoscore=false) 
	{
		// remove unwanted punctuation
		static $punc = array(
			// remove
			"'" => '', '"' => '', '`' => '', '=' => '', '+' => '', '*' => '', '&' => '', '^' => '',
			'%' => '', '$' => '', '#' => '', '@' => '', '!' => '', '<' => '', '>' => '', '?' => '',
			'[' => '', ']' => '', '{' => '', '}' => '', '(' => '', ')' => '', '~' => '',
			' ' => '', ',' => '', ';' => '', ':' => '', '/' => '', '|' => '',
			'<' => '', '>' => ''
		);
		$string = str_replace("&#39;", "'", $string);
		if($spacetoscore)
			unset($punc[' ']);
		$string = strtr($string, $punc);
		if($spacetoscore)
			$string = str_replace(" ", "_", $string);
		return trim($string);
	}
	
	###############################################################################
	
	// For backward compatibility. New versions should use password_hash from PHP
	public static function generateHash($plainText, $salt = null)
	{
		if ($salt === null)
			$salt = substr(md5(uniqid(rand(), true)), 0, 25);
		else
			$salt = substr($salt, 0, 25);
		return $salt . sha1($salt . $plainText);
	}
	
	###############################################################################
	# Parses an RGBA or Hex String string and returns an array with each color
	# @param  string $str The RGBA string, ex: "rgba(66, 66, 66, 1)"
	# @return array Returns an array with each r, g, b and a value or false 
	# if an invalid string is passed in.
	# https://stackoverflow.com/questions/38778230/php-opacity-multiplication
	# https://stackoverflow.com/questions/15202079/convert-hex-color-to-rgb-values-in-php
	#

	public static function parse_color_code($rgba) 
	{
		$rgba = trim(str_replace(' ', '', $rgba));
		if(stripos($rgba, 'rgba') !== false) {
			$res = sscanf($rgba, "rgba(%d, %d, %d, %f)");
		}
		else if(stripos($rgba, 'rgb') !== false) {
			$res = sscanf($rgba, "rgb(%d, %d, %d)");
			$res[] = 1;
		}
		else if(self::starts_with($rgba, "#")) {
			$hex = str_replace('#', '', $rgba);
			$length = strlen($hex);
			$res[] = hexdec($length == 6 ? substr($hex, 0, 2) : ($length == 3 ? str_repeat(substr($hex, 0, 1), 2) : 0));
			$res[] = hexdec($length == 6 ? substr($hex, 2, 2) : ($length == 3 ? str_repeat(substr($hex, 1, 1), 2) : 0));
			$res[] = hexdec($length == 6 ? substr($hex, 4, 2) : ($length == 3 ? str_repeat(substr($hex, 2, 1), 2) : 0));
			$res[] = 1;
		}
		else
			return false;
		return array_combine(array('r', 'g', 'b', 'a'), $res);
	}
	
	###############################################################################
	
	public static function change_alpha_in_color_code($colorcode, $a=1) 
	{
		$rgba = self::parse_color_code($colorcode);
		if($rgba !== false)
			return "rgba(" . $rgba['r'] . "," . $rgba['g'] . "," . $rgba['b'] . "," . $a . ")";
		else
			return false;
	}
	
	###############################################################################
		
	public static function convert_rgba_to_hex($colorcode) {
		$rgba = self::parse_color_code($colorcode);
		if($rgba !== false) {
			return sprintf("#%02x%02x%02x", $rgba['r'], $rgba['g'], $rgba['b']);
		}
		return false;
	}
	
	###############################################################################
	# https://stackoverflow.com/questions/3512311/how-to-generate-lighter-darker-color-with-php
	#
	
	public static function adjust_color_brightness($hex, $steps) 
	{
		$hex = self::convert_rgba_to_hex($hex);
		
		// Steps should be between -255 and 255. Negative = darker, positive = lighter
		$steps = max(-255, min(255, $steps));

		// Normalize into a six character long hex string
		$hex = str_replace('#', '', $hex);
		if (strlen($hex) == 3) {
			$hex = str_repeat(substr($hex,0,1), 2).str_repeat(substr($hex,1,1), 2).str_repeat(substr($hex,2,1), 2);
		}

		// Split into three parts: R, G and B
		$color_parts = str_split($hex, 2);
		$return = '#';

		foreach ($color_parts as $color) {
			$color   = hexdec($color); // Convert to decimal
			$color   = max(0,min(255,$color + $steps)); // Adjust color
			$return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
		}
		return $return;
	}
	
	###############################################################################
	
	public static function get_color_contrast($colorcode)
	{
		$rgba = self::parse_color_code($colorcode);
		if($rgba !== false) {
			return sqrt(
				$rgba['r'] * $rgba['r'] * .241 +
				$rgba['g'] * $rgba['g'] * .691 +
				$rgba['b'] * $rgba['b'] * .068
			);
		}
		return false;
	}
	
	###############################################################################
	
	public static function invert_color($colorcode)
	{
		$rgba = self::parse_color_code($colorcode);
		if($rgba !== false) {
			$rgba['r'] = 255-$rgba['r'];
			$rgba['g'] = 255-$rgba['g'];
			$rgba['b'] = 255-$rgba['b'];
			$hex = sprintf("#%02x%02x%02x", $rgba['r'], $rgba['g'], $rgba['b']);;
			return self::change_alpha_in_color_code($hex, $rgba['a']);
		}
	}
	
	###############################################################################
	
	public static function get_google_font_list($key, $category="")
	{
		$url = "https://www.googleapis.com/webfonts/v1/webfonts?key=" . $key;
		$result = json_decode(file_get_contents( $url ));
		//self::print($result);
		$font_list = array();
		foreach ( $result->items as $font ) {
			if($category != "") {
				if(strtolower($font->category) != strtolower($category))
					continue;
			}
			$font_list[] = [
				'font_name' => $font->family,
				'category' => $font->category,
				'variants' => implode(', ', $font->variants),
				// subsets
				// version
				// files
			];
		}
		return $font_list;
	}
	
	###############################################################################
	
	public static function get_lock_file($name, $mode=LOCK_EX | LOCK_NB)
	{
		$logpath = self::get_log_folder_path();
		$file = $logpath . "$name.lock";
		if(!is_file($file)) {
			$fp = fopen($file, "w");
			fclose($fp);
			chmod($file, 0777);
		}
		$fp = fopen($file, "r+");
		if($fp !== false) {
			if(!flock($fp, $mode)) {
				fclose($fp);
				$fp = false;
			}
		}
		return $fp;
	}
	
	#########################################################################################

	public static function confirm_action($message)
	{
		if(self::is_cli()) {
			echo "\n{$message}\nAre you sure you want to do this?  Type 'yes' to continue: ";
			$handle = fopen ("php://stdin","r");
			$line = fgets($handle);
			if(trim($line) != 'yes')
			{
				echo "ABORTING!\n";
				exit;
			}
			fclose($handle);
			echo "\n";
		}
		else
			exit;
	}
	
	###############################################################################

	public static function ioncube_loader_version_array () 
	{
		if ( extension_loaded('ionCube Loader') ) {
			// ioncube_loader_iversion is the newer function, use it if it is available.
			if ( function_exists('ioncube_loader_iversion') ) {
				$ioncube_loader_iversion = ioncube_loader_iversion();
				if($ioncube_loader_iversion >= 100000) {
					// MMmmrr
					$ioncube_loader_version_major = (int)substr($ioncube_loader_iversion,0,2);
					$ioncube_loader_version_minor = (int)substr($ioncube_loader_iversion,2,2);
					$ioncube_loader_version_revision = (int)substr($ioncube_loader_iversion,4,2);
					$ioncube_loader_version = "$ioncube_loader_version_major.$ioncube_loader_version_minor.$ioncube_loader_version_revision";
				}
				else {
					// Mmmrr
					$ioncube_loader_version_major = (int)substr($ioncube_loader_iversion,0,1);
					$ioncube_loader_version_minor = (int)substr($ioncube_loader_iversion,1,2);
					$ioncube_loader_version_revision = (int)substr($ioncube_loader_iversion,3,2);
					$ioncube_loader_version = "$ioncube_loader_version_major.$ioncube_loader_version_minor.$ioncube_loader_version_revision";
				}
			} 
			else {
				$ioncube_loader_version = ioncube_loader_version();
				$ioncube_loader_version_major = (int)substr($ioncube_loader_version,0,1);
				$ioncube_loader_version_minor = (int)substr($ioncube_loader_version,2,1);
			}
			return array('version'=>$ioncube_loader_version, 'major'=>$ioncube_loader_version_major, 'minor'=>$ioncube_loader_version_minor);
		}
		return array('version'=>0, 'major'=>0, 'minor'=>0);
	}
	
	###############################################################################
	
	public static function compressCSS($buffer) {
		$buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer);
		// Remove space after colons
		$buffer = str_replace(': ', ':', $buffer);
		// Remove whitespace
		$buffer = str_replace(array("\r\n", "\r", "\n", "\t", '  ', '    ', '    '), '', $buffer);
		return trim($buffer);
	}

	###############################################################################

	public static function outputCSSFile($buffer, $compress=true)
	{
		if($compress)
			$buffer = self::compressCSS($buffer);
		header("Content-type: text/css");
		echo($buffer);
	}

	###############################################################################
	// Sanitize the CSV delimiter

	public static function sanitizeCSVDelimiter($delimiter=",")
	{
		$delimiter = strtolower($delimiter);
		if($delimiter == "semicolon")
			$delimiter = ";";
		else if($delimiter == "comma")
			$delimiter = ",";
		else if($delimiter == "tab" || $delimiter == "t")
			$delimiter = "\t";
		if($delimiter != "," && $delimiter != ";" && $delimiter != "\t")
			$delimiter = ",";
		return $delimiter;
	}

	###############################################################################

	public static function getDOMTitle($htmltext)
	{
		$title = '';
		if($htmltext && class_exists("DomDocument")) {
			$dom = new \DOMDocument();
			libxml_use_internal_errors(true);
			if($dom->loadHTML($htmltext)) {
				$list = $dom->getElementsByTagName("title");
				if ($list->length > 0) {
					$title = $list->item(0)->textContent;
				}
			}
			libxml_clear_errors();
		}
		return $title;
	}
	
	###############################################################################
	
	public static function toUTF8($text) {
		if(preg_match('!!u', $text) === false)
			$text = utf8_encode($text);
		//$text = mb_convert_encoding($text, 'HTML-ENTITIES', "UTF-8");
		$text = htmlentities($text, ENT_QUOTES);
		return $text;
	}
	
	###############################################################################
	
	public static function getChecksum($string) {
		return md5($string);
	}
	
	###############################################################################
	
	public static function getKnownSubdomains($hostname, $apikey=null, $apisecret=null) {
		$hostname = UTIL::idn_convert_to_host_name($hostname, "");
		if(empty($apikey) || empty($apisecret)) {
			$cpurl = "https://dnpedia.com/tlds/subdomains.php?domain=" . $hostname;
			$results = self::curl_get_url($cpurl, 60, false, false, "-");
			if($results['status'] == 200) {
				$json = json_decode($results['result'], true);
				if(isset($json['records']) && intval($json['records']) > 0) {
					return $json['data'];
				}
			}
		}
		else {
			// TODO:
		}
		return [];
	}

	###############################################################################
}
