<?php
/* =================================================================================
 * License: GPL-2.0 license
 * Author: 众产®  https://ciy.cn/code
 * Version: 0.7.9
====================================================================================*/
/*
 * common.php 常用公共函数库
 * 
 * 功能函数相关
 * clog/var_dump                    PHP调试变量界面打印
 * fixmonth/todate                  日期月份增减函数/数字时间转字符串
 * locinzone                        当前经纬度是否在围栏中
 * timems                           获取当前微秒数
 * totimepoint                      数字时钟转字符串
 * ismobile                         判断手机号是否合法
 * isweixin                         判断客户端是否在微信中
 * idcard                           判断身份证号合法
 * iduscc                           判断统一社会信用代码合法
 * locdistance                      计算两经纬度之间的距离(毫米METRE)
 * 
 * 加解密相关
 * encrypt                          字符串加解密
 * enid/deid                        ID数字加解密
 * conv33_10/conv10_33              数字转33进制
 * 
 * 字符串/数组相关
 * arrayrand                        随机抽取数组元素并删除
 * startwith/endwith                首字符/尾字符匹配
 * strpos_first                     字符数组优先匹配，一般匹配 ' "
 * gb_substr/gb_strlen/gb_haschinese      中文字符串处理函数
 * getstrparam/setstrparam          比json还简化的数据保存方式，一般用于数据字典保存。例: name=AAA|age=12|cc=CCTT
 * 
 * 数据库相关
 * fieldadd                         手动增加列表显示字段，并返回排序字段字符串
 * getrelation                      获取表内关联数据，用于大数据量表的所属ID转文字
 * tran_delcheck/tran_delall/me     事务删除前确认/删除相关数据
 * 
 * 字典相关
 * id2map                           将 id/name代码数组对 转换为 键值对
 * ccode                            代码数组中，通过代码值找代码名
 * mcode                            代码数组中，通过代码值找代码名，多级
 * scode                            代码数组中，查找多个代码值，返回数组
 * dcode                            代码数组中，通过代码名找代码值
 * 
 * LOG相关
 * savelogfile                      保存log信息写入到本地文件
 * logdbstr                         将数据变化格式化成字符串
 * 
 * 输入输出相关
 * \ciy\post{}                       payload json参数处理类 is/get/getint/getfloat/getdate/getbool/getraw/safehtml
 * getstr/getint/post/request/cookie    获取用户输入数据(不能信任任何用户输入，包括cookie)
 * getip                            IP的快捷函数，支持数字和字符串返回
 * 
 * 文件相关
 * dirmake/filedel/copy/save/load   创建多层新文件夹/文件静默删除/拷贝/保存/读取
 * file_down                        URL文件下载保存
 * fileext                          获取文件扩展名，如jpg
 */
if (substr(__DIR__, 0, 1) == '/')
    error_reporting(0); //0禁用错误输出；E_ALL打开错误输出
else
    error_reporting(E_ALL); //0禁用错误输出；E_ALL打开错误输出
date_default_timezone_set('Asia/Shanghai');

if (!isset($path))
    $path = $_SERVER['DOCUMENT_ROOT'];
defined('PATH_ROOT') || define('PATH_ROOT', substr($path, 0, strrpos($path, '/') + 1)); //web根目录。
defined('PATH_WEB') || define('PATH_WEB', PATH_ROOT . 'web/'); //web根目录。
defined('NAME_SELF') || define('NAME_SELF', substr($_SERVER['PHP_SELF'], strrpos($_SERVER['PHP_SELF'], '/') + 1, -4));
defined('MAXTIMES') || define('MAXTIMES', 9999999999999);
$logpath = PATH_ROOT . 'log/';

spl_autoload_register(function ($class) {
    if (substr($class, 0, 4) == 'ciy\\') //将zciyphp简写成ciy，z开头的目录通常排在最下，不过影响找业务目录。
        $class = 'zciyphp\\' . substr($class, 4);
    $file = PATH_ROOT . $class . '.php';
    $file = str_replace('\\', '/', $file);
    if (is_file($file)) {
        require_once $file;
        return;
    }
});
class emptyclass {
}

function clog() {
    echo "\n" . '<pre>';
    $first = true;
    foreach (func_get_args() as $var) {
        if (is_null($var))
            echo 'null';
        else if (is_long($var))
            echo 'long:' . $var;
        else if (is_integer($var))
            echo 'int:' . $var;
        else if (is_string($var)) {
            if (strlen($var) == 0)
                echo 'str:---空字符串----';
            else {
                if ($first && func_num_args() > 1)
                    echo '<kbd>' . $var . '</kbd>';
                else
                    echo 'str:' . $var;
            }
        } else if (is_bool($var))
            echo 'bool:' . ($var ? 'true' : 'false');
        else {
            echo 'Type:' . gettype($var) . "\n";
            print_r($var);
        }
        $first = false;
        echo "\n";
    }
    echo '</pre>' . "\n";
    @ob_flush();
    flush();
}

function fixmonth($num, $dt) { //php +n months 修复在28号之后产生的错误
    $day = date('d', $dt);
    if ($day < 29)
        return strtotime(($num > 0 ? '+' : '-') . $num . ' month', $dt);
    $lastday = date('d', strtotime('last day of this month', $dt));
    $nextdt = strtotime(($num > 0 ? '+' : '-') . $num . ' month', strtotime(date('Y-m-1', $dt)));
    $nextlastday = date('d', strtotime('last day of this month', $nextdt));
    if ($day <= $nextlastday)
        return strtotime(date('Y-m-', $nextdt) . $day);
    return strtotime(date('Y-m-', $nextdt) . ($nextlastday - $lastday + $day));
}
function tostr($str = '') {
    if (!$str)
        return '';
    return $str . '';
}
function toint($str = 0) {
    if (!$str)
        return 0;
    if (!is_numeric($str))
        return 0;
    return intval(round($str));
}
function todate($time, $format = 'i') {
    if ($time == 0)
        return '--';
    if ($format == 'H')
        return date('Y-m-d H', $time);
    if ($format == 'd')
        return date('Y-m-d', $time);
    if ($format == 'hi')
        return date('H:i', $time);
    if ($format == 'm')
        return date('Y-m', $time);
    if ($format == 's')
        return date('Y-m-d H:i:s', $time);
    return date('Y-m-d H:i', $time);
}
function totimepoint($time, $bsecond = false) {
    $time = toint($time);
    if ($time <= 0 || $time > 86400)
        return '';
    $time--;
    $hour = toint($time / 3600);
    $minute = toint((($time - $hour * 3600) / 60));
    $ret = str_pad($hour, 2, '0', STR_PAD_LEFT) . ':' . str_pad($minute, 2, '0', STR_PAD_LEFT);
    if ($bsecond)
        return $ret;
    $second = toint($time - $hour * 3600 - $minute * 60);
    return $ret . ':' . str_pad($second, 2, '0', STR_PAD_LEFT);
}
function tounit($val) {
    $us = explode('|', $val);
    $vals = array();
    if (count($us) == 5)
        $vals[] = '1' . $us[4] . $us[3] . $us[2];
    if (count($us) > 2)
        $vals[] = '1' . $us[2] . $us[1] . $us[0];
    if (count($vals) == 0 && !empty($us[0]))
        $vals[] = $us[0];
    return $vals;
}
function tocyc($val) {
    if ($val < 0)
        return -$val . '月';
    else if ($val >= 86400)
        return toint($val / 86400) . '天';
    else
        return $val . '秒';
}
function tostamp($str = 'now') {
    if ($str == 'now')
        return time();
    $time = strtotime($str);
    if ($time === false)
        return 0;
    return $time;
}
function locinzone($paths, $zlat, $zlng) {
    //30.434272 120.274938,30.433977 120.277270,30.432403 120.277957,30.431764 120.276340,30.432270 120.273358,30.433997 120.273283
    $fences = explode(',', $paths);
    $nvert = count($fences);
    $vertx = [];
    $verty = [];
    foreach ($fences as $r) {
        list($lat, $lng) = explode(' ', $r);
        $vertx[] = $lat;
        $verty[] = $lng;
    }
    $i = $j = 0;
    $c = false;
    for ($i = 0, $j = $nvert - 1; $i < $nvert; $j = $i++) {
        if ((($verty[$i] > $zlng) != ($verty[$j] > $zlng)) &&
            ($zlat < ($vertx[$j] - $vertx[$i]) * ($zlng - $verty[$i]) / ($verty[$j] - $verty[$i]) + $vertx[$i])
        )
            $c = !$c;
    }
    return $c;
}
function timems() {
    $microtime = microtime();
    $comps = explode(' ', $microtime);
    return toint(sprintf('%d%03d', $comps[1], $comps[0] * 1000));
}
function ismobile($mob) {
    return preg_match('/^1\d{10}$/', $mob);
}
function ismail($str) {
    return filter_var($str, FILTER_VALIDATE_EMAIL);
}
function isweixin() {
    $useragent = strtolower($_SERVER["HTTP_USER_AGENT"]);
    $is_weixin = strripos($useragent, 'micromessenger');
    if ($is_weixin > 0)
        return true;
    return false;
}
function getcciycheck($num) {
    $checksum = 0;
    for ($i = 15; $i > 0; $i--) {
        $nibble = ($num >> (4 * $i)) & 0xF;
        $checksum ^= $nibble;
    }
    return $checksum;
}
function iscciy($num) {
    return getcciycheck($num) == ($num & 0xf);
}
function idcard($str) {
    $str = strtoupper(trim($str));
    if (empty($str))
        return '身份证未输入';
    if (strlen($str) != 18)
        return '身份证长度错误';
    $province = ['11', '12', '13', '14', '15', '21', '22', '23', '31', '32', '33', '34', '35', '36', '37', '41', '42', '43', '44', '45', '46', '50', '51', '52', '53', '54', '61', '62', '63', '64', '65', '71', '81', '82', '91'];
    if (!in_array(substr($str, 0, 2), $province))
        return '身份证地区不合法';

    $birth_year = substr($str, 6, 4);
    $birth_month = substr($str, 10, 2);
    $birth_day = substr($str, 12, 2);
    $birth = strtotime($birth_year . '-' . $birth_month . '-' . $birth_day);
    if ($birth === false)
        return '身份证生日错误';
    if ($birth > time())
        return '身份证生日不能超过今天';
    $ahead17_char = substr($str, 0, 17);
    $last_char = substr($str, -1);
    $factor = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
    $c = array(1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2);
    $t_res = 0;
    for ($i = 0; $i < 17; $i++) {
        $t_res += intval($ahead17_char[$i]) * $factor[$i];
    }
    $calc_last_char = $c[$t_res % 11];
    if (strtolower($last_char) != strtolower($calc_last_char))
        return '身份证校验位错误'; // . $calc_last_char;
    $sex = substr($str, -2, 1);
    if ($sex % 2 == 0)
        $sex = 2;
    else
        $sex = 1;
    //$age = toint(date('Y')) - toint($birth_year);
    return ['code' => $str, 'birth' => $birth_year . '-' . $birth_month . '-' . $birth_day, 'sex' => $sex, 'area' => substr($str, 0, 6)];
}
function iduscc($uscc) {
    $uscc = strtoupper(trim($uscc));
    if (empty($uscc))
        return '统一社会信用代码未输入';
    if (strlen($uscc) != 18)
        return '统一社会信用代码不是18位';
    $province = ['11', '12', '13', '14', '15', '21', '22', '23', '31', '32', '33', '34', '35', '36', '37', '41', '42', '43', '44', '45', '46', '50', '51', '52', '53', '54', '61', '62', '63', '64', '65', '71', '81', '82', '91'];
    if (!in_array(substr($uscc, 2, 2), $province))
        return '统一社会信用代码地区不合法';
    $lcrc = _tsucc(substr($uscc, 0, 17));
    if (substr($uscc, -1, 1) != $lcrc)
        return '统一社会信用代码校验位错误';
    return ['code' => $uscc, 'area' => substr($uscc, 2, 6), 'pn' => substr($uscc, 0, 2)];
}
function _tsucc($usc) {
    $list = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'J' => 18, 'K' => 19, 'L' => 20, 'M' => 21, 'N' => 22, 'P' => 23, 'Q' => 24, 'R' => 25, 'T' => 26, 'U' => 27, 'W' => 28, 'X' => 29, 'Y' => 30);
    $listf = array_flip($list);
    $wi = array(1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28);
    $num = 0;
    for ($i = 0; $i < 17; $i++) {
        $num += $list[$usc[$i]] * $wi[$i];
    }
    switch ($num % 31) {
        case '0':
            $result = 0;
            break;
        default:
            $result = 31 - $num % 31;
            break;
    }
    return $listf[$result];
}
function locdistance($lat1, $lng1, $lat2, $lng2) {
    $earthRadius = 6367000;
    $lat1 = ($lat1 * pi()) / 180;
    $lng1 = ($lng1 * pi()) / 180;
    $lat2 = ($lat2 * pi()) / 180;
    $lng2 = ($lng2 * pi()) / 180;
    $calcLongitude = $lng2 - $lng1;
    $calcLatitude = $lat2 - $lat1;
    $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2);
    $stepTwo = 2 * asin(min(1, sqrt($stepOne)));
    $Distance = $earthRadius * $stepTwo;
    return $Distance * 1000;
}
function str2bin($str) {
    $bytes = [];
    for ($i = 0; $i < strlen($str); $i++) {
        $bytes[] = ord($str[$i]);
    }
    return $bytes;
}

function ciysign($str, $key) {
    $sign = hash_hmac('sha256', $str, $key, false);
    $sign = substr($sign, 0, 16);
    $sign = str_replace($sign, 'd', 'p');
    $sign = str_replace($sign, 'e', 'z');
    $sign = str_replace($sign, 'f', 'm');
    return $sign;
}
/* * *******************************************************************
 * 函数名称:encrypt
 * 函数作用:加密解密字符串
 * 安全函数，建议有能力自行修改一些
 * 使用方法:
 * 加密     :encrypt('str','E','nowamagic');
 * 解密     :encrypt('被加密过的字符串','D','nowamagic');
 * 参数说明:
 * $string   :需要加密解密的字符串
 * $operation:判断是加密还是解密:E:加密   D:解密
 * $key      :加密的钥匙(密匙);
 * ******************************************************************* */
function encrypt($string, $operation, $key = '') {
    $key = md5($key, true);
    $key_length = strlen($key);
    if ($operation == 'D')
        $string = base64_decode(substr(str_replace('_', '/', str_replace('-', '+', str_replace('*', '=', $string))), 1));
    else {
        $string = substr(md5($string . $key, true), 0, 8) . $string;
    }
    $string_length = strlen($string);
    $rndkey = $box = array();
    $result = '';
    for ($i = 0; $i < 256; $i++) {
        $rndkey[$i] = ord($key[$i % $key_length]);
        $box[$i] = $i;
    }
    for ($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }
    for ($a = $j = $i = 0; $i < $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    }
    if ($operation == 'D') {
        if (substr($result, 0, 8) == substr(md5(substr($result, 8) . $key, true), 0, 8)) {
            return substr($result, 8);
        } else {
            return '';
        }
    } else {
        return '1' . str_replace('/', '_', str_replace('+', '-', str_replace('=', '*', base64_encode($result))));
    }
}
/* * *******************************************************************
 * 函数名称:enid/deid
 * 函数作用:id加密函数/id解密函数。
 * 安全函数，建议有能力自行修改一些
 * 参数说明:
 * $id    两位数的数字id（id>=10）
 * 返回值：加密后的数字
 *   例：enid(8245) = 872435
 *   例：deid(872435) = 8245
 * ******************************************************************* */
function enid($id, $key = 253) {
    $id = toint($id);
    if ($id <= 0)
        return 0;
    if ($id < 10)
        $strid = '0' . $id;
    else
        $strid = $id . '';
    $fx = _calnumber($id, $key);
    $ulen = strlen($strid);
    return substr($strid, 0, 1) . $fx[0] . substr($strid, 1, $ulen - 2) . $fx[1] . substr($strid, -1, 1);
}
function deid($id, $key = 253) {
    $strid = $id . '';
    $ulen = strlen($strid);
    $fx = substr($strid, 1, 1) . substr($strid, -2, 1);
    $id = toint(substr($strid, 0, 1) . substr($strid, 2, $ulen - 4) . substr($strid, -1, 1));
    if ($fx == _calnumber($id, $key))
        return $id;
    return 0;
}
/* * *******************************************************************
 * 函数名称:calnumber
 * 函数作用:换算id的加密校验数，被enid、deid函数调用。
 * 参数说明:
 * $key    默认加密因子，可以自行修改。尽量用奇数100-1000
 * $len    返回校验数位数。
 * 返回值：len位数字字符串
 *   例：calnumber(8245,224,2) = 73
 * ******************************************************************* */
function _calnumber($num, $key, $len = 2) {
    if ($num > 250600)
        $num %= 250600;
    $n = $num % 8566;
    if ($n < 100)
        $n += 100;
    $xx = abs($num * $n + $key);
    if ($xx % 13 > 8)
        $xx += $key;
    if ($xx % 13 > 4)
        $xx += $key + $key + $key;
    if ($len < 1)
        $len = 2;
    $ret = abs($xx % pow(10, $len));
    return sprintf('%0' . $len . 'd', $ret);
}
function sha256($str) {
    return hash('sha256', $str);
}
function sha512($str) {
    return hash('sha512', $str);
}
function conv33_10($num) {
    $from = 33;
    $dict = 'abcdefghijkmnpqrstuvwxyz123456789';
    $len = strlen($num);
    $dec = 0;
    for ($i = 0; $i < $len; $i++) {
        $pos = strpos($dict, $num[$i]);
        $dec = bcadd(bcmul(bcpow($from, $len - $i - 1), $pos), $dec);
    }
    return $dec;
}

function conv10_33($num) {
    $to = 33;
    $dict = 'abcdefghijkmnpqrstuvwxyz123456789';
    $ret = '';
    do {
        $ret = $dict[bcmod($num, $to)] . $ret;
        $num = bcdiv($num, $to);
    } while ($num > 0);
    return $ret;
}
function arrayrand($arr, $num = 1) {
    $retarr = array();
    if (count($arr) < $num)
        $num = count($arr);
    for ($i = 0; $i < $num; $i++) {
        $ind = array_rand($arr);
        $retarr[] = array_splice($arr, $ind, 1)[0];
    }
    return $retarr;
}
function startwith($str, $suffix) {
    return strpos($str, $suffix) === 0;
}
function endwith($str, $suffix) {
    return substr($str, -strlen($suffix)) === $suffix;
}
function strpos_first($search, $findstrs, $offset = 0) { //返回第一个char位置，第一个char，内部位置
    if (strlen($search) < $offset)
        return [-1, '', ''];
    if (is_string($findstrs))
        $findstrs = array($findstrs);
    $f_str = '';
    $f_ind = 2147483647;
    foreach ($findstrs as $findstr) {
        $ind = strpos($search, $findstr, $offset);
        if ($ind === false)
            continue;
        if ($ind < $f_ind) {
            $f_str = $findstr;
            $f_ind = $ind;
        }
    }
    if ($f_ind == 2147483647)
        return [-1, '', ''];
    $ind2 = strpos($search, $f_str, $f_ind + 1);
    if ($ind2 > 0)
        return [$f_ind, $f_str, substr($search, $f_ind + 1, $ind2 - $f_ind - 1)];
    return [$f_ind, $f_str, ''];
}
/**
 * 类似substr，主要用于显示字符限制。支持中文，2个半角算一个字符占位。超过部分用$dot代替。
 * 建议：能用css实现超出汉字隐藏，尽量不要用此函数。
 * 例：gb_substr('CHN中华人民共和国',10)='CHN中华...'  gb_substr('CHN中华人民共和国',9)='CHN中华...'
 */
function gb_substr($str, $len, $dot = '...') {
    $i = 0;
    $l = 0;
    $c = 0;
    $a = array();
    while ($l < $len) {
        $t = substr($str, $i, 1);
        if (ord($t) >= 224) {
            $c = 3;
            $t = substr($str, $i, $c);
            $l += 2;
        } elseif (ord($t) >= 192) {
            $c = 2;
            $t = substr($str, $i, $c);
            $l += 2;
        } else {
            $c = 1;
            $l++;
        }
        $i += $c;
        if ($l > $len)
            break;
        $a[] = $t;
    }
    $re = implode('', $a);
    if (substr($str, $i, 1) !== false) {
        array_pop($a);
        ($c == 1) and array_pop($a);
        $re = implode('', $a);
        $re .= $dot;
    }
    return $re;
}
function dbstr($str, $len) {
    if (mb_strlen($str, 'UTF-8') > $len)
        return mb_substr($str, 0, $len, 'UTF-8');
    return $str;
}
/**
 * 类似strlen，英文算1个，中文算2个字符。
 * 例：gb_strlen('CHN中华人民共和国')=17
 */
function gb_strlen($str) {
    return (strlen($str) + mb_strlen($str, 'UTF8')) / 2;
}

function gb_haschinese($str) {
    return preg_match("/([\x81-\xfe][\x40-\xfe])/", $str);
}
/**
 * 比json还简化的多数据保存方式。该方式有一定的限制，请熟悉后使用。
 * 例：getstrparam('Skey=10392|Pid=33|Memo=其他备注文字') = Array([Skey] => 10392[Pid] => 33[Memo] => 其他备注文字);
 * $sparam = getstrparam('Skey=10392|Pid=33|Memo=其他备注文字');
 * $val = @$sparam['Memo'];
 * $sparam['Memo'] = 'xxx';
 * $retval = setstrparam($sparam); 值： 'Skey=10392|Pid=33|Memo=xxx'
 */
function getstrparam($pstr, $split = '|') {
    $strs = explode($split, $pstr);
    $data = array();
    foreach ($strs as $str) {
        $ind = strpos($str, '=');
        if ($ind !== false)
            $data[substr($str, 0, $ind)] = trim(substr($str, $ind + 1));
    }
    return $data;
}
function setstrparam($parr, $split = '|') {
    $pstr = '';
    foreach ($parr as $p => $a)
        $pstr .= $p . '=' . $a . $split;
    return trim($pstr, $split);
}
function split($sp, $str) {
    return explode($sp, trim($str, $sp));
}
function fieldadd($fshow, &$field, $pos, $key, $val) {
    $field[$key] = array('c' => $val);
    $fshows = explode(',', $fshow);
    if ($pos == -1) {
        if (empty($fshows[0]))
            $fshows[0] = $key;
        else
            $fshows[] = $key;
    } else if ($pos == 0)
        array_unshift($fshows, $key);
    else
        array_splice($fshows, $pos, 0, $key);
    return implode(',', $fshows);
}
function getrelation($cdb, $rows, $table, $fieldstr, $column = 'id,name', $queryid = 'id') {
    $ids = array();
    $fields = explode(',', $fieldstr);
    foreach ($fields as $field) {
        foreach ($rows as $row) {
            if (isset($row[$field]) && !in_array($row[$field], $ids))
                $ids[] = $row[$field];
        }
    }
    if (count($ids) == 0)
        return array();
    $csql = new \ciy\sql($table);
    $csql->where($queryid . ' in ', implode(',', $ids));
    $csql->column($column);
    return $cdb->get($csql);
}
function delcheck($db, $delid, $table, $field, $msg = '', $where = '') {
    $csql = new \ciy\sql($table);
    if (!empty($where))
        $csql->where($where);
    $csql->where($field, $delid);
    $cnt = $db->get1($csql);
    if ($cnt > 0)
        throw new \Exception('CIYIGN存在' . $cnt . '个' . $msg . '，不能删除');
}
function delall($db, $delid, $table, $field, $msg = '') {
    $csql = new \ciy\sql($table);
    $csql->where($field, $delid);
    if ($db->delete($csql) === false)
        throw new \Exception($msg . '删除失败:' . $db->error);
}
function delme($db, $delid, $table) {
    $csql = new \ciy\sql($table);
    $csql->where('id', $delid);
    if ($db->delete($csql) === false)
        throw new \Exception('删除失败:' . $db->error);
}
function deltimeall($db, $delid, $table, $field, $msg = '') {
    if ($db->execute('update ' . $table . ' set deltimes=' . time() . ' where ' . $field . '=' . $delid) === false)
        throw new \Exception($msg . '标记删除失败:' . $db->error);
}
function deltimeme($db, $delid, $table) {
    if ($db->execute('update ' . $table . ' set deltimes=' . time() . ' where id=' . $delid) === false)
        throw new \Exception('标记删除失败:' . $db->error);
}

function mapid2data($datas) {
    $ret = array();
    foreach ($datas as $data) {
        $ret[$data['id']] = $data;
    }
    return $ret;
}
function mapid2any($datas) {
    $ret = array();
    foreach ($datas as $data) {
        $ret[$data['id']] = $data;
    }
    return $ret;
}
function ccode($rows, $code, $showcode = 'id', $showtitle = 'name') {
    foreach ($rows as $row) {
        if ($code == $row[$showcode]) {
            if (empty($showtitle))
                return $row;
            return $row[$showtitle];
        }
    }
    if (empty($showtitle))
        return null;
    return '--';
}
function mcode($rows, $code, $showcode = 'id', $showtitle = 'name', $upcode = 'upid') {
    $codes = array();
    for ($i = 0; $i < 10; $i++) {
        if ($code <= 0)
            break;
        $bfind = false;
        foreach ($rows as $row) {
            if ($code == $row[$showcode]) {
                if (empty($showtitle))
                    array_unshift($codes, $row);
                else
                    array_unshift($codes, $row[$showtitle]);
                $code = toint(@$row[$upcode]);
                $bfind = true;
                break;
            }
        }
        if (!$bfind)
            break;
    }
    return $codes;
}
function scode($rows, $code, $showcode = 'id', $showtitle = 'name') {
    $rets = array();
    $codes = explode(',', $code);
    foreach ($codes as $c) {
        if (empty($c))
            continue;
        foreach ($rows as $row) {
            if ($c == $row[$showcode]) {
                if (empty($showtitle))
                    $rets[] = $row;
                else
                    $rets[] = $row[$showtitle];
            }
        }
    }
    return $rets;
}
function bcode($rows, $val) {
    $rets = array();
    $val = toint($val);
    foreach ($rows as $cod) {
        if (($val & (1 << (toint($cod['id']) - 1))) != 0)
            $rets[] = $cod['name'];
    }
    return $rets;
}
function dcode($rows, $codename, $showcode = 'id', $showtitle = 'name') {
    foreach ($rows as $row) {
        if ($codename == $row[$showtitle]) {
            if (empty($showcode))
                return $row;
            return $row[$showcode];
        }
    }
    return -1;
}
function logdbstr($oldrow, $newrow) {
    $msg = '';
    if (is_array($oldrow) && is_array($newrow)) {
        $msg .= 'Upd=' . @$oldrow['id'];
        $modify = false;
        foreach ($newrow as $f => $v) {
            if (!isset($oldrow[$f]))
                continue;
            if ($oldrow[$f] != $v) {
                $msg .= '_|@|_' . $f . '=' . $oldrow[$f] . '→' . $v;
                $modify = true;
            }
        }
        if (!$modify)
            return "";
    } else if (is_array($newrow)) {
        $msg .= 'Ins=' . @$newrow['id'];
        foreach ($newrow as $f => $v) {
            if ($f == 'id')
                continue;
            $msg .= '_|@|_' . $f . '=' . $v;
        }
    } else if (is_array($oldrow)) {
        $msg .= 'Del=' . @$oldrow['id'];
        foreach ($oldrow as $f => $v) {
            if ($f == 'id')
                continue;
            $msg .= '_|@|_' . $f . '=' . $v;
        }
    } else {
        return "";
    }
    return $msg;
}
function savelogfile($types, $msg, $isrequest = false) {
    global $logpath;
    if (substr($msg, 0, 6) == 'CIYIGN')
        return true;
    $filename = $logpath . $types . '.log';
    $dir = dirname($filename);
    dirmake($dir);
    $fp = fopen($filename, 'a');
    if (!$fp)
        return false;
    if ($isrequest) {
        $msg .= ' GET:';
        foreach ($_GET as $key => $value)
            $msg .= $key . '=' . $value . '&';
        $msg .= ' POST:' . file_get_contents('php://input');
    }
    $msg .= "\r\n";
    if (@fwrite($fp, date('Y-m-d H:i:s') . "\t" . $msg)) {
        fclose($fp);
        return true;
    }
    fclose($fp);
    return false;
}
/**
 * JSON函数调用组件，用于界面Ajax请求在本页处理。
 * isform 用于页面form提交刷新的直接请求。非ajax请求。（为兼容传统更新方式，不建议使用）
 * ?func=函数名
 * 将调用json_函数名(){}
 * 函数返回array数组。
 */
function _setmemvar($db, $types, $value) {
    // if (function_exists('setmemvar'))
    //     return setmemvar($db, $types, $value);
    return false;
}
function ciy_ouputJSON($retarr, $ms = 0, $uri = '') {
    global $db;
    global $_token;
    if ($db) {
        if ($ms > 0) {
            _setmemvar($db, 'func_runms', array('params+' . $ms));
            if ($ms > 500 && !empty($uri)) {
                $updata = array();
                $updata['uri'] = $uri;
                $updata['posts'] = file_get_contents('php://input');
                $updata['ms'] = $ms;
                $updata['addtimes'] = tostamp();
                $csql = new \ciy\sql('zc_funcslow');
                $db->insert($csql, $updata);
            }
        }
        if (isset($retarr['code']) && $retarr['code'] == 1)
            _setmemvar($db, 'func_succ', array('params+1'));
        else
            _setmemvar($db, 'func_fail', array('params+1'));
    }
    if (isset($_token['__ciyauth']))
        $retarr['_ciyauth'] = $_token['__ciyauth'];
    $jsonstr = json_encode($retarr, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_UNICODE);
    if ($jsonstr === false) {
        $retarr['errmsg'] = mb_convert_encoding($retarr['errmsg'], 'UTF-8', 'ISO-8859-1');
        $jsonstr = json_encode($retarr);
        if ($jsonstr === false) {
            $retarr['errmsg'] = json_last_error_msg();
            $jsonstr = json_encode($retarr);
        }
    }
    if (isset($_GET['jsonp'])) {
        $callback = $_GET['jsonp'];
        if (strlen($callback) < 128 && preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $callback)) {
            header('Content-Type: application/javascript; charset=utf-8');
            die($callback . '(' . $jsonstr . ')');
        }
    }
    header('Content-Type: application/json; charset=utf-8');
    die($jsonstr);
}
//code=2 未登录，3 未授权，9 函数未加载
function errjson($error, $errcode = 0, $ext = null) {
    if (is_array($ext))
        $ret = $ext;
    else
        $ret = array();
    if ($errcode == 1)
        $errcode = -1;
    $ret['code'] = $errcode;
    if (substr($error, 0, 6) == 'CIYIGN')
        $error = substr($error, 6);
    $ret['errmsg'] = $error;
    return $ret;
}
function succjson($ext = null) {
    if (is_array($ext))
        $ret = $ext;
    else
        $ret = array();
    $ret['code'] = 1;
    return $ret;
}
function memadd($key) {
}
function randstr($num, $letter = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') {
    $b = [];
    for ($i = 0; $i < $num; $i++) {
        $b[] = $letter[rand(0, strlen($letter) - 1)];
    }
    return implode('', $b);
}
function objstr($obj, $name, $defvalue = '', $allow = 'text') { //text:仅文本，all:都可以
    if (!isset($obj[$name]))
        return $defvalue;
    $val = $obj[$name];
    if (empty($val))
        return $defvalue;
    if ($allow == 'text')
        return strip_tags($val);
    return $val;
}
function objint($obj, $name, $defvalue = 0) {
    if (!isset($obj[$name]) || $obj[$name] === '')
        return $defvalue;
    return toint($obj[$name]);
}
function objfloat($obj, $name, $defvalue = 0.0) {
    if (!isset($obj[$name]))
        return $defvalue;
    return (float)$obj[$name];
}
function objdate($obj, $name, $defvalue = 0) {
    if (!isset($obj[$name]))
        return $defvalue;
    $val = $obj[$name];
    if (strpos($val, '-') !== false)
        return strtotime($val);
    return toint($val);
}
function get($name, $defvalue = '', $allow = 'text') { //text:仅文本，all:都可以
    if (!isset($_GET[$name]))
        return $defvalue;
    $val = $_GET[$name];
    if ($allow == 'text')
        return strip_tags($val . '');
    return $val;
}
function getint($name, $defvalue = 0) {
    if (!isset($_GET[$name]))
        return $defvalue;
    return toint($_GET[$name]);
}
function post($name, $defvalue = '', $allow = 'text') { //text:仅文本，all:都可以
    if (!isset($_POST[$name]))
        return $defvalue;
    $val = $_POST[$name];
    if ($allow == 'text')
        return strip_tags($val);
    return $val;
}
function request($name, $defvalue = '', $allow = 'text') { //text:仅文本，all:都可以
    if (!isset($_REQUEST[$name]))
        return $defvalue;
    $val = $_REQUEST[$name];
    if ($allow == 'text')
        return strip_tags($val);
    return $val;
}
function cookie($name, $defvalue = '', $allow = 'text') { //text:仅文本，all:都可以
    if (!isset($_COOKIE[$name]))
        return $defvalue;
    $val = $_COOKIE[$name];
    if ($allow == 'text')
        return strip_tags($val);
    return $val;
}
/**
 * 返回32位int真实IP地址。显示时请用long2ip()函数。
 * $real = true 时，返回字符串ip地址。
 * 数据库ip字段建议设置：int有符号类型（建议）；varchar(15)；兼容IPv6 varchar(40)
 *   例：getip() = 1343193298          getip(true) = '80.15.128.210'
 *   例：getip() = -765170305          getip(true) = '210.100.109.127'
 * 提示：真实IP地址访问方法有很多种，有些用户会用header伪造真实IP。请根据您的实际服务器集群部署调整本函数。特别是CDN和反向代理部分。
 */
function getip() {
    $headers = [
        'HTTP_CF_CONNECTING_IP',
        'HTTP_TRUE_CLIENT_IP',
        'HTTP_X_FORWARDED_FOR',
        'HTTP_FORWARDED',
        'HTTP_X_REAL_IP',
        'HTTP_FORWARDED_FOR',
        'HTTP_X_FORWARDED',
    ];
    $ips = array();
    foreach ($headers as $header) {
        if (!empty($_SERVER[$header]))
            $ips[] = strtolower($_SERVER[$header]);
    }
    foreach ($ips as $ip) {
        $ind = strpos($ip, ',');
        if ($ind !== false)
            $ip = substr($ip, 0, $ind);
        $ind = strpos($ip, 'for=');
        if ($ind !== false) {
            $ip = substr($ip, $ind + 4);
            if ($ip[0] == '"') {
                $ip = substr($ip, 1, strpos($ip, '"', 1) - 1);
                $ind = strpos($ip, ']');
                if ($ind !== false)
                    $ip = substr($ip, 1, $ind - 1);
            } else if ($ip[0] == '[') {
                $ip = substr($ip, 1, strpos($ip, ']') - 1);
            } else {
                $ind = strpos($ip, ';');
                if ($ind !== false)
                    $ip = substr($ip, 0, $ind);

                if (strpos($ip, '.') !== false) {
                    $ind = strpos($ip, ':');
                    if ($ind !== false)
                        $ip = substr($ip, 0, $ind);
                }
            }
        } else {
            if (strpos($ip, '.') !== false) {
                $ind = strpos($ip, ':');
                if ($ind !== false)
                    $ip = substr($ip, 0, $ind);
            }
        }
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))
            return $ip;
    }
    if (isset($_SERVER['REMOTE_ADDR']))
        return $_SERVER['REMOTE_ADDR'];
    return '0.0.0.0';
}
//循环建立新文件夹
function dirmake($dir, $power = 0777) {
    if (!$dir)
        return false;
    if (!is_dir($dir)) {
        $ret = mkdir($dir, $power, true);
        if ($ret === false)
            savelogfile('err_file', 'dir make err:' . $dir);
        return $ret;
    }
    return true;
}
//静默删除文件
function filedel($file) {
    try {
        unlink($file);
    } catch (Exception $ex) {
        savelogfile('err_file', 'file del err:' . $file . ':' . $ex->getMessage());
    }
}
//静默拷贝文件
function filecopy($srcfile, $dstfile) {
    try {
        copy($srcfile, $dstfile);
    } catch (Exception $ex) {
        savelogfile('err_file', 'file copy err:' . $srcfile . '->' . $dstfile . ':' . $ex->getMessage());
    }
}
//存储文本到文件
function filesave($filename, $text) {
    if (!$filename || !$text)
        return false;
    if (!dirmake(dirname($filename)))
        return false;
    try {
        if ($fp = fopen($filename, 'w')) {
            if (@fwrite($fp, $text)) {
                fclose($fp);
                return true;
            } else {
                fclose($fp);
                return false;
            }
        }
    } catch (Exception $ex) {
        savelogfile('err_file', 'file save err:' . $filename . ':' . $ex->getMessage());
    }
    return false;
}
//读取文本文件
function fileload($dfile) {
    if (!file_exists($dfile))
        return '';
    try {
        return file_get_contents($dfile);
    } catch (Exception $ex) {
        savelogfile('err_file', 'file load err:' . $dfile . ':' . $ex->getMessage());
    }
    return '';
}
//存储文本到文件
function storsave($filename, $text) {
    if (!$filename || !$text)
        return false;
    $filename = str_replace('{Y}', date('Y'), $filename);
    $filename = str_replace('{m}', date('m'), $filename);
    $filename = str_replace('{d}', date('d'), $filename);
    if (substr($filename, 0, 1) == '/')
        $filename = PATH_WEB . 'ud' . $filename;
    return filesave($filename, $text);
}
function webini($section) {
    if (!file_exists(PATH_ROOT . 'web.ini'))
        return 'INI file nofind:' . PATH_ROOT . 'web.ini';
    try {
        $inis = explode("\n", file_get_contents(PATH_ROOT . 'web.ini'));
        $vars = null;
        foreach ($inis as $ini) {
            if (empty($ini))
                continue;
            if (strpos($ini, '[') === 0) {
                $sec = substr($ini, 1, strpos($ini, ']') - 1);
                if ($sec == $section) {
                    $vars = array();
                } else if (is_array($vars))
                    return $vars;
            } else {
                if (!is_array($vars))
                    continue;
                $pos = strpos($ini, '=');
                if ($pos === false)
                    continue;
                $key = substr($ini, 0, $pos);
                $val = substr($ini, $pos + 1);
                $vars[$key] = trim($val);
            }
        }
        return 'INI section nofind:' . $section;
    } catch (Exception $ex) {
        return 'INI catch:' . $ex->getMessage();
    }
    return 'INI error';
}
/**
 * 下载音视频图片文件到本地
 * url      下载链接地址
 * savepath 保存相对路径。如ud/
 * filename 保存文件名。默认日期文件名
 * timeout  下载超时时间 秒
 */
function file_down($url, $savepath, $filename = '', $timeout = 60, $defext = '.jpg') {
    if (!dirmake(PATH_WEB . $savepath))
        return 'ERR:保存目录建立失败';
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, urldecode($url));
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    @curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
    $data = curl_exec($ch);
    if ($data === false || empty($data)) {
        return 'ERR:下载失败';
    }
    $info = curl_getinfo($ch);
    curl_close($ch);
    if (empty($filename))
        $filename = date('Ymd_His_') . rand(1000, 9999);
    $fileext = $defext;
    if ($info['content_type'] == 'image/jpeg')
        $fileext = '.jpg';
    else if ($info['content_type'] == 'image/jpg')
        $fileext = '.jpg';
    else if ($info['content_type'] == 'image/png')
        $fileext = '.png';
    else if ($info['content_type'] == 'image/gif')
        $fileext = '.gif';
    else if ($info['content_type'] == 'audio/amr')
        $fileext = '.amr';
    else if ($info['content_type'] == 'audio/wav')
        $fileext = '.wav';
    else if ($info['content_type'] == 'audio/mpeg')
        $fileext = '.mp3';
    else if ($info['content_type'] == 'audio/ogg')
        $fileext = '.ogg';
    else if ($info['content_type'] == 'image/svg+xml')
        $fileext = '.svg';
    else if ($info['content_type'] == 'image/webp')
        $fileext = '.webp';
    $fp = fopen(PATH_WEB . $savepath . $filename . $fileext, 'w');
    fwrite($fp, $data);
    @fclose($fp);
    return $savepath . $filename . $fileext;
}
//文件扩展名  jpg
function file_ext($file) {
    $ind = strrpos($file, '.');
    if ($ind === false)
        return '';
    return substr($file, $ind + 1);
}
function file_stor($url) {
    if (!$url)
        return '';
    if (strtolower(substr($url, 0, 4)) === 'http')
        return $url;
    $cfg = webini('s3' . substr($url, 0, 1));
    return $cfg['url'] . substr($url, 1);
}
