<?php
/**
* 通用函数库
*/

/**
* 取语言包中的文字
* @param string $str
* @param string $locale 本地化域,为空则取配置 $cfg['locale']
* @todo 可以去对接 php gettext使语言常驻内存，性能最佳，但灵活性受限制，一般用于很少再变化的已上线项目的优化
* @return string
*/
function lg($str,$locale=''){
	//array_key_exists 如果存在键但值为null，返回 true
	//isset 如果存在键但值为null，返回 false
	if($locale=='')
		$locale = \mvc::$cfg['locale'];
	if(!isset(\mvc::$localeData[$locale]) || !array_key_exists('language', \mvc::$localeData[$locale])){
		\mvc::loadLocale($locale,'language');
	}
	if(isset(\mvc::$localeData[$locale]['language'])){
		$key = str_replace(["\r\n","\n"],'',trim($str));
		if(isset(\mvc::$localeData[$locale]['language'][$key])){
			return \mvc::$localeData[$locale]['language'][$key];
		}
	}
	return $str;
	//return gettext($str);
}
function lg2($str,$lang='en'){
    if(!isset(\mvc::$localeData2[$lang])){
        $locale = \mvc::$cfg['locale'];
        $type = 'language';
        $langfile = \mvc::$cfg['dir']['locale'].'/'. $locale .'/'.$type.'/'.$lang.'/field.php';
        $l1 = require($langfile);
        $langfile = \mvc::$cfg['dir']['locale'].'/'. $locale .'/'.$type.'/'.$lang.'/ui.php';
        $l2 = require($langfile);
        $langfile = \mvc::$cfg['dir']['locale'].'/'. $locale .'/'.$type.'/'.$lang.'/msg.php';
        $l3 = require($langfile);
        \mvc::$localeData2[$lang] = array_merge($l3,$l2,$l1);
        unset($l1,$l2,$l3);
    }
	$key = str_replace(["\r\n","\n"],'',trim($str));
	if(isset(\mvc::$localeData2[$lang][$key])){
		return \mvc::$localeData2[$lang][$key];
	}
	return $str;
	//return gettext($str);
}

function slg($str){
    return $str;
}

/**
 * 取当前用户
 * @return string
 */
function getOpuser()
{
	return (!empty($_SESSION['user']['user_id'])) ? $_SESSION['user']['user_id'] : 'SYS';
}

/**
 * 取当前用户的姓名
 * @return string
 */
function getActiveOpuserName()
{
	return (isset($_SESSION['user']['au_name']) && !empty($_SESSION['user']['au_name'])) ? $_SESSION['user']['au_name'] : 'SYS';
}

/**
* 得到当前时间
*
* @return string
*/
function getNowTime(){
	return date(\mvc::$cfg['datetimeformat']);
}

/**
* 得到当前日期
*
* @return string
*/
function getNowDate(){
	return date(\mvc::$cfg['dateformat']);
}

/**
* 显示短日期
*
* @return string
*/
function shortDate($datetime){
	return date(\mvc::$cfg['dateformat'],strtotime($datetime));
}

/**
* 显示格式化后的小数
*
* @param decimal $value
* @return decimal
*/
function decimal($value){
	return sprintf("%.2f",$value);
}

/**
* 取指定尺寸的图片
*
* @return string
*/
function filenameSize($filename,$key){
	$extname = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
	return substr($filename,0,strlen($extname)*-1-1).$key.'.'.$extname; 
}

/**
* 文件扩展名
*
* @return string
*/
function extname($filename){
	return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
}

/**
* 文件名不带扩展
*
* @return string
*/
function basenameFirst($filename){
    $result = basename($filename);
	return str_replace('.'.pathinfo($result, PATHINFO_EXTENSION),'',$result);
}

/**
* 检测变量是否有值, 0算有值
*
* @param mixed $var
* @return bool
*/
function exists($var){
    if(!isset($var)) 
        return false;
    if(is_string($var)) $var = trim($var);
    $t= ($var==='0' || $var===0) ? 1 : $var; 
	return !empty($t);
}

/**
* 使用指定变量格式化字符串
* 
* qgprintf("sum({1}) sum_{2}",$var1,$var2);
* qgprintf("sum({:a}) sum_{:b}",$arr); //$arr['a'=>1,'b'=>2]
* @param mixed $var
* @return string
*/
function qgprintf($rulestr,...$args){
    if(is_array($args[0])){
        foreach($args[0] as $k=>$v){
            $rulestr = str_replace('{:'.$k.'}',$v,$rulestr);
        }
    }else{
        foreach($args as $i=>$var){
            $rulestr = str_replace('{'.($i+1).'}',$var,$rulestr);
        }
    }
    return $rulestr;
}

/**
* 返回安全字符串
*
* @return string
*/
function safeString($str){
	return addslashes(trim($str));
}

/**
* 格式化打印变量,并中断程序执行
*
* 在前端带缩进格式显示,并以JSON格式输出到浏览器控制台
* @param mixed $args 待打印的变量,可变参数，支持多个变量
* @return void
*/
function admindd(...$args)
{
	if (getOpuser() == 'test.admin') {
		call_user_func_array('dump', $args);
		exit();
	}
}
function dd(...$args)
{
	call_user_func_array('dump', $args);
	exit();
}
function ddMini(...$args){
	call_user_func_array('dumpMini',$args);
	exit();
}

//开发者测试
function debug(...$args){
    if(getOpuser()=='test.admin'){
    	call_user_func_array('dd',$args);
    	exit();
    }
}


/**
* 格式化打印变量
*
* 在前端带缩进格式显示,并以JSON格式输出到浏览器控制台
* @param mixed $args 待打印的变量,可变参数，支持多个变量
* @return void
*/
function dump(...$args){
    echo call_user_func_array('dumpResult',$args);
}
function dumpMini(...$args){
    $contents = call_user_func_array('dumpResult',$args);
    echo str_replace("max-height:auto;","max-height:500px;",$contents);
}
function dumpResult(...$args){
	$result = '';
	foreach($args as $index => $v){
		$result .= _dump($v,$index);
	}
	if(MVC_SAPI_IS_CLI){
		$result = preg_replace('/<script>.*<\/script>/s', '', $result);
		$result = preg_replace('/<style>.*<\/style>/s', '', $result);
		$result = strip_tags($result);
		$result = PHP_EOL . trim($result);
	}
	return $result;
}
/**
* 格式化打印变量,在新窗口输出
*
* 在前端带缩进格式显示,并以JSON格式输出到浏览器控制台
* @param mixed $args 待打印的变量,可变参数，支持多个变量
* @return void
*/
function dumpOpen(...$args){
	$result = '';
	foreach($args as $index => $v){
		$result .= _dump($v,$index);
	}
    $id = guid();
    $uri = url('dd');
    echo '
        <form id="{$id}" method="post" action="{$uri}" target="_blank" style="display:none">
            <textarea name="dd">{$result}</textarea>
        </form>
        <script>
        var form = document.getElementById("{$id}");
        form.submit();
        </script>
    ';
}

/**
* @ignore
*/
function _dump($variable,$index){
	$json = json_encode($variable);
	$backtrace = debug_backtrace();
    $i=1;
	$caller = $backtrace[$i];
    while(strpos($caller['file'],'function.php')>0){
        $i++;
        $caller = $backtrace[$i];
    }
	$fileArr = file($caller['file']);
	$code = trim($fileArr[$caller['line']-1]);
	$t = explode(',',$code);
    if(isset($t[$index])) $t[$index] = preg_replace('/(\$\w+)/',"<span class='dd-call-active'>$1</span>",$t[$index]);
    $code = implode(',',$t);
	unset($fileArr);
	$result = trim("<script>console.log($json);</script>
			<style>
				.dd pre{line-height: 1.5em;font-size:16px;font-family: monaco, Consolas, Menlo, Courier, monospace;padding:0;margin:0}
				.dd pre .dd-blue{color:blue}
				.dd pre .dd-to{color:#ccc}
				.dd pre .dd-num{color:#F00}
				.dd pre .dd-string{color:#690}
				.dd pre .dd-array{color:#990}
				.dd pre .dd-object{color:#C0C}
				.dd pre .dd-object-val{color:#000;font-size:11px;line-height:15px}
				.dd pre .dd-stdClass{color:#C00;font-weight:bold}
				.dd pre .dd-file{color:#ccc}
				.dd pre .dd-call{color:#609;font-weight:bold}
				.dd pre .dd-call-active{color:#f00}
				.dd pre .dd-code{color:#000;font-size:10px}
				.dd pre .dd-area-1{color:#f00;font-weight:bold}
				.dd pre .dd-area-2{color:#090;font-weight:bold}
				.dd pre .dd-area-3{color:#00f;font-weight:bold}
				.dd pre .dd-area-4{color:#F0F;font-weight:bold}
			</style>
			<div class='dd' style='background-color:#fffffe;
				color:#000;
				border: 1px #ddd solid;
				margin:10px 0;
				padding:0 10px;'>
			<pre style='max-height:auto;overflow:auto'>
	");
	$result .= '<span class="dd-call">'.$code.'</span>';
	$result .= '<span class="dd-file">'.$caller['file'].'('.$caller['line'].')</span>'.PHP_EOL;
	$result .= _print_r($variable);
	$result .= PHP_EOL .'</pre></div>' .PHP_EOL;
	return $result;
}
/**
* 自定义对象格式化输出 print_r
* @param mixed $variable 待打印的变量
* @return string
* @ignore
*/
function _print_r($variable,$tab=1){
	if(is_object($variable) && !($variable instanceOf Closure)){
		$arr = json_decode(json_encode($variable),true);
		if(!empty($arr)){
			$variable = $arr;
		}
	}
	if(!is_array($variable)){
		return _echoValueByType($variable);
	}
	$result = '';
	$result .= '<span class="dd-area-'.$tab.'">[</span>'.PHP_EOL;
	$reg=0;
	foreach($variable as $k=>$v){
		for($i=0;$i<$tab;$i++){
			$result .= "    ";
		}
		if(is_array($v)){
			$result .= '"<span class="dd-array">'.$k.'</span>" => ';
			$result .= _print_r($v,$tab+1);
		}else if(is_object($v)){
			$result .= '"<span class="dd-object">'.$k.'</span>" => ';
			$result .= _print_r($v,$tab+1);
		}else{
			$result .= '"'.$k.'" <span class="dd-to">=></span> ';
			$result .= _echoValueByType($v);
		}
		if($reg!=count($variable)-1) $result .= ',';
		$result .= PHP_EOL;
		$reg++;
	}
	for($i=0;$i<$tab-1;$i++){
		$result .= "    ";
	}
	$result .= '<span class="dd-area-'.$tab.'">]</span>';
	return $result;
}
/**
* 按值的类型输出对应的颜色
* @param mixed $value
* @return string
* @ignore
*/
function _echoValueByType($value){
	$type = gettype($value);
	if(is_null($value)){
		$cssClass = "dd-blue";
		$value = 'null';
	}else if(is_bool($value)){
		$cssClass = "dd-blue";
		$value = $value ? 'true' : 'false';
	}else if(is_float($value) || is_integer($value)){
		$cssClass = "dd-num";
	}else if(is_string($value) && substr($value,0,10)=='{stdClass:'){
		$cssClass = "dd-stdclass";
	}else if(is_string($value) && in_array(strtoupper(explode(' ',$value)[0]),['SELECT','INSERT','UPDATE','DELETE'])){
		$cssClass = "";
		if (class_exists('SqlFormatter', false)) {
			$value = \SqlFormatter::format($value);
		}
	}else if(is_string($value)){
		$cssClass = "dd-string";
		$value = '"'.htmlspecialchars($value).'" ('.strlen($value).')';
	}else if($value instanceOf Closure){
		$cssClass = "dd-code";
		$value = phpCodeColor(closureToString($value));
	}else if(is_object($value)){
		$cssClass = "dd-object-val";
		$json = json_encode($value);
		if($json=='{}'){
            try{
                $value = print_r($value,true);
            }catch(\Throwable | \Exception $e){
                dd(time());
                $cssClass = "dd-stdClass";
                $class = new ReflectionClass($value);
                $value = "new {$class->name}()";
            }
		}else{
			$value = $json;
		}
	}else{
        $cssClass = '';
    }
	return '<span class="'.$cssClass.'">'.$value.'</span>';
}

/**
* 反射创建对象
* @param string $className 类名
* @param array $params 参数,可带参数名称
* @return object
* @ignore
*/
function _createObject($className,$params){
	$class = new ReflectionClass($className);
	$constructor = $class->getConstructor();
	$args = [];
	if($constructor){
		foreach($constructor->getParameters() as $k => $v){
			if(isset($params[$v->name])){
				$args[$k] = $params[$v->name];
			}elseif(isset($params[$k])){
				$args[$k] = $params[$k];
			}elseif($v->isDefaultValueAvailable()){
				$args[$k] = $v->getDefaultValue();
			}else{
				throw new \Exception ($className . '::__construct'. ' ' .  lg('参数') .' $'.$v->name . ' ' .lg('没有传值') );
			}
		}
	}
	$obj = $class->newInstanceArgs($args);
	return $obj;
};

/**
* Raw object
* @param string $string 
* @return Medoo Map
*/
function raw($string,$map=[]){
    return \three\Medoo::raw($string,$map);
}

/**
* 某个值是否存在异或集合值中
* @param int $search 
* @param int $handle
* @return bool
*/
function hasXor($search,$handle){
    if(empty($search) || empty($handle))
        return false;
    else{
        $search *=1;
        $handle *=1;
        return ($search ^ $handle) < $handle;
    }
}
/**
* 添加新值到异或集合值中
* @param int|array $new 
* @param int $handle
* @return int
*/
function addXor($new,$handle){
	$newArr = is_array($new)? $new: [$new];
	$result = (int)$handle;
	foreach($newArr as $val)
	{
		$arr = explodeXor($val);
		foreach($arr as $v){
			if(!hasXor($v,$handle)){
				$result = $v + $result;
			}
		}
	}
    return $result;
}
/**
* 从异或集合值中删除值
* @param int|array $del 
* @param int $handle
* @return int
*/
function deleteXor($del,$handle){
	$delArr = is_array($del)? $del: [$del];
    $result = (int)$handle;
	foreach($delArr as $val)
	{
		$arr = explodeXor($val);
		foreach($arr as $v){
			if(hasXor($v,$handle)){
				$result = $result - $v;
			}
		}
	}
    return $result;
}

/**
* 拆分异或集合值
* @param int $handle
* @return array
*/
function explodeXor($handle){
    $handle = (int)$handle;
    $xor = 1;
    for($i=0;$i<26;$i++){
        $values[$i] = $xor;
        $xor *= 2;
    }
    $data = [];
    foreach($values as $v){
        if(hasXor($v,$handle)){
            $data[] = $v;
        }
    }
    return $data;
}

/**
* 异或值合并
* @param int $value1
* @param int $value2
* @return int
*/
function mergeXor($value1,$value2){
    $result = 0;
    if(!is_numeric($value1) || !is_numeric($value1))
        return  $result;
    $a1 = explodeXor($value1*1);
    $a2 = explodeXor($value2*1);
    $tmp = [];
    foreach($a1 as $v) $tmp[$v] = 1;
    foreach($a2 as $v) $tmp[$v] = 1;
    foreach($tmp as $k=>$v) $result += $k;
    return $result;
}

/**
* 合并两个 Ids
* @param string $value1 例 1,2,3
* @param string $value2 例 2,4,6
* @return string 返回 1,2,3,4,6
*/
function mergeIds($ids1,$ids2){
    return trim(implode(',',array_unique(array_merge(explode(',',$ids1),explode(',',$ids2)))),',');
}

/**
* 创建或获取一个对象
* @param string $className 类名
* @param array $params 带名称参数
* @return object
*/
function make($className,...$params){
	$object = null;
	if(substr($className,0,1)!='\\') $className = '\\'.$className;
	if(isset(\mvc::$container[$className])){
		$container = \mvc::$container[$className];
		if($container['single']==true && $container['object']!=null){
			if(!empty($params)){
				//单例模式下不能再传递新的参数创建对象
				throw new \Exception ( $abstract . ' '. lg('单例模式下不能再次传递新的参数值创建容器对象') );
			}
			$object = \mvc::$container[$className]['object'];
		}else{
			$object = $container['create']($params);
			if($container['single']==true){
				\mvc::$container[$className]['object'] = $object;
			}
		}
	}else{
		$object = _createObject($className,$params);
	}
	return $object;
}


/**
* 创建或获取一个单例对象
* @param string $className 类名
* @param array $params 带名称参数
* @return object
*/
function makeOne($className,...$params){ //可变传参
	\mvc::createClosure($className,$params,true);
	return make($className,...$params);
}
function makeOneArr($className,$params){ //数组传参
	\mvc::createClosure($className,$params,true);
	return make($className,...$params);
}
/**
* 手动绑定一个类到容器
* @param string $className 类名
* @param array $params 参数,和config/providers.php 中的 params 内容一样
* @return void
* @example ```binding(\lib\file::class,[]);"```
* @example ```php
*            带依赖关系绑定
*            binding(\model\test\man::class,[
*               [
*               'class'=>\model\test\car::class,
*               'single'=>false,
*               'params' => []
*               ],
*               '30W'
*            ]);
*/
function binding($className,$params=[]){
	\mvc::createClosure($className,$params,false);
}
/**
* 手动绑定一个类到容器,单例模式
* @param string $className 类名
* @param array $params 参数,和config/providers.php 中的 params 内容一样
* @return void
* @example 参见 ```binding()```
*/
function single($className,$params=[]){
	\mvc::createClosure($className,$params,true);
}
/**
* 执行指定配置的事件
*
* 会自动增加一个当前类的当前方法名为同步事件名,如\model\test\part.delete, 如果存在则执行，不存在就放弃
* @param ... $enents 事件名称,可多个
* @return void
*/
function event(...$enents){
	$arr = debug_backtrace();
	$call = $arr[1];
	$actionMethodTag = "___activeMethod";
	if(!empty($call['object']->{$actionMethodTag})){
		throw new \Exception ( lg('一个方法只能调用一次event函数,你可以将多个事件写在一个方法里') .':  '.$enentName );
	}
	$call['object']->{$actionMethodTag} = $call['function'];
	if($enents){
		foreach($enents as $k=>$v){
			$enents[$k] = [ $v, true]; //设置检测标记
		}
	}
	//自动添加一个默认事件名 当前类.当前方法
	$class = get_class($call['object']);
	if(substr($class,0,1)!='\\') $class = '\\'.$class;
	$enents[] = [ $class.'.'.$call['function'], false ];
	foreach($enents as $item){
		\mvc::notifyEvent($call['object'], $item[0],$item[1]);
	}
	\mvc::$answerList = []; //公共应答参数初始化
}
/**
* 将action和参数转成url
*
* @param string|object $action
* @param string $type k|v
* @return string
*/
function url($action,$params,$type='k'){
	if(is_object($action)) $action = trim(get_class($action),'/');
	if($type=='k'){
		$arr = [];
		foreach($params as $k => $v){
			if(is_object($v) || is_array($v)){
				throw new \Exception ( lg('参数不能是一个对象或是数组') );
			}
			$arr[] = $k.'/'.urlencode($v);
		}
		return $action.'/k/'.implode('/',$arr);
	}else if($type=='v'){
		$arr = [];
		foreach($params as $k => $v){
			if(is_object($v) || is_array($v)){
				throw new \Exception ( lg('参数不能是一个对象或是数组') );
			}
			$arr[] = urlencode($v);
		}
		return $action.'/v/'.implode('/',$arr);
	}
}

/**
 * @param $data 需要处理的字符串
 * @return string
 */
function escape($data)
{
    if(!is_string($data)) $data = '';
    $trimHtmlTag = 'script|i?frame|style|html|body|title|link|meta';
    $findArr = array(
        sprintf("/<(%s)([^>]*?)>/isU", $trimHtmlTag),
        sprintf("/<\/(%s)>/isU", $trimHtmlTag)
    );
    $toArr = array('&lt;$1$2&gt;', '&lt;/$1&gt;');
    $data = preg_replace($findArr, $toArr,$data);
    return trim(strtr($data, array("'"=>"\'")));
}


/**
* 调用一个类的方法
*
* @param string $namespace_class_method 命名空间/类/方法
* @param array $params
* @return mixed
* @example ``` call("admin/action/page/upload",["filename"=>"e:/www/test.txt"]) ```
*/
function call($namespace_class_method,$params=[]){

	$action = str_replace("\\","/",$namespace_class_method);

	$action = trim($action,'/');
	$arr = explode('/',$action);
	$method = array_pop($arr);
	$action = "\\".implode("\\",$arr);
	if($arr[1]=='action') $action .='Action';
	$obj = make($action);
	return call_user_func_array ( array ( $obj , $method ), $params );
}


/**
* 递归数组
*
* @param array $arr 数组
* @param function $function 回调函数 function($k,$v)
* @param var $gvar 局部全局变量传递
* @return mixed
* @example ``` call("admin/action/page/upload",["filename"=>"e:/www/test.txt"]) ```
*/
function eachArray(&$arr,$function,$keys='',$gvar=null){
    if($keys!='') $keys.=',';
    foreach($arr as $k=>&$v){
        if(is_array($v)){
            $keys.= $k;
            eachArray($v,$function,$keys,$gvar);
        }else{
            $function($arr, $k,$v,$keys.$k,$gvar);
        }
    }
}
/**
* 取将用 , 号连接的多维数组字符串键或者是键数组
*
* @param string|arr $keys 用 , 号连接字符串键 如 key1.subkey1 或者 ['key1','subkey1']
* @return &var 指定键的值
* @example ``` call("admin/action/page/upload",["filename"=>"e:/www/test.txt"]) ```
*/
function getKeysArray(&$arr,$keys){
    if(!is_array($keys)) $keys = explode(',',$keys);
    function eachItem(&$arr,$key){

    }
    $value = $arr;
    foreach($keys as $key){
       $value = $value[$key];
    }
    return $value;
}
/**
* 将字符串转成不可逆密文
*
* md5的加强版
*
* 自定义盐 \mvc::$cfg['safe']['passwordSalt'] 即使为空系统也会加内置盐
*
* 自定义盐为空或不存在盐配置时
*
* 1234456 = 0f903b04ebcd241544d83ba497ad1ee6
*
* Pass.123456 = cdb2fe5bf2f12844a5d6e95d9b5ea1b6
* @param string $str
* @return string 32位小写字符串
*/
function password($str){
	//2020/4/18
	if(empty($str) || strlen($str)<6){
		throw new \Exception ( lg('密码类转密文至少要求提供6个字符') );
	}
	$salt = '$7%8C3f&Y0h:Ma';
	if(isset(\mvc::$cfg['safe']) && isset(\mvc::$cfg['safe']['passwordSalt'])) $salt .= \mvc::$cfg['safe']['passwordSalt'];
	$result = strtolower(md5(substr(md5($str[0].$str[5].'Q.G.M.V.C'),3,21).md5($str[1].$str.$str[3]).md5($salt.$str[2])));
	$result =  strrev(substr($result,24,8).substr($result,8,8).substr($result,0,8).substr($result,16,8));
	$i1 = (ord($str[0]) + ord($str[5])) % 32;
	$i2 = ($i1 + ord($str[3])) % 32;
	if($i1==$i2) $i2 = ($i2 + 5) % 32;
	$c = $result[$i1]; $result[$i1] = $result[$i2]; $result[$i2] = $c;
	return strrev(md5(substr(md5($salt.'q-g-m-v-c'),7,27).sha1($result).md5('q;g;m;v;c'.$salt)));
}
/**
* 生成唯一ID,字符串, 最长11位字符
*
* 返回小写10-11位长度可排序唯一ID，如需要存数据库可以使用 char(11)
* @param string $isSerial 是否生成可排序的唯一ID，默认false
* @return string
*/
function guid(){
	$prefix = base_convert(getUnixTimestamp(),10,36);
	list($usec, $sec) = explode(" ", microtime());
	$guid = uniqid(rand(0,PHP_INT_MAX),true)
				. $sec . $usec
				. serialize($_SERVER)
				. serialize(\mvc::$req)
				. mt_rand(0,PHP_INT_MAX)
				. random_int(0,PHP_INT_MAX);
	$arr = shortStrUid($guid,1);
	return $prefix.$arr[1].$arr[2];
}
/**
* 生成唯一ID,整数, 最大17位数字
*
* 返回最大17位数字，如需要存数据库，可以使用 bigint 来存储
* @return int
*/
function guidInteger(){
	return base_convert(guid(),36,10) * 1;
}
/**
* 获取毫秒级时间戳 13位整数
*
* @return int
*/
function getUnixTimestamp(){
    list($s1, $s2) = explode(' ', microtime());
    return (float)sprintf('%.0f',(floatval($s1) + floatval($s2)) * 1000);
}
/**
* 将一个字条串生成一个短标识，类似短网址
*
* 返回4组结果,可任意取一组
* 如果要给数据库记录做短标记，可用这4组去判断有没有重复
* @return array
*/
function shortStrUid($str, $length=6) {
	$base32 = array (
			'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
			'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
			'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
			'y', 'z', '0', '1', '2', '3', '4', '5'
	);
	$hex = md5($str);
	$subHexLen = 4; // 划分为4段，保留4组结果
	$resultArray = [];
	for ($i = 0; $i < $subHexLen; $i++) {
		$subHex = substr($hex, $i * 8, 8);    // 分成4段，每段取8位字符
		$int = 0x3FFFFFFF & hexdec($subHex);  // 8位字节与16进制取与操作
		$out = '';
		for ($j = 0; $j < $length; $j++) {
			$val = $int % 32;   //取0~31之间的整数
			$out .= $base32[$val]; //从数组中获取对应字符
			$int = $int >> (30/$length);
		}
		$resultArray[] = $out;
	}
	return $resultArray;
}
/**
* md4 md5中间的16位
* @return array
*/
function md4($str) {
	return substr(md5($str),8,16);
}

/**
 * 将网址 ？ 后的内容转成数组
 *
 * @param string $url 网址
 * @return array
 */
function urlQuery2Array($url){
	$arr = parse_url($url);
	$query = $arr['query'];
	$arr = explode('&', $query);
	$result = [];
	foreach ($arr as $k => $v) {
		$arr = explode('=', $v);
		$result[$arr[0]] = $arr[1];
	}
	return $result;
}

/**
 * 写错误日志
 *
 * 日志将保存到 config/站点.php['dir']['log'] 配置的目录下的 error下，
 * 日志文件按日分隔
 *
 * @param string $msg 错误内容文本
 * @param string $type 类型,如 program 或 database
 * @return void
 */
function errorLog($msg,$type='program'){
	$msg = json_encode($msg,JSON_UNESCAPED_UNICODE);
	$message = sprintf("begin:[%s]\r\ntype:%s\r\nmsg:%s\r\n:end.\r\n", date("Y-m-d H:i:s"), $type, $msg);
	$logPath = \mvc::$cfg['dir']['log']."/error";
	\helper\file::mkdir($logPath);
	error_log($message , 3, $logPath."/".date ( "Y-m-d" ).'_'.$type.".log" );
}

/**
 * 将PHP数组按PHP代码格式写入文件
 *
 * @param string  $filename
 * @param array  $arr
 * @return void
 */
function arrayToCodeFile($filename,$arr)
{
	file_put_contents($filename, '<?php'.PHP_EOL.'return '.$this->varexport($arr).';');
}
/**
 * 将PHP数组转成PHP代码格式字符串
 *
 * @param array  $arr
 * @return string
 * @ignore
 */
function varexport($arr) {
	$export = var_export($arr, TRUE);
	$export = preg_replace("/^([ ]*)(.*)/m", '$1$1$2', $export);
	$array = preg_split("/\r\n|\n|\r/", $export);
	$array = preg_replace(["/\s*array\s\($/", "/\)(,)?$/", "/\s=>\s$/"], [NULL, ']$1', ' => ['], $array);
	$export = join(PHP_EOL, array_filter(["["] + $array));
	return $export;
}

/**
* 匿名函数转字符串
* @param object $closureObject
* @return string 匿名函数代码
*/
function closureToString($closureObject){
	$reflection = new ReflectionFunction($closureObject);
	$file = new SplFileObject($reflection->getFileName());
	$file->seek($reflection->getStartLine()-1);
	$code = '';
	while ($file->key() < $reflection->getEndLine()){
		$code .= $file->current();
		$file->next();
	}
	$begin = strpos($code, 'function');
	$end = strrpos($code, '}');
	$code = substr($code, $begin, $end - $begin + 1);
	return $code;
}

/**
* php代码变色
* @param string $code_string 代码行
* @return string 变色后的字符串
*/
function phpCodeColor($code_string){
	$code_string = trim($code_string);
	$code_string = preg_replace('/(\/\*[\s\S]*\*\/)/U', '<span style="color:green">$1</span>', $code_string); //注释变色
	$code_string = preg_replace('/\/\/(.*)?\n/', '<span style="color:green">//$1</span>', $code_string); //注释变色
	$code_string = preg_replace("/(\'([^\']+)?\')/s", '<span style="color:#FF00FF">$1</span>', $code_string); //单引号
	$code_string = preg_replace('/(\$[\w]+)/', '<span style="color:#008080">$1</span>', $code_string); //变量变色
	$code_string = preg_replace('/([{|}|(|)])/', '<span style="font-weight:bold;color:#800;padding-right:2px">$1</span>', $code_string); //{}()加粗
	$code_string = preg_replace('/(\\\\\w+)/', '<span style="color:#C00">$1</span>', $code_string); //命名空间引用
	//关键字
	$keywords = [
		'new','function','private','public','static','as','namespace','use','class','extends',
		'foreach','for','while','if','else','namespace','return','echo',
		'require','require_once','include','include_once','isset','empty','is_file','method_exists'
	];
	foreach($keywords as $key){
		$partern = '/\b'.$key.'\b[\s]*/i';
		$code_string = preg_replace($partern,'<span style="color:blue">'.$key.' </span>',$code_string);
	}
	//函数
	$functions = [
		'ReflectionClass','print_r','var_dump','dump','dd',
	];
	foreach($functions as $func){
		$partern = '/\b'.$func.'\b[\s]*/i';
		$code_string = preg_replace($partern,'<span style="color:red">'.$func.' </span>',$code_string);
	}
	return $code_string;
}

function formatNum( $string, $b = '')
{
    $strmat = getSemiangle($string);
    $strTemp = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". $b;
    for ($i=0; $i<strlen($string); $i++)
    {
        $c = substr($string,$i,1);
        $pos = strpos($strTemp, $c);
        if ($pos === false) {
            $strmat = str_replace($c,"", $strmat);
        }
    }
    return strtoupper($strmat);
}

/**
 * @name 全角转半角
 * @param  $str       string 待转换的字符串
 * @author 强哥
 * @copyright 2013-06-18
 * @return string
 */
function getSemiangle($str)
{
    $arr = array('０' => '0', '１' => '1', '２' => '2', '３' => '3', '４' => '4','５' => '5', '６' => '6', '７' => '7', '８' => '8', '９' => '9',
        'Ａ' => 'A', 'Ｂ' => 'B', 'Ｃ' => 'C', 'Ｄ' => 'D', 'Ｅ' => 'E','Ｆ' => 'F', 'Ｇ' => 'G', 'Ｈ' => 'H', 'Ｉ' => 'I', 'Ｊ' => 'J', 'Ｋ' => 'K',
        'Ｌ' => 'L', 'Ｍ' => 'M', 'Ｎ' => 'N', 'Ｏ' => 'O','Ｐ' => 'P', 'Ｑ' => 'Q', 'Ｒ' => 'R', 'Ｓ' => 'S', 'Ｔ' => 'T','Ｕ' => 'U', 'Ｖ' => 'V',
        'Ｗ' => 'W', 'Ｘ' => 'X', 'Ｙ' => 'Y','Ｚ' => 'Z', 'ａ' => 'a', 'ｂ' => 'b', 'ｃ' => 'c', 'ｄ' => 'd','ｅ' => 'e', 'ｆ' => 'f', 'ｇ' => 'g',
        'ｈ' => 'h', 'ｉ' => 'i','ｊ' => 'j', 'ｋ' => 'k', 'ｌ' => 'l', 'ｍ' => 'm', 'ｎ' => 'n','ｏ' => 'o', 'ｐ' => 'p', 'ｑ' => 'q', 'ｒ' => 'r',
        'ｓ' => 's', 'ｔ' => 't', 'ｕ' => 'nu', 'ｖ' => 'v', 'ｗ' => 'w', 'ｘ' => 'x', 'ｙ' => 'y', 'ｚ' => 'z', '～'=>'~', '·'=>'`', '！'=>'!',
        '＠'=>'@', '＃'=>'#', '￥'=>'$', '％'=>'%', '……'=>'^', '＆'=>'&', '×'=>'*', '（'=>'(', '）'=>')', '——'=>'_', '－'=>'-', '＋'=>'+', '＝'=>'=',
        '｛'=>'{', '｝'=>'}', '【'=>'[', '】'=>']', '｜'=>'|', '＼'=>'\\', '：'=>':', '；'=>';', '”'=>'"', '’'=>'\'', '《'=>'<', '，'=>',', '》'=>'>',
        '。'=>'.', '？'=>'?', '、'=>'/');
    return strtr($str, $arr);
}

if(!function_exists( 'exif_imagetype')){
    function exif_imagetype ( $filename ){
        if ( ( list($width, $height, $type, $attr) = getimagesize( $filename ) ) !== false ) {
            return $type;
        }
        return false;
    }
}

function exitif(){
  if(\mvc::$exitif){
	exit();
  }
}

/**
  * 错误时，输出格式化后的 json 并终止程序
  * @param int $code  错误代码
  * @param string $msg 错误信息
  * @return  void
  */
function exitJson($code, $msg=''){
    if(is_string($code) && $msg=='') $msg = $code;
    $text = ($msg=='') ? $code . \mvc::$errorCode[$code] : $msg;
    if(!DEBUG) ob_clean ();
    header('Content-Type','application/json');
    $result['code'] = 0;
    $result['msg'] = $text;
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    if(function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    }
    exitif();
}

/**
  * 输出格式化后的 json 并终止程序
  * @param int $code  错误代码
  * @param string $msg 错误信息
  * @return  void
  */
function msgJson($code, $msg=''){
    if(is_string($code) && $msg=='') $msg = $code;
    $text = ($msg=='') ? $code . \mvc::$errorCode[$code] : $msg;
    if(!DEBUG) ob_clean ();
    header('Content-Type','application/json');
    $result['code'] = 1;
    $result['msg'] = $text;
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    if(function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    }
    exitif();
}

/**
  * 递归创建目录
  * @param string $dir 要创建的目录
  * @param int $mode 权限，默认 0777
  * @return  bool
  */
function mkdirs($dir, $mode = 0777) {
	if( is_null($dir) || $dir === "" ){
		return false;
	}
	if( is_dir($dir) || $dir === "/" ){
        @chmod($dir,$mode);
		return true;
	}
	if( mkdirs(dirname($dir), $mode) ){
        $r = mkdir($dir, $mode);
        if($r){
            @chmod($dir,$mode);
        }
		return $r;
	}
	return false;
}

/**
  * 递归删除目录
  * @param string $dir 要删除的目录
  * @return  bool
  */
function rmdirs($dir) {
    $dh=opendir($dir);
    while ($file=readdir($dh)) {
        if($file!="." && $file!="..") {
            $fullpath=$dir."/".$file;
            if(!is_dir($fullpath)) {
                unlink($fullpath);
            } else {
                rmdirs($fullpath);
            }
        }
    }
    closedir($dh);
    return rmdir($dir);
}

/**
  * 发送 webscoket 命令
  * @param string|array $to 要发送的用户ID数组, 如果为字符串 ALL 则广播所有人
  * @param string $script javascript 脚本,前端将使用 eval($script) 执行
  * @param string $type "call":eval, "msg":layer.msg
  * @return  void
  */
function socketSendCall($to, $script){
	$data = [
		'cmd'=>'SEND',
		'value'=>[
			'to'=> $to,
			'data'=> [
				"type"=> 'call',
				"data"=> $script
			]
		]
	];
	if(is_null(\mvc::$websocketClient)){
		\mvc::$websocketClient = new \WebSocket\Client(\mvc::$cfg['websocketLocalServer']);
	}
	\mvc::$websocketClient->text(json_encode($data));
}
function socketSendMsg($to, $arr,$type='msg'){
    $send = [
        'amsg_title'=>'',
        'amsg_body'=>'',
        'amsg_url'=>'',
        'urltitle'=>'',
    ];
    $send = array_merge($send,$arr);
	$data = [
		'cmd'=>'SEND',
		'value'=>[
			'to'=> $to,
			'data'=> [
				"type"=> $type,
				"data"=> ['title'=>$send['amsg_title'],'body'=>$send['amsg_body'],'url'=>$send['amsg_url'],'urltitle'=>$send['amsg_urltitle']]
			]
		]
	];
	if(is_null(\mvc::$websocketClient)){
		\mvc::$websocketClient = new \WebSocket\Client(\mvc::$cfg['websocketLocalServer']);
	}
	\mvc::$websocketClient->text(json_encode($data));
}

/**
  * 发送 邮件
  * @param string|array $to 要发送的用户ID数组, 如果为字符串 ALL 则广播所有人
  * @param array $data 信息内容
  * @return  void
  */
function mailSend($to, $data){
	return true;
}


/**
  * 发送 短信
  * @param string|array $to 要发送的用户ID数组, 如果为字符串 ALL 则广播所有人
  * @param array $data 信息内容
  * @return  void
  */
function mailSms($to, $data){
	return true;
}


/**
  * @method 发送
  * @http  GET|POST
  * @param  $url                string [必填]
  * @param  $get_arr            array  [可选]
  * @param  $post_arr           array  [可选]
  * @param  $header             array  [可选]
  * @return array
  */
function _iohtml($url, $get_arr=[], $post_arr=[],$header=[],$timeout=120)
{
	if(!empty($get_arr)){
		$url = $url.'?'.http_build_query($get_arr);
	}
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
	if(!empty($header)){
		curl_setopt($ch, CURLOPT_HTTPHEADER, $header);//设置请求头
	}
	if(!empty($post_arr)){
		curl_setopt($ch, CURLOPT_POST, true);
		curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_arr));
	}
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	$ssl = substr($url, 0, 8) == "https://" ? true : false;
	if($ssl){
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
	}
	$html = curl_exec($ch);
	curl_close($ch);
	return $html;
}

/**
  * @method 发送
  * @http  GET
  * @param  $url                string [必填]
  * @param  $get_arr            array  [可选]
  * @return array
  */
function ioGet($url, $get_arr=[],$header=[],$timeout=120){
	return _iohtml($url,$get_arr,null,$header,$timeout);
}
function ioGetJson($url, $get_arr=[],$header=[],$timeout=120){
    $html = _iohtml($url,$get_arr,null,$header,$timeout);
    if($html && substr($html,0,1)=='{'){
	    return json_decode($html,true);
    }else{
        return [];
    }
}

/**
  * @method 发送
  * @http  POST
  * @param  $url                string [必填]
  * @param  $post_arr           array  [可选]
  * @return array
  */
function ioPost($url, $post_arr=[],$header=[],$timeout=120){
	return _iohtml($url,null,$post_arr,$header,$timeout);
}
function ioPostJson($url, $post_arr=[],$header=[],$timeout=120){
	return json_decode(_iohtml($url,null,$post_arr,$header,$timeout),true);
}


/**
  * @method ID集分批执行函数
  * @param  $ids                string|array
  * @param  $callback           function(array $idArr)
  * @param  $step               步长
  * @expmple
	    chunkIds($ids,function($idArr)use($codeRule){
		    $codeRule->codeAreaNumberCheck2(implode(',', $idArr));
	    });
  * @return array
  */
function chunkIds($ids,$callback,$step=100){
    if(is_string($ids)) $ids = explode(',',$ids);
    $arr = array_chunk($ids,$step);
    foreach($arr as $v){
        $callback($v);
    }
}


/**
 * 加密函数
 * @param $txt
 * @param string $key
 * @return string
 */
function lock($txt,$key='123456')
{
	$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=+";
	$nh = rand(0,63);
	$ch = $chars[$nh];
	$mdKey = md5($key.$ch);
	$mdKey = substr($mdKey,$nh%8, $nh%8+7);
	$txt = base64_encode($txt);
	$tmp = '';
	$i=0;$j=0;$k = 0;
	for ($i=0; $i<strlen($txt); $i++) {
		$k = $k == strlen($mdKey) ? 0 : $k;
		$j = ($nh+strpos($chars,$txt[$i])+ord($mdKey[$k++]))%64;
		$tmp .= $chars[$j];
	}
	return urlencode($ch.$tmp);
}

/**
 * 解密函数
 * @param $txt
 * @param string $key
 * @return string
 */
function unlock($txt,$key='123456')
{
	$txt = urldecode($txt);
	$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=+";
	$ch = $txt[0];
	$nh = strpos($chars,$ch);
	$mdKey = md5($key.$ch);
	$mdKey = substr($mdKey,$nh%8, $nh%8+7);
	$txt = substr($txt,1);
	$tmp = '';
	$i=0;$j=0; $k = 0;
	for ($i=0; $i<strlen($txt); $i++) {
		$k = $k == strlen($mdKey) ? 0 : $k;
		$j = strpos($chars,$txt[$i])-$nh - ord($mdKey[$k++]);
		while ($j<0) $j+=64;
		$tmp .= $chars[$j];
	}
	return base64_decode($tmp);
}

/**
     * 字符串截取，支持中文和其他编码
     * @static
     * @access public
     * @param string $str 需要转换的字符串
     * @param string $start 开始位置
     * @param string $length 截取长度
     * @param string $charset 编码格式
     * @param string $suffix 截断显示字符
     * @return string
     */
function msubstr($str, $start=0, $length, $charset="utf-8", $suffix=true) {
	if(function_exists("mb_substr"))
		$slice = mb_substr($str, $start, $length, $charset);
	elseif(function_exists('iconv_substr')) {
		$slice = iconv_substr($str,$start,$length,$charset);
	}else{
		$re['utf-8']   = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
		$re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
		$re['gbk']    = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
		$re['big5']   = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
		preg_match_all($re[$charset], $str, $match);
		$slice = join("",array_slice($match[0], $start, $length));
	}
	return $suffix && $str != $slice? $slice.'...' : $slice;
}