<?php
/** 全局公用
 * ===========================================================================
 * Veitool 快捷开发框架系统
 * Author: Niaho 26843818@qq.com
 * Copyright (c)2019-2025 www.veitool.com All rights reserved.
 * Licensed: 这不是一个自由软件，不允许对程序代码以任何形式任何目的的再发行
 * ---------------------------------------------------------------------------
 */

// 版本信息
define('VT_VERSION', '2.2.5');
// 后台管理员 session 标识
define('VT_MANAGER', 'V_MANAGER');
// 前台会员 session 标识
define('VT_MEMBER', 'V_MEMBER');
// 前台游客 session 标识
define('VT_VISITOR', 'V_VISITOR');
// 分隔符
define('VT_DS', DIRECTORY_SEPARATOR);
// 资源目录 运行目录为根目录时请设为空\其他二级或多级目录时后面无斜杠如：/mydir 或 /mydir/xyz
define('VT_DIR', '');
// 全局静态目录
define('VT_STATIC', VT_DIR . '/static/');
// 项目目录
define('ROOT_PATH', realpath(dirname(__DIR__)) . VT_DS);
// 站点目录
define('VT_PUBLIC', ROOT_PATH . 'public' . VT_DS);
// 插件目录
define('ADDON_PATH', ROOT_PATH . 'addons' . VT_DS);
// 临时目录
define('RUNTIME_PATH', ROOT_PATH . 'runtime' . VT_DS);

use app\model\system\SystemUploadFile;
//获取图片地址
function get_upload_file($fileid = '', $datatype = '')
{
    if ($datatype == 'list') {
        //多附件读取列表
        $map[] = ['fileid', 'in', $fileid];
        return SystemUploadFile::where($map)->select()->toArray();
    } elseif ($datatype == 'info') {
        $map[] = ['fileid', '=', $fileid];
        return SystemUploadFile::where($map)->find();
    } else {
        $fileinfo =  (new SystemUploadFile)->where('fileid', $fileid)->find();
        return isset($fileinfo['fileurl']) ? $fileinfo['fileurl'] : '';
    }

}

/**
 * md5判断
 * @param string $w 字符
 * @return  bool
 */
function is_md5($w)
{
    return preg_match("/^[a-f0-9]{32}$/", $w);
}

/**
 * 判断字符是否合乎规则
 * @param string $s 目标字符串
 * @param string $f 正则类型 ip,mobile,email 或者 允许有的位数范围,如:{1,3}
 * @param array $t 合法的字符集0:字母数字汉字下划线 1:数字 2:小写字母 3:大写字母 4:汉字 5:任何非空白字符
 * @param string $o 允许有字符
 * @return  bool
 */
function is_preg($s, $f = '', $t = [0], $o = '')
{
    if ($s == '' || is_array($s)) return false;
    $s = str_replace([chr(10), chr(13), "\t"], ['', '', ''], $s);
    $p = [
        'ip' => "/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/",
        'mobile' => "/^1[3|4|5|6|7|8|9]{1}[0-9]{9}$/",
        'email' => "/^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/",
        'class' => "/^[A-Z]{1}[a-zA-Z0-9]{1,15}$/",
        'idcard' => "/^([1-6][1-9]|50)\d{4}(19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/"
    ];
    if (isset($p[$f])) return preg_match($p[$f], $s);
    $m = '/^[';
    $p = ['\w', "0-9", 'a-z', 'A-Z', '\x{4e00}-\x{9fa5}', '\S'];
    foreach ($t as $v) {
        $m .= $p[$v] ?? '';
    }
    $m .= $o ? $o . ']' . $f . '+$/u' : ']' . $f . '+$/u';
    return preg_match($m, $s);
}

/**
 * 返回字符串长度
 * @param string $s 目标源
 * @return  int
 */
function word_count($s)
{
    return function_exists('mb_strlen') ? mb_strlen($s, 'utf8') : strlen($s);
}

/**
 * 生成指定长度的随机字符
 * @param int $l 指定长度
 * @param string $c 源字符集
 * @return  string
 */
function random($l, $c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz')
{
    $h = '';
    $m = strlen($c) - 1;
    for ($i = 0; $i < $l; $i++) {
        $h .= $c[mt_rand(0, $m)];
    }
    return $h;
}

/**
 * 生成密码
 * @param string $p 密码
 * @param string $s 密钥
 * @return  string
 */
function set_password($p, $s)
{
    return md5((is_md5($p) ? md5($p) : md5(md5($p))) . $s);
}

/**
 * 设置订单号
 * @retrun  string
 */
function set_order_id()
{
    return date('YmdHis', time()) . substr(microtime(), 2, 6) . sprintf('%03d', rand(0, 999));
}

/**
 * 字符串中间替换为星号
 * @param string $s 字符串
 * @param string $n 星号数 0:按字符算
 * @return  string
 */
function half_replace($s, $n = 0)
{
    $l = mb_strlen($s, 'UTF-8');
    if ($l <= 2) return $s;
    $l = $n > 0 ? $n : $l - 2;
    return mb_substr($s, 0, 1, 'UTF-8') . str_repeat('*', $l) . mb_substr($s, -1, 1, 'UTF-8');
}

/**
 * 回车、换行、空格字符过滤
 * @param string $s 目标字符
 * @return  string
 */
function vtrim($s)
{
    return str_replace([chr(10), chr(13), "\t", ' '], ['', '', '', ''], $s);
}

/**
 * 字符过滤 【空值或中间未出现空格均不过滤】
 * @param string|array $s 目标字符
 * @param int $t 过滤类型 默认1 1转码 0解码
 * @return  string|array
 */
function strip_sql($s, $t = 1)
{
    if (is_array($s)) {
        return array_map('strip_sql', $s);
    } else {
        if (empty($s)) return $s;
        $s = trim($s);
        if ($t) {
            global $_VFILTER;
            if ($_VFILTER) $s = str_ireplace($_VFILTER, '', $s);
            if (strripos($s, ' ') == 0) return $s;
            $p = 'vt_';
            $s = preg_replace("/\/\*([\s\S]*?)\*\//", "", $s);
            $s = preg_replace("/0x([a-f0-9]{2,})/i", '0&#120;\\1', $s);
            $s = preg_replace_callback("/(select|update|replace|delete|drop)([\s\S]*?)(" . $p . "|from)/i", 'strip_wd', $s);
            $s = preg_replace_callback("/(load_file|substring|substr|reverse|trim|space|left|right|mid|lpad|concat|concat_ws|make_set|ascii|bin|oct|hex|ord|char|conv)([^a-z]?)\(/i", 'strip_wd', $s);
            $s = preg_replace_callback("/(union|where|having|outfile|dumpfile|" . $p . ")/i", 'strip_wd', $s);
            return $s;
        } else {
            return str_replace(['&#95;', '&#100;', '&#101;', '&#103;', '&#105;', '&#109;', '&#110;', '&#112;', '&#114;', '&#115;', '&#116;', '&#118;', '&#120;'], ['_', 'd', 'e', 'g', 'i', 'm', 'n', 'p', 'r', 's', 't', 'v', 'x'], $s);
        }
    }
}

/**
 * 过滤扩展初始化，需要扩展过滤时前置于方法strip_sql调用该方法
 * @param string $filter 自定义过滤的字符串多个逗号,隔开，默认按系统配置 sys_filter 设置进行过滤
 * @global  array $_VFILTER
 */
function strip_sql_extend($filter = '')
{
    global $_VFILTER;
    $filter = $filter ?: vconfig('sys_filter');
    $_VFILTER = $_VFILTER ?: ($filter ? explode(',', $filter) : []);
}

/**
 * 转ASCII码 (以上方法 strip_sql 中用于转换ASCII码仿SQL注入)
 * @param array $m 对象
 * @return  string
 */
function strip_wd($m)
{
    if (is_array($m) && isset($m[1])) {
        $wd = substr($m[1], 0, -1) . '&#' . ord(strtolower(substr($m[1], -1))) . ';';
        if (isset($m[3])) return $wd . $m[2] . $m[3];
        if (isset($m[2])) return $wd . $m[2] . '(';
        return $wd;
    }
    return '';
}

/**
 * HTML过滤
 * @param string|array $str 目标
 * @param int $low 级别 默认1全过滤，0简单标签过滤
 * @return  string|array
 */
function strip_html($str, $low = 1)
{
    if (is_array($str)) {
        return array_map('strip_html', $str, ['low' => $low]);
    } elseif (!empty($str)) {
        $str = htmlspecialchars_decode(trim($str));
        $str = strip_tags($str);
        if ($low) {
            $str = str_replace(['"', "\\", "'", "/", "..", "../", "./", "//"], '', $str);
            $no = '/<!--.*-->/';
            $str = preg_replace("$no", '', $str);
            $no = '/%0[0-8bcef]/';
            $str = preg_replace($no, '', $str);
            $no = '/%1[0-9a-f]/';
            $str = preg_replace($no, '', $str);
            $no = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';
            $str = preg_replace($no, '', $str);
        }
    }
    return $str;
}

/**
 * 转换HTML实体(双引号、单引号均转换)
 * @param string $s 目标字符
 * @return  string
 */
function vhtmlspecialchars($s)
{
    if (is_array($s)) {
        return array_map('vhtmlspecialchars', $s);
    } else {
        $s = htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
        return str_replace('&amp;', '&', $s);
    }
}

/**
 * 数字格式转换
 * @param float $v 数值
 * @param int $p 小数点后位数
 * @param bool $s 是否格式化为字符串
 * @return   float|string
 */
function dround($v, $p = 2, $s = false)
{
    $v = round(floatval($v), $p);
    if ($s) $v = sprintf('%.' . $p . 'f', $v);
    return $v;
}

/**
 * 获取扩展名
 * @param string $f 文件路径串
 * @return   string
 */
function file_ext($f)
{
    if (strpos($f, '.') === false) return '';
    $ext = strtolower(trim(substr(strrchr($f, '.'), 1)));
    return preg_match("/^[a-z0-9]{1,10}$/", $ext) ? $ext : '';
}

/**
 * 删除文件夹
 * @param string $dirname 目录
 * @param bool $self 是否删除自身
 * @return bool
 */
function rmdirs($dirname, $self = true)
{
    if (!is_dir($dirname)) {
        return false;
    }
    $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirname, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
    foreach ($files as $fileinfo) {
        $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
        $todo($fileinfo->getRealPath());
    }
    if ($self) {
        @rmdir($dirname);
    }
    return true;
}

/**
 * 复制文件夹
 * @param string $source 源文件夹
 * @param string $dest 目标文件夹
 */
function copydirs($source, $dest)
{
    if (!is_dir($dest)) {
        mkdir($dest, 0755, true);
    }
    $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
    foreach ($iterator as $item) {
        if ($item->isDir()) {
            $sontDir = $dest . VT_DS . $iterator->getSubPathName();
            if (!is_dir($sontDir)) {
                mkdir($sontDir, 0755, true);
            }
        } else {
            copy($item, $dest . VT_DS . $iterator->getSubPathName());
        }
    }
}

/**
 * 移除空目录
 * @param string $dir 目录
 * @return
 */
function remove_empty_folder($dir)
{
    try {
        $isDirEmpty = !(new \FilesystemIterator($dir))->valid();
        if ($isDirEmpty) {
            @rmdir($dir);
            remove_empty_folder(dirname($dir));
        }
    } catch (\UnexpectedValueException $e) {
    } catch (\Exception $e) {
    }
}

/**
 * 键串转换键值串
 * @param string $ids 键串
 * @param array $arr 数组
 * @return  string
 */
function idstoname($ids, $arr)
{
    $str = '';
    $a = explode(',', $ids);
    foreach ($a as $i) {
        $t = $arr[$i] ?? '';
        if ($t) $str .= $str ? '，' . $t : $t;
    }
    return $str;
}

/**
 * 获取站点配置
 * @param string $name 配置键【支持:域1.域2，插件配置获取:@插件名.键名】
 * @param string $default 缺省值
 * @return array|string
 */
function vconfig($name = '', $default = '')
{
    static $_VCF = [];
    $_VCF = $_VCF ?: \app\model\system\SystemSetting::cache();
    if ($name) {
        if (isset($_VCF[$name])) {
            $rs = $_VCF[$name];
        } else {
            $dt = explode('.', $name);
            if (isset($_VCF[$dt[0]])) {
                $rs = $_VCF[$dt[0]];
                if (isset($dt[1])) {
                    $rs = $rs[$dt[1]] ?? $default;
                    $_VCF[$dt[0] . '.' . $dt[1]] = $rs;
                }
            } else {
                $rs = $default;
                $_VCF[$dt[0]] = $rs;
            }
        }
        return $rs || $rs == '0' || $rs === 0 ? $rs : $default;
    } else {
        return $_VCF;
    }
}

/**
 * 时间格式
 * @param int $time 时间戳
 * @return string
 */
function show_time($time)
{
    $rtime = date("Y-m-d H:i", $time);
    $time = time() - $time;
    if ($time < 60) {
        $str = '刚刚';
    } elseif ($time < 3600) {
        $str = floor($time / 60) . '分钟前';
    } elseif ($time < 86400) {
        $str = floor($time / 3600) . '小时前';
    } elseif ($time < 259200) {
        $str = floor($time / 86400) == 1 ? '昨天' : '前天';
    } else {
        $str = $rtime;
    }
    return $str;
}

/**
 * 配置项解析 Setting 后台设置模块中用到
 * @param string $value 配置值
 * @return  array|string
 */
function parse_attr($value = '')
{
    $array = preg_split('/[,;\r\n]+/', trim($value, ",;\r\n"));
    if (strpos($value, ':')) {
        $value = [];
        foreach ($array as $val) {
            list($k, $v) = explode(':', $val);
            $value[$k] = $v;
        }
    } else {
        $value = $array;
    }
    return $value;
}

/**
 * 获取所有子类ID
 * @param int $pid 上级ID
 * @param array $box 数据源
 * @param string $ikey ID键
 * @param string $pkey 上级键
 * @return string  ID串
 */
function get_subclass($pid, $box, $ikey = 'id', $pkey = 'pid')
{
    $str = '';
    foreach ($box as $item) {
        if ($item[$pkey] == $pid) {
            $str .= $str ? ',' . $item[$ikey] : $item[$ikey];
            $get = get_subclass($item[$ikey], $box, $ikey, $pkey);
            if ($get) {
                $str .= ',' . $get;
            }
        }
    }
    return $str;
}

/**
 * 小地区往上查询
 * @param int $areaid 地区ID
 * @param string $str 分隔符
 * @param int $deep 查找深度
 * @param int $start 查找开始
 * @return  bool
 */
function area_pos($areaid, $str = ' &raquo; ', $deep = 0, $start = 0)
{
    $areaid = intval($areaid);
    $area = \app\model\system\SystemArea::cache();
    if (!$areaid || !$area) return '';
    $arrparentid = $area[$areaid]['arrparentid'] ? explode(',', $area[$areaid]['arrparentid']) : [];
    $arrparentid[] = $areaid;
    $pos = '';
    if ($deep) $i = 1;
    $j = 0;
    foreach ($arrparentid as $areaid) {
        if (!$areaid || !isset($area[$areaid])) continue;
        if ($j++ < $start) continue;
        if ($deep) {
            if ($i > $deep) continue;
            $i++;
        }
        $pos .= $area[$areaid]['areaname'] . $str;
    }
    $_len = strlen($str);
    if ($str && substr($pos, -$_len, $_len) === $str) $pos = substr($pos, 0, strlen($pos) - $_len);
    return $pos;
}

/**
 * 多级列表构造（有递归）
 * @param array $rs 所有菜单数组集
 * @param int $pid 开始的父级ID
 * @param array $key 3要素 ['id','parentid','title'] 顺序不能变
 * @param int $t 填充符
 * @param int $j 层级数
 * @param string $s 缩进符
 * @param array $ids 某id键的子类个数集
 * @param array $arr 返回的父子重构顺序集 相对 $rs 多了 new_title 键
 * @return array
 */
function list_tree($rs = [], $pid = 0, $key = ['id', 'parentid', 'title'], $t = 1, $j = 0, $s = '', $ids = [], $arr = [])
{
    if (empty($rs)) return $arr;
    $ids = empty($ids) ? array_count_values(array_column($rs, $key[1])) : $ids;
    $i = 1;
    $k = $j;
    $a = $s;
    if ($t == 1) {
        $c = ["&nbsp;&nbsp;&nbsp;│ ", '&nbsp;&nbsp;&nbsp;├─ ', '&nbsp;&nbsp;&nbsp;└─ ', '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'];
    } else {
        $c = [" - ", " - ", " - ", " - "];
    }
    foreach ($rs as $v) {
        if ($pid == $v[$key[1]]) {
            $n = '';
            if ($k > 0) {
                $n = ($ids[$pid] == $i) ? $a . $c[2] : $a . $c[1];
                $s = ($ids[$pid] == $i) ? $a . $c[3] : $a . $c[0];
            }
            $v['new_title'] = $n . $v[$key[2]];
            $arr[] = $v;
            $id = $v[$key[0]];
            if (isset($ids[$id])) {
                $j = $k + 1;
                $arr = list_tree($rs, $id, $key, $t, $j, $s, $ids, $arr);
            }
            $i++;
        }
    }
    return $arr;
}

/**
 * 生成单据号
 * @param string $code 单据标识
 * @param string $spr 拼接范式
 * @param string $start 起始范式
 * @return string
 */
function build_bill_no(string $code, string $spr = "-%03d", string $start = "-001")
{
    $day = strtotime(date('Y-m-d'));
    $rs = \think\facade\Db::name('system_sequence')->where('code', $code)->find();
    if ($rs) {
        $str = $rs['prefix'] . date("Ymd", $day);
        if ($rs['day'] == $day) {
            $str .= sprintf($spr, $rs['seq']);
            $rs['seq']++;
        } else {
            $str .= $start;
            $rs['seq'] = 2;
            $rs['day'] = $day;
        }
        \think\facade\Db::name('system_sequence')->save($rs);
        return $str;
    } else {
        return '??' . date("Ymd", $day) . '001';
    }
}