<?php

/**
 * @copyright (C)2016-2099 Hnaoyun Inc.
 * @author XingMeng
 * @email hnxsh@foxmail.com
 * @date 2017年11月5日
 *
 */
use core\basic\Config;

// 获取用户浏览器类型
function get_user_bs($bs = null)
{
    if (isset($_SERVER["HTTP_USER_AGENT"])) {
        $user_agent = strtolower($_SERVER["HTTP_USER_AGENT"]);
    } else {
        return null;
    }
    
    // 直接检测传递的值
    if ($bs) {
        if (strpos($user_agent, strtolower($bs))) {
            return true;
        } else {
            return false;
        }
    }
    
    // 固定检测
    if (strpos($user_agent, 'micromessenger')) {
        $user_bs = 'Weixin';
    } elseif (strpos($user_agent, 'qq')) {
        $user_bs = 'QQ';
    } elseif (strpos($user_agent, 'weibo')) {
        $user_bs = 'Weibo';
    } elseif (strpos($user_agent, 'alipayclient')) {
        $user_bs = 'Alipay';
    } elseif (strpos($user_agent, 'trident/7.0')) {
        $user_bs = 'IE11'; // 新版本IE优先，避免360等浏览器的兼容模式检测错误
    } elseif (strpos($user_agent, 'trident/6.0')) {
        $user_bs = 'IE10';
    } elseif (strpos($user_agent, 'trident/5.0')) {
        $user_bs = 'IE9';
    } elseif (strpos($user_agent, 'trident/4.0')) {
        $user_bs = 'IE8';
    } elseif (strpos($user_agent, 'msie 7.0')) {
        $user_bs = 'IE7';
    } elseif (strpos($user_agent, 'msie 6.0')) {
        $user_bs = 'IE6';
    } elseif (strpos($user_agent, 'edge')) {
        $user_bs = 'Edge';
    } elseif (strpos($user_agent, 'firefox')) {
        $user_bs = 'Firefox';
    } elseif (strpos($user_agent, 'chrome') || strpos($user_agent, 'android')) {
        $user_bs = 'Chrome';
    } elseif (strpos($user_agent, 'safari')) {
        $user_bs = 'Safari';
    } elseif (strpos($user_agent, 'mj12bot')) {
        $user_bs = 'MJ12bot';
    } else {
        $user_bs = 'Other';
    }
    return $user_bs;
}

// 获取用户操作系统类型
function get_user_os($osstr = null)
{
    if (isset($_SERVER["HTTP_USER_AGENT"])) {
        $user_agent = strtolower($_SERVER["HTTP_USER_AGENT"]);
    } else {
        return null;
    }
    
    // 直接检测传递的值
    if ($osstr) {
        if (strpos($user_agent, strtolower($osstr))) {
            return true;
        } else {
            return false;
        }
    }
    
    if (strpos($user_agent, 'windows nt 5.0')) {
        $user_os = 'Windows 2000';
    } elseif (strpos($user_agent, 'windows nt 9')) {
        $user_os = 'Windows 9X';
    } elseif (strpos($user_agent, 'windows nt 5.1')) {
        $user_os = 'Windows XP';
    } elseif (strpos($user_agent, 'windows nt 5.2')) {
        $user_os = 'Windows 2003';
    } elseif (strpos($user_agent, 'windows nt 6.0')) {
        $user_os = 'Windows Vista';
    } elseif (strpos($user_agent, 'windows nt 6.1')) {
        $user_os = 'Windows 7';
    } elseif (strpos($user_agent, 'windows nt 6.2')) {
        $user_os = 'Windows 8';
    } elseif (strpos($user_agent, 'windows nt 6.3')) {
        $user_os = 'Windows 8.1';
    } elseif (strpos($user_agent, 'windows nt 10')) {
        $user_os = 'Windows 10';
    } elseif (strpos($user_agent, 'windows phone')) {
        $user_os = 'Windows Phone';
    } elseif (strpos($user_agent, 'android')) {
        $user_os = 'Android';
    } elseif (strpos($user_agent, 'iphone')) {
        $user_os = 'iPhone';
    } elseif (strpos($user_agent, 'ipad')) {
        $user_os = 'iPad';
    } elseif (strpos($user_agent, 'mac')) {
        $user_os = 'Mac';
    } elseif (strpos($user_agent, 'sunos')) {
        $user_os = 'Sun OS';
    } elseif (strpos($user_agent, 'bsd')) {
        $user_os = 'BSD';
    } elseif (strpos($user_agent, 'ubuntu')) {
        $user_os = 'Ubuntu';
    } elseif (strpos($user_agent, 'linux')) {
        $user_os = 'Linux';
    } elseif (strpos($user_agent, 'unix')) {
        $user_os = 'Unix';
    } else {
        $user_os = 'Other';
    }
    return $user_os;
}

// 获取用户IP
function get_user_ip(): string
{
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $cip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
        $cip = $_SERVER['HTTP_CLIENT_IP'];
    } else {
        $cip = $_SERVER['REMOTE_ADDR'];
    }
    if ($cip == '::1') { // 使用localhost时
        $cip = '127.0.0.1';
    }
    if (! preg_match('/^[0-9\.]+$/', $cip)) { // 非标准的IP
        $cip = '0.0.0.0';
    }
    return htmlspecialchars($cip);
}

// 执行URL请求，并返回数据
function get_url($url, $fields = array(), $UserAgent = null, $vfSSL = false)
{
    $SSL = substr($url, 0, 8) == "https://" ? true : false;
    
    $ch = curl_init();
    if ($UserAgent) { // 在HTTP请求中包含一个"User-Agent: "头的字符串。
        curl_setopt($ch, CURLOPT_USERAGENT, $UserAgent);
    } else {
        curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER["HTTP_USER_AGENT"]);
    }
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60); // 在发起连接前等待的时间，如果设置为0，则无限等待
    curl_setopt($ch, CURLOPT_TIMEOUT, 90); // 设置cURL允许执行的最长秒数
    curl_setopt($ch, CURLOPT_URL, $url); // 设置请求地址
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 设置cURL 参数，要求结果保存到字符串中还是输出到屏幕上。
                                                 
    // SSL验证
    if ($SSL) {
        if ($vfSSL) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
            curl_setopt($ch, CURLOPT_CAINFO, CORE_PATH . '/cacert.pem');
        } else {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // 不检查证书中是否设置域名
        }
    }
    
    // 数据字段
    if ($fields) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
    }
    
    $output = curl_exec($ch);
    if (curl_errno($ch)) {
        error('请求远程地址错误：' . curl_error($ch));
    }
    curl_close($ch);
    return $output;
}

// 返回时间戳格式化日期时间，默认当前
function get_datetime($timestamp = null)
{
    if (! $timestamp)
        $timestamp = time();
    return date('Y-m-d H:i:s', $timestamp);
}

// 返回时间戳格式化日期，默认当前
function get_date($timestamp = null)
{
    if (! $timestamp)
        $timestamp = time();
    return date('Y-m-d', $timestamp);
}

// 返回时间戳差值部分，年、月、日
function get_date_diff($startstamp, $endstamp, $return = 'm')
{
    $y = date('Y', $endstamp) - date('Y', $startstamp);
    $m = date('m', $endstamp) - date('m', $startstamp);
    
    switch ($return) {
        case 'y':
            if ($y <= 1) {
                $y = $m / 12;
            }
            $string = $y;
            break;
        case 'm':
            $string = $y * 12 + $m;
            break;
        case 'd':
            $string = ($endstamp - $startstamp) / 86400;
            break;
    }
    return $string;
}

// 生成无限极树,$data为二维数组数据
function get_tree($data, $tid, $idField, $pidField, $sonName = 'son')
{
    $tree = array();
    foreach ($data as $key => $value) {
        if (is_array($value)) {
            if ($value[$pidField] == "$tid") { // 父亲找到儿子
                $value[$sonName] = get_tree($data, $value[$idField], $idField, $pidField, $sonName);
                $tree[] = $value;
            }
        } else {
            if ($value->$pidField == "$tid") { // 父亲找到儿子
                $temp = clone $value;
                $temp->$sonName = get_tree($data, $value->$idField, $idField, $pidField, $sonName);
                $tree[] = $temp;
            }
        }
    }
    return $tree;
}

// 获取数据数组的映射数组
function get_mapping($array, $vValue, $vKey = null)
{
    if (! $array)
        return array();
    foreach ($array as $key => $value) {
        if (is_array($value)) {
            if ($vKey) {
                $result[$value[$vKey]] = $value[$vValue];
            } else {
                $result[] = $value[$vValue];
            }
        } elseif (is_object($value)) {
            if ($vKey) {
                $result[$value->$vKey] = $value->$vValue;
            } else {
                $result[] = $value->$vValue;
            }
        } else {
            return $array;
        }
    }
    return $result;
}

// 页码赋值，异常返回1
function get_page()
{
    if (isset($_GET['page'])) {
        $value = trim($_GET['page']);
        if (preg_match('/^[0-9]+$/', $value)) {
            return $value;
        }
    }
    return 1;
}

// 返回请求类型
function get_request_method()
{
    return $_SERVER['REQUEST_METHOD'];
}

// 获取当前完整URL地址
function get_current_url()
{
    $http_type = is_https() ? 'https://' : 'http://';
    return $http_type . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
}

// 获取字符串第N次出现位置
function get_strpos($string, $find, $n)
{
    $pos = strpos($string, $find);
    for ($i = 2; $i <= $n; $i ++) {
        $pos = strpos($string, $find, $pos + 1);
    }
    return $pos;
}

// array_column向下兼容低版本PHP
if (! function_exists('array_column')) {

    function array_column($input, $columnKey, $indexKey = null)
    {
        $columnKeyIsNumber = (is_numeric($columnKey)) ? true : false;
        $indexKeyIsNull = (is_null($indexKey)) ? true : false;
        $indexKeyIsNumber = (is_numeric($indexKey)) ? true : false;
        $result = array();
        foreach ((array) $input as $key => $row) {
            if ($columnKeyIsNumber) {
                $tmp = array_slice($row, $columnKey, 1);
                $tmp = (is_array($tmp) && ! empty($tmp)) ? current($tmp) : null;
            } else {
                $tmp = isset($row[$columnKey]) ? $row[$columnKey] : null;
            }
            if (! $indexKeyIsNull) {
                if ($indexKeyIsNumber) {
                    $key = array_slice($row, $indexKey, 1);
                    $key = (is_array($key) && ! empty($key)) ? current($key) : null;
                    $key = is_null($key) ? 0 : $key;
                } else {
                    $key = isset($row[$indexKey]) ? $row[$indexKey] : 0;
                }
            }
            $result[$key] = $tmp;
        }
        return $result;
    }
}

/**
 * 系统信息弹出解析函数
 *
 * @param string $info_tpl模板
 * @param string $string内容
 * @param string $jump_url跳转地址
 * @param number $time时间
 */
function parse_info_tpl($info_tpl, $string, $jump_url = null, $time = 0)
{
    if (file_exists($info_tpl)) {
        $tpl_content = file_get_contents($info_tpl);
        if ($jump_url) {
            $timeout_js = "<script>var timeout = {time};var showbox = document.getElementById('time');show();function show(){showbox.innerHTML = timeout+ ' 秒后自动跳转';timeout--;if (timeout == 0) {window.location.href = '{url}';}else {setTimeout(function(){show();}, 1000);}}</script>";
        } else {
            $timeout_js = '';
        }
        $tpl_content = str_replace('{js}', $timeout_js, $tpl_content);
        $tpl_content = str_replace('{info}', $string, $tpl_content);
        $tpl_content = str_replace('{url}', $jump_url, $tpl_content);
        $tpl_content = str_replace('{time}', $time, $tpl_content);
        $tpl_content = str_replace('{sitedir}', SITE_DIR, $tpl_content);
        $tpl_content = str_replace('{coredir}', CORE_DIR, $tpl_content);
        $tpl_content = str_replace('{appversion}', APP_VERSION . '-' . RELEASE_TIME, $tpl_content);
        $tpl_content = str_replace('{serveros}', PHP_OS, $tpl_content);
        $tpl_content = str_replace('{serversoft}', $_SERVER['SERVER_SOFTWARE'], $tpl_content);
        return $tpl_content;
    } else {
        exit('<div style="font-size:50px;">:(</div>提示信息的模板文件不存在！');
    }
}

// 获取转义数据，支持字符串、数组、对象
function escape_string($string)
{
    if (! $string)
        return $string;
    if (is_array($string)) { // 数组处理
        foreach ($string as $key => $value) {
            $string[$key] = escape_string($value);
        }
    } elseif (is_object($string)) { // 对象处理
        foreach ($string as $key => $value) {
            $string->$key = escape_string($value);
        }
    } else { // 字符串处理
        ## 防止跨站脚本攻击 (XSS)
        $string = htmlspecialchars(trim($string), ENT_QUOTES, 'UTF-8');
        ## 防止SQL注入攻击
        $string = addslashes($string);
    }
    return $string;
}

// 解析 fuzzy 参数（0/false/off/no 为精确匹配，其余为模糊匹配；未传时返回 $default）
function parse_fuzzy_param($value, $default = null)
{
    if ($value === null || $value === '') {
        return $default;
    }
    return ! in_array(strtolower((string) $value), array('0', 'false', 'off', 'no'), true);
}

// 构建单标签 SQL 条件（逗号分隔标签集合语义）
function build_tags_where($value, $fuzzy = false)
{
    $value = escape_string(trim($value));
    if (! $value) {
        return '';
    }
    if ($fuzzy) {
        return "a.tags like '%" . $value . "%'";
    }
    return "(a.tags='" . $value . "' OR a.tags like '" . $value . ",%' OR a.tags like '%," . $value . "' OR a.tags like '%," . $value . ",%')";
}

// 字符反转义html实体及斜杠，支持字符串、数组、对象
function decode_string($string)
{
    if (! $string)
        return $string;
    if (is_array($string)) { // 数组处理
        foreach ($string as $key => $value) {
            $string[$key] = decode_string($value);
        }
    } elseif (is_object($string)) { // 对象处理
        foreach ($string as $key => $value) {
            $string->$key = decode_string($value);
        }
    } else { // 字符串处理
        $string = stripcslashes($string);
        $string = htmlspecialchars_decode($string, ENT_QUOTES);
        $string = preg_replace_r('/pboot:if/i', 'pboot@if', $string); // 避免解码绕过问题
    }
    return $string;
}

// 清洗 CSS 文本中的危险声明（用于 <style> 块与 style 属性）
function sanitize_css($css)
{
    if (! $css || ! is_string($css))
        return $css;

    // @import 外链样式
    $css = preg_replace('/@import\s+[^;}\n]+;?/i', '', $css);

    // IE expression()，支持一层嵌套括号；畸形括号时避免死循环
    $prev = null;
    while ($prev !== $css && preg_match('/expression\s*\(/i', $css)) {
        $prev = $css;
        $css = preg_replace('/expression\s*\((?:[^()]|\([^()]*\))*\)/i', '', $css);
        if ($prev === $css) {
            $css = preg_replace('/expression\s*\([^;}\n]*/i', '', $css);
            break;
        }
    }
    // expression 清除后的空属性/残留括号
    $css = preg_replace('/[a-z_-][\w-]*\s*:\s*(?=[;}])/i', '', $css);
    $css = preg_replace('/\{\s*\}/', '', $css);

    // IE behavior: url()
    $css = preg_replace('/behavior\s*:\s*url\s*\([^)]*\)/i', '', $css);

    // url(javascript:) / url(vbscript:)
    $css = preg_replace('/url\s*\(\s*["\']?\s*javascript\s*:[^)]*\)/i', '', $css);
    $css = preg_replace('/url\s*\(\s*["\']?\s*vbscript\s*:[^)]*\)/i', '', $css);

    // 旧版 Firefox -moz-binding
    $css = preg_replace('/-moz-binding\s*:[^;}]*/i', '', $css);

    return $css;
}

// 移除富文本中针对整站布局的全局劫持规则（保留文章局部 class 样式）
function filter_css_global_hijack_rules($css)
{
    if (! $css || ! is_string($css))
        return $css;

    // 先剔除 display:none 劫持（[^{}]+ 避免把规则体内的 } 误当作选择器边界）
    $css = preg_replace_callback('/([^{}]+)\{([^}]*)\}/is', function ($m) {
        $selector = trim($m[1]);
        $declarations = $m[2];
        if (preg_match('/display\s*:\s*none/i', $declarations) &&
            preg_match('/\b(?:body|html|header|nav|footer|#header)\b/i', $selector)) {
            return '';
        }
        // 裸 a / a:pseudo / a, ... 会影响整页导航与所有外链（富文本 <style> 为全局生效）
        if (preg_match('/^\s*a(?:\s*:[\w-]+)?\s*(?:,|$)/i', $selector) &&
            ! preg_match('/^\s*a[.#\[]/i', $selector)) {
            return '';
        }
        // 通配符劫持
        if (preg_match('/^\s*\*[\s,:#.[]/i', $selector) || preg_match('/^\s*\*\s*$/', $selector)) {
            return '';
        }
        return $m[0];
    }, $css);

    // body/html 伪元素全屏遮罩
    $css = preg_replace('/\b(?:body|html)\s*::\s*(?:before|after)\s*\{[^{}]*\}/is', '', $css);

    // 兜底：剔除残留 body/html display:none（防止规则被破坏后漏网）
    $css = preg_replace('/\b(?:body|html)\s*\{[^}]*display\s*:\s*none[^}]*\}/is', '', $css);

    // 清理空声明与空规则块
    $css = preg_replace('/[a-z_-][\w-]*\s*:\s*(?=[;}])/i', '', $css);
    $css = preg_replace('/[^{};,@\s][^{}]*\{\s*\}/', '', $css);

    return $css;
}

// 过滤 style 属性值，危险内容剔除后尽量保留合法声明
function filter_inline_style_attr($css)
{
    $css = filter_css_global_hijack_rules(sanitize_css($css));
    // 折叠多余分号与空白
    $css = preg_replace('/;\s*;/', ';', $css);
    return trim($css, " \t\n\r\0\x0B;");
}

// 后台 UEditor <script type="text/plain"> 容器输出：解码 + 防止 </script> 破出页面
// 不在此处过滤 iframe：编辑器内容会在保存时原样回写数据库，任何清洗都等于永久删除
// 为 iframe 无引号 src 补双引号，避免 UEditor htmlparser 在 / 处截断（如仅剩 https:）
function normalize_iframe_unquoted_src($html)
{
    if (! is_string($html) || $html === '' || stripos($html, '<iframe') === false) {
        return $html;
    }
    return preg_replace_callback('/<iframe\b([^>]*)>/i', function ($m) {
        $attrs = $m[1];
        if (! preg_match('/\bsrc\s*=\s*(?![\'"])[^\s>]/i', $attrs)) {
            return $m[0];
        }
        $newAttrs = preg_replace_callback(
            '/\bsrc\s*=\s*(?![\'"])([^\s>]+)/i',
            function ($srcMatch) {
                return 'src="' . htmlspecialchars($srcMatch[1], ENT_QUOTES, 'UTF-8') . '"';
            },
            $attrs
        );
        return '<iframe' . $newAttrs . '>';
    }, $html);
}

// 后台富文本入库前规范化：解码 → iframe 无引号 src 补引号 → 再转义
function normalize_richtext_for_storage($content)
{
    if (! is_string($content) || $content === '') {
        return $content;
    }
    return escape_string(normalize_iframe_unquoted_src(decode_string($content)));
}

function ueditor_holder_html($html)
{
    if (! $html || ! is_string($html)) {
        return $html;
    }
    $html = decode_string($html);
    $html = normalize_iframe_unquoted_src($html);
    // HTML 解析器会无视 type=text/plain，字面量 </script> 会提前闭合容器
    $html = preg_replace('/<\/script/i', '<\\/script', $html);
    return $html;
}

// 处理富文本中的 iframe：仅放行 content_iframe_whitelist 配置的可信域名，
// 命中后仅重建 src/width/height/title/loading/allowfullscreen 等安全属性，并强制
// sandbox/referrerpolicy；未配置白名单或未命中的 iframe 整段移除（含闭标签与内部 fallback）
function filter_html_iframes($html)
{
    if (stripos($html, '<iframe') === false && stripos($html, '</iframe') === false) {
        return $html;
    }

    // 规范化白名单：仅保留主机名（去协议、路径、端口、末尾点），统一小写
    $raw = Config::get('content_iframe_whitelist', true);
    $whitelist = array();
    if (is_array($raw)) {
        foreach ($raw as $item) {
            $host = filter_iframe_normalize_host($item);
            if ($host !== '') {
                $whitelist[$host] = true;
            }
        }
    }

    // 未配置白名单：保持原有行为，移除所有 iframe（开标签、闭标签及成对内容）
    if (! $whitelist) {
        $html = preg_replace('/<iframe\b[^>]*>.*?<\/iframe>/is', '', $html);
        $html = preg_replace('/<\/?iframe\b[^>]*>/i', '', $html);
        return $html;
    }

    // 命中白名单的 iframe 先重建为安全标签并存入占位符，避免后续清理误伤重建结果
    $placeholders = array();
    $html = preg_replace_callback('/<iframe\b([^>]*)>(.*?)<\/iframe>/is', function ($m) use ($whitelist, &$placeholders) {
        $safe = filter_iframe_rebuild($m[1], $whitelist);
        if ($safe === '') {
            return '';
        }
        $token = "\x01IFRAME_" . count($placeholders) . "\x01";
        $placeholders[$token] = $safe;
        return $token;
    }, $html);

    // 处理未配对的残留开标签（无闭合的畸形 iframe）
    $html = preg_replace_callback('/<iframe\b([^>]*)>/i', function ($m) use ($whitelist, &$placeholders) {
        $safe = filter_iframe_rebuild($m[1], $whitelist);
        if ($safe === '') {
            return '';
        }
        $token = "\x01IFRAME_" . count($placeholders) . "\x01";
        $placeholders[$token] = $safe;
        return $token;
    }, $html);

    // 清理所有残留闭标签，再还原占位符
    $html = preg_replace('/<\/iframe\s*>/i', '', $html);
    if ($placeholders) {
        $html = strtr($html, $placeholders);
    }

    return $html;
}

// 从任意用户输入中提取字面量主机名：支持完整 URL、协议相对 URL、纯域名
function filter_iframe_normalize_host_literal($value)
{
    $value = trim((string) $value);
    if ($value === '') {
        return '';
    }
    // 纯域名（无协议、无斜杠）时补一个协议以便 parse_url 解析
    if (strpos($value, '//') === false && strpos($value, '/') === false) {
        $value = 'http://' . $value;
    } elseif (strpos($value, '//') === 0) {
        $value = 'http:' . $value;
    }
    $host = parse_url($value, PHP_URL_HOST);
    if (! $host) {
        return '';
    }
    $host = rtrim(strtolower($host), '.');
    // host 仅允许合法字符（与 filter_iframe_sanitize_src 一致）
    if (! preg_match('/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/', $host)) {
        return '';
    }
    return $host;
}

// 从白名单配置项中提取主机名或通配 pattern（支持 *.example.com、.example.com）
function filter_iframe_normalize_host($value)
{
    $value = trim((string) $value);
    if ($value === '') {
        return '';
    }

    // Cookie 域写法 .example.com → *.example.com
    if ($value[0] === '.' && strpos($value, '*') === false) {
        $value = '*' . $value;
    }

    // 通配子域 *.example.com（base 须含至少一个点，拒绝 *.com 等单段域误配）
    if (strpos($value, '*.') === 0) {
        $base = filter_iframe_normalize_host_literal(substr($value, 2));
        if ($base === '' || strpos($base, '.') === false) {
            return '';
        }
        return '*.' . $base;
    }

    // 含 * 但非 *. 前缀：非法
    if (strpos($value, '*') !== false) {
        return '';
    }

    return filter_iframe_normalize_host_literal($value);
}

// 将白名单配置串解析为有序 host 列表与命中 map
function filter_iframe_whitelist_parse_hosts($value)
{
    $hosts = array();
    $map = array();
    if (is_string($value) && $value !== '') {
        $value = str_replace("\r\n", ',', $value);
        $value = str_replace('，', ',', $value);
        foreach (explode(',', $value) as $item) {
            $host = filter_iframe_normalize_host($item);
            if ($host !== '' && ! in_array($host, $hosts, true)) {
                $hosts[] = $host;
                $map[$host] = true;
            }
        }
    }
    return array('hosts' => $hosts, 'map' => $map);
}

// 从 iframe 属性串提取最后一个 src（双引号 / 单引号 / 无引号统一规则）
// 自动加白与前台重建共用，避免解析规则漂移；重复 src 时取最后一个
function filter_iframe_extract_src($attr_str)
{
    if (! is_string($attr_str) || $attr_str === '') {
        return '';
    }

    // 无引号用 [^\s>]+：兼容含 ?a=1 的 URL（HTML5 无引号本不含 =，但 CMS/浏览器常见）
    $pattern = '/\bsrc\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|([^\s>]+))/i';
    if (! preg_match_all($pattern, $attr_str, $matches, PREG_SET_ORDER)) {
        return '';
    }

    $match = end($matches);
    if (isset($match[1]) && $match[1] !== '') {
        return trim($match[1]);
    }
    if (isset($match[2]) && $match[2] !== '') {
        return trim($match[2]);
    }
    if (isset($match[3])) {
        return trim($match[3]);
    }

    return '';
}

// 从富文本 HTML 中提取所有 iframe src 的精确 host（去重、小写、不含端口）
function filter_html_extract_iframe_hosts($html)
{
    if (! is_string($html) || $html === '' || stripos($html, '<iframe') === false) {
        return array();
    }
    if (! preg_match_all('/<iframe\b[^>]*>/i', $html, $tags)) {
        return array();
    }
    $found = array();
    foreach ($tags[0] as $tag) {
        $src = filter_iframe_extract_src($tag);
        if ($src === '') {
            continue;
        }
        $src = filter_iframe_sanitize_src($src);
        if ($src === '') {
            continue;
        }
        $parse_src = (strpos($src, '//') === 0) ? 'http:' . $src : $src;
        $host = filter_iframe_normalize_host_literal(parse_url($parse_src, PHP_URL_HOST));
        if ($host !== '' && ! in_array($host, $found, true)) {
            $found[] = $host;
        }
    }
    return $found;
}

// 返回当前白名单（精确或手工通配）尚未覆盖、需要追加的 host 列表
function filter_iframe_whitelist_filter_new_hosts(array $hosts, $current_csv)
{
    $map = filter_iframe_whitelist_parse_hosts($current_csv)['map'];
    $new = array();
    foreach ($hosts as $host) {
        $host = filter_iframe_normalize_host_literal($host);
        if ($host === '' || filter_iframe_host_in_whitelist($host, $map)) {
            continue;
        }
        if (! in_array($host, $new, true)) {
            $new[] = $host;
        }
    }
    return $new;
}

// 将精确 host 幂等合并进白名单逗号串（不推导 *.example.com）
function filter_iframe_whitelist_merge_exact_hosts($current_csv, array $hosts)
{
    $parsed = filter_iframe_whitelist_parse_hosts($current_csv);
    $existing = $parsed['hosts'];
    $map = $parsed['map'];
    $added_hosts = array();

    foreach ($hosts as $host) {
        $host = filter_iframe_normalize_host_literal($host);
        if ($host === '' || filter_iframe_host_in_whitelist($host, $map)) {
            continue;
        }
        $existing[] = $host;
        $map[$host] = true;
        $added_hosts[] = $host;
    }

    return array(
        'ok' => true,
        'added' => count($added_hosts) > 0,
        'added_hosts' => $added_hosts,
        'value' => implode(',', $existing),
    );
}

// 判断 host 是否命中 iframe 白名单（精确 + *.example.com 子域通配，不匹配裸域）
function filter_iframe_host_in_whitelist($host, $whitelist)
{
    if (! is_string($host) || $host === '' || ! is_array($whitelist)) {
        return false;
    }
    if (isset($whitelist[$host])) {
        return true;
    }
    foreach ($whitelist as $pattern => $_) {
        if (strpos($pattern, '*.') !== 0) {
            continue;
        }
        $suffix = substr($pattern, 1);
        $len = strlen($suffix);
        if ($len < 2 || $suffix[0] !== '.') {
            continue;
        }
        if (strlen($host) > $len && substr($host, - $len) === $suffix) {
            return true;
        }
    }
    return false;
}

// 规范化并校验 iframe src，非法则返回空串
function filter_iframe_sanitize_src($src)
{
    if (! is_string($src) || $src === '') {
        return '';
    }

    // HTML 实体解码（防 &#106;avascript: 等绕过）
    $src = html_entity_decode($src, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    $decoded = html_entity_decode($src, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    if ($decoded !== $src) {
        $src = $decoded;
    }

    $src = trim($src);
    if ($src === '') {
        return '';
    }

    // 拒绝控制字符、空字节、换行
    if (preg_match('/[\x00-\x1F\x7F]/', $src)) {
        return '';
    }

    // 拒绝反斜杠（部分环境对 https:\/\/ 解析不一致）
    if (strpos($src, '\\') !== false) {
        return '';
    }

    // 拒绝已知危险 scheme（解码后再判一次）
    if (preg_match('/^\s*(javascript|vbscript|data)\s*:/i', $src)) {
        return '';
    }

    // 协议相对 URL：补 http: 便于 parse_url
    $parse_src = (strpos($src, '//') === 0) ? 'http:' . $src : $src;

    $scheme = parse_url($parse_src, PHP_URL_SCHEME);
    if ($scheme !== null && ! in_array(strtolower($scheme), array('http', 'https'), true)) {
        return '';
    }

    $host = parse_url($parse_src, PHP_URL_HOST);
    if (! $host || strpos($host, '@') !== false) {
        return '';
    }

    // host 仅允许合法字符（字母数字.-，支持 punycode xn--）
    if (! preg_match('/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/i', $host)) {
        return '';
    }

    return $src;
}

// 校验单个 iframe 的属性串，命中白名单则返回重建后的安全标签，否则返回空串
function filter_iframe_rebuild($attr_str, $whitelist)
{
    // 提取 src：优先最后一个（HTML 重复属性时多数浏览器取最后一个）
    $src = filter_iframe_extract_src($attr_str);
    if ($src === '') {
        return '';
    }

    $src = filter_iframe_sanitize_src($src);
    if ($src === '') {
        return '';
    }

    $parse_src = (strpos($src, '//') === 0) ? 'http:' . $src : $src;
    $host = rtrim(strtolower(parse_url($parse_src, PHP_URL_HOST)), '.');
    if (! filter_iframe_host_in_whitelist($host, $whitelist)) {
        return '';
    }

    // 重建：仅保留必要安全属性，其余属性（含 on* 事件）一律丢弃
    $attrs = 'src="' . htmlspecialchars($src, ENT_QUOTES) . '"';

    if (preg_match('/\bwidth\s*=\s*(["\']?)([\d.]+%?)\1/i', $attr_str, $w)) {
        $attrs .= ' width="' . htmlspecialchars($w[2], ENT_QUOTES) . '"';
    }
    if (preg_match('/\bheight\s*=\s*(["\']?)([\d.]+%?)\1/i', $attr_str, $h)) {
        $attrs .= ' height="' . htmlspecialchars($h[2], ENT_QUOTES) . '"';
    }
    if (preg_match('/\btitle\s*=\s*(["\'])(.*?)\1/i', $attr_str, $t)) {
        $attrs .= ' title="' . htmlspecialchars($t[2], ENT_QUOTES) . '"';
    }

    // 强制安全属性；sandbox 下需显式声明 allow-fullscreen 才能全屏
    $attrs .= ' frameborder="0" loading="lazy" referrerpolicy="no-referrer"';
    $attrs .= ' sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-forms allow-fullscreen"';
    $attrs .= ' allowfullscreen';

    return '<iframe ' . $attrs . '></iframe>';
}

// 过滤HTML内容中的危险标签和属性，保留安全的HTML标签
// 用于富文本内容字段（如文章content），允许显示格式化内容但阻止XSS攻击
function filter_html($html)
{
    if (! $html || ! is_string($html))
        return $html;

    // 0. 优先处理 iframe：命中白名单域名的重建为安全属性版本，其余整段移除
    $html = filter_html_iframes($html);

    // 1. 移除所有危险标签（script, object, embed, applet, form, base, meta, link, svg等）
    // 注意：iframe 已在上一步单独处理，不再纳入黑名单，否则会误删白名单 iframe 的闭标签
    $dangerous_tags = array(
        'script', 'object', 'embed', 'applet', 'form', 'input',
        'button', 'select', 'textarea', 'base', 'meta', 'link', 'svg',
        'math', 'noscript', 'template', 'frame', 'frameset', 'body', 'head'
    );
    foreach ($dangerous_tags as $tag) {
        // 移除开标签、闭标签和自闭合标签
        $html = preg_replace('/<' . $tag . '[\s>\/][^>]*>/i', '', $html);
        $html = preg_replace('/<\/' . $tag . '[^>]*>/i', '', $html);
        $html = preg_replace('/<' . $tag . '\s*\/?>/i', '', $html);
    }

    // 2. 移除所有 on 开头的事件属性（onclick, onerror, onload, onmouseover等）
    $html = preg_replace('/\s+on\w+\s*=\s*(["\']?)[^>"\']*\1/i', '', $html);
    // 处理无引号的事件属性
    $html = preg_replace('/\s+on\w+\s*=\s*[^\s>]+/i', '', $html);

    // 3. 移除 javascript: 和 vbscript: 协议
    $html = preg_replace('/href\s*=\s*(["\']?)\s*javascript\s*:[^>"\']*\1/i', 'href="#"', $html);
    $html = preg_replace('/href\s*=\s*(["\']?)\s*vbscript\s*:[^>"\']*\1/i', 'href="#"', $html);
    $html = preg_replace('/src\s*=\s*(["\']?)\s*javascript\s*:[^>"\']*\1/i', '', $html);
    $html = preg_replace('/src\s*=\s*(["\']?)\s*vbscript\s*:[^>"\']*\1/i', '', $html);
    // 处理无引号的协议
    $html = preg_replace('/href\s*=\s*javascript\s*:[^\s>]+/i', 'href="#"', $html);
    $html = preg_replace('/src\s*=\s*javascript\s*:[^\s>]+/i', '', $html);

    // 4. 移除 data: 协议中的危险内容（仅允许图片data URI）
    $html = preg_replace('/src\s*=\s*(["\']?)\s*data\s*:(?!image\/(png|jpeg|jpg|gif|webp|bmp))[^>"\']*\1/i', '', $html);

    // 5. 清洗 <style> 块：保留合法排版，剔除恶意 CSS
    $html = preg_replace_callback('/<style\b([^>]*)>(.*?)<\/style>/is', function ($matches) {
        $attrs = preg_replace('/\s+on\w+\s*=\s*[^\s>]*/i', '', $matches[1]);
        $css = filter_css_global_hijack_rules(sanitize_css($matches[2]));
        $css = trim($css);
        if ($css === '') {
            return '';
        }
        return '<style' . $attrs . '>' . $css . '</style>';
    }, $html);
    // 移除空 <style/> 自闭合标签
    $html = preg_replace('/<style\b[^>]*\/>/i', '', $html);

    // 6. 清洗 style 属性（双引号 / 单引号）
    $html = preg_replace_callback('/\s+style\s*=\s*(")([^"]*)"/is', function ($matches) {
        $css = filter_inline_style_attr($matches[2]);
        return $css === '' ? '' : ' style="' . $css . '"';
    }, $html);
    $html = preg_replace_callback("/\s+style\s*=\s*(')([^']*)'/is", function ($matches) {
        $css = filter_inline_style_attr($matches[2]);
        return $css === '' ? '' : " style='" . $css . "'";
    }, $html);
    // 无引号 style 属性：含危险模式则整段移除
    $html = preg_replace('/\s+style\s*=\s*[^"\'>\s][^>]*(?:expression|@import|behavior\s*:\s*url|javascript\s*:)/i', '', $html);

    // 7. 移除 XML相关危险内容
    $html = preg_replace('/<\?xml[^>]*\?>/i', '', $html);
    $html = preg_replace('/<!\[CDATA\[/i', '', $html);
    $html = preg_replace('/\]\]>/i', '', $html);

    // 8. 移除HTML注释中的条件注释（IE条件注释可执行代码）
    $html = preg_replace('/<!--\[if\s/i', '&lt;!--[if ', $html);
    $html = preg_replace('/<!\[endif\]-->/i', '<![endif]--&gt;', $html);

    return $html;
}

// 字符反转义斜杠，支持字符串、数组、对象
function decode_slashes($string)
{
    if (! $string)
        return $string;
    if (is_array($string)) { // 数组处理
        foreach ($string as $key => $value) {
            $string[$key] = decode_slashes($value);
        }
    } elseif (is_object($string)) { // 对象处理
        foreach ($string as $key => $value) {
            $string->$key = decode_slashes($value);
        }
    } else { // 字符串处理
        $string = stripcslashes($string);
    }
    return $string;
}

// 字符串双层MD5加密
function encrypt_string($string)
{
    return md5(md5($string));
}

// 生成唯一标识符
function get_uniqid()
{
    return encrypt_string(uniqid(mt_rand(), true));
}

// 清洗html代码的空白符号
function clear_html_blank($string)
{
    $string = str_replace("\r\n", '', $string); // 清除换行符
    $string = str_replace("\n", '', $string); // 清除换行符
    $string = str_replace("\t", '', $string); // 清除制表符
    $string = str_replace('　', '', $string); // 清除大空格
    $string = str_replace('&nbsp;', '', $string); // 清除 &nbsp;
    $string = preg_replace('/\s+/', ' ', $string); // 清除空格
    return $string;
}

// 去除字符串两端斜线
function trim_slash($string)
{
    return trim($string, '/');
}

// 驼峰转换下划线加小写字母
function hump_to_underline($string)
{
    return strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $string));
}

// 转换对象为数组
function object_to_array($object)
{
    if($object === null){
        return [];
    }else{
        return json_decode(json_encode($object),true);
    }
}

// 转换数组为对象
function array_to_object($array)
{
    return json_decode(json_encode($array));
}

// 值是否在对象中
function in_object($needle, $object)
{
    foreach ($object as $value) {
        if ($needle == $value)
            return true;
    }
}

// 结果集中查找指定字段父节点是否存在
function result_value_search($needle, $result, $skey)
{
    foreach ($result as $key => $value) {
        if ($value->$skey == $needle) {
            return $key;
        }
    }
    return false;
}

// 多维数组合并
function mult_array_merge($array1, $array2)
{
    if (is_array($array2)) {
        foreach ($array2 as $key => $value) {
            if (is_array($value)) {
                if (array_key_exists($key, $array1)) {
                    $array1[$key] = mult_array_merge($array1[$key], $value);
                } else {
                    $array1[$key] = $value;
                }
            } else {
                $array1[$key] = $value;
            }
        }
    }
    return $array1;
}

// 数组转换为带引号字符串
function implode_quot($glue, array $pieces, $diffnum = false)
{
    if (! $pieces)
        return "''";
    foreach ($pieces as $key => $value) {
        if ($diffnum && ! is_numeric($value)) {
            $value = "'$value'";
        } elseif (! $diffnum) {
            $value = "'$value'";
        }
        if (isset($string)) {
            $string .= $glue . $value;
        } else {
            $string = $value;
        }
    }
    return $string;
}

// 是否为多维数组,是返回true
function is_multi_array($array)
{
    if (is_array($array)) {
        return (count($array) != count($array, 1));
    } else {
        return false;
    }
}

// 是否为移动设备
function is_mobile()
{
    $os = get_user_os();
    if ($os == 'Android' || $os == 'iPhone' || $os == 'Windows Phone' || $os == 'iPad') {
        return true;
    }
}

// 是否为POST请求
function is_post()
{
    if ($_POST) {
        return true;
    } else {
        return false;
    }
}

// 是否为GET请求
function is_get()
{
    if ($_GET) {
        return true;
    } else {
        return false;
    }
}

// 是否为PUT请求
function is_put()
{
    if ($_SERVER['REQUEST_METHOD'] == 'PUT') {
        return true;
    } else {
        return false;
    }
}

// 是否为PATCH请求
function is_patch()
{
    if ($_SERVER['REQUEST_METHOD'] == 'PATCH') {
        return true;
    } else {
        return false;
    }
}

// 是否为DELETE请求
function is_delete()
{
    if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
        return true;
    } else {
        return false;
    }
}

// 是否为AJAX请求
function is_ajax()
{
    if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
        return true;
    } else {
        return false;
    }
}

// 判断当前是否为https
function is_https()
{
    if ((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on')) {
        return true;
    } elseif (isset($_SERVER['REQUEST_SCHEME']) && strtolower($_SERVER['REQUEST_SCHEME']) == 'https') {
        return true;
    } elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') {
        return true;
    } elseif (isset($_SERVER['HTTP_X_CLIENT_SCHEME']) && strtolower($_SERVER['HTTP_X_CLIENT_SCHEME']) == 'https') {
        return true;
    } else {
        return false;
    }
}

// 获取当前访问地址
function get_http_url($noport = false)
{
    if (is_https()) {
        $url = 'https://' . $_SERVER['HTTP_HOST'];
    } else {
        $url = 'http://' . $_SERVER['HTTP_HOST'];
    }
    if ($noport) {
        $url = str_replace(':' . $_SERVER['SERVER_PORT'], '', $url);
    }
    return $url;
}

// 获取当前访问域名
function get_http_host($noport = true)
{
    if ($noport) {
        return str_replace(':' . $_SERVER['SERVER_PORT'], '', $_SERVER['HTTP_HOST']);
    } else {
        return $_SERVER['HTTP_HOST'];
    }
}

// 服务器信息
function get_server_info()
{
    // 定义输出常量
    define('YES', 'Yes');
    define('NO', '<span style="color:red">No</span>');
    
    // 服务器系统
    $data['php_os'] = PHP_OS;
    // 服务器访问地址
    $data['http_host'] = $_SERVER['HTTP_HOST'];
    // 服务器名称
    $data['server_name'] = $_SERVER['SERVER_NAME'];
    // 服务器端口
    $data['server_port'] = $_SERVER['SERVER_PORT'];
    // 服务器地址
    $data['server_addr'] = isset($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR'] : $_SERVER['SERVER_ADDR'];
    // 服务器软件
    $data['server_software'] = $_SERVER['SERVER_SOFTWARE'];
    // 站点目录
    $data['document_root'] = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : DOC_PATH;
    // PHP版本
    $data['php_version'] = PHP_VERSION;
    // 数据库驱动
    $data['db_driver'] = Config::get('database.type');
    // php配置文件
    $data['php_ini'] = @php_ini_loaded_file();
    // 最大上传
    $data['upload_max_filesize'] = ini_get('upload_max_filesize');
    // 最大提交
    $data['post_max_size'] = ini_get('post_max_size');
    // 最大提交文件数
    $data['max_file_uploads'] = ini_get('max_file_uploads');
    // 内存限制
    $data['memory_limit'] = ini_get('memory_limit');
    // 检测gd扩展
    $data['gd'] = extension_loaded('gd') ? YES : NO;
    // 检测imap扩展
    $data['imap'] = extension_loaded('imap') ? YES : NO;
    // 检测socket扩展
    $data['sockets'] = extension_loaded('sockets') ? YES : NO;
    // 检测curl扩展
    $data['curl'] = extension_loaded('curl') ? YES : NO;
    // 会话保存路径
    $data['session_save_path'] = session_save_path() ?: $_SERVER['TMP'];
    // 检测standard库是否存在
    $data['standard'] = extension_loaded('standard') ? YES : NO;
    // 检测多线程支持
    $data['pthreads'] = extension_loaded('pthreads') ? YES : NO;
    // 检测XCache支持
    $data['xcache'] = extension_loaded('XCache') ? YES : NO;
    // 检测APC支持
    $data['apc'] = extension_loaded('APC') ? YES : NO;
    // 检测eAccelerator支持
    $data['eaccelerator'] = extension_loaded('eAccelerator') ? YES : NO;
    // 检测wincache支持
    $data['wincache'] = extension_loaded('wincache') ? YES : NO;
    // 检测ZendOPcache支持
    $data['zendopcache'] = extension_loaded('Zend OPcache') ? YES : NO;
    // 检测memcache支持
    $data['memcache'] = extension_loaded('memcache') ? YES : NO;
    // 检测memcached支持
    $data['memcached'] = extension_loaded('memcached') ? YES : NO;
    // 已经安装模块
    $loaded_extensions = get_loaded_extensions();
    $extensions = '';
    foreach ($loaded_extensions as $key => $value) {
        $extensions .= $value . ', ';
    }
    $data['extensions'] = $extensions;
    return json_decode(json_encode($data));
}

// 获取数据库类型
function get_db_type()
{
    switch (Config::get('database.type')) {
        case 'mysqli':
        case 'pdo_mysql':
            $db = 'mysql';
            break;
        case 'sqlite':
        case 'pdo_sqlite':
            $db = 'sqlite';
            break;
        case 'pdo_pgsql':
            $db = 'pgsql';
            break;
        default:
            $db = null;
    }
    return $db;
}

// 获取间隔的月份的起始及结束日期
function get_month_days($date, $start = 0, $interval = 1, $retamp = false)
{
    $timestamp = strtotime($date) ?: $date;
    $first_day = strtotime(date('Y', $timestamp) . '-' . date('m', $timestamp) . '-01 +' . $start . ' month');
    $last_day = strtotime(date('Y-m-d', $first_day) . ' +' . $interval . ' month -1 day');
    if ($retamp) {
        $return = array(
            'first' => $first_day,
            'last' => $last_day
        );
    } else {
        $return = array(
            'first' => date('Y-m-d', $first_day),
            'last' => date('Y-m-d', $last_day)
        );
    }
    return $return;
}

// 框架地址地址前缀
function url_index_path($indexfile = null)
{
    $indexfile = $indexfile ?: $_SERVER["SCRIPT_NAME"];
    if (Config::get('app_url_type') == 2 && strripos($indexfile, 'index.php') !== false) {
        return SITE_DIR;
    } elseif (Config::get('app_url_type') == 3 && strripos($indexfile, 'index.php') !== false) {
        return SITE_DIR . '/?p=';
    } elseif (Config::get('app_url_type') == 3 && strripos($indexfile, 'index.php') === false) {
        return $indexfile . '?p=';
    } else {
        return $indexfile;
    }
}

// 获取服务端web软件
function get_server_soft()
{
    $soft = strtolower($_SERVER["SERVER_SOFTWARE"]);
    if (strpos($soft, 'iis')) {
        return 'iis';
    } elseif (strpos($soft, 'apache')) {
        return 'apache';
    } elseif (strpos($soft, 'nginx')) {
        return 'nginx';
    } else {
        return 'other';
    }
}

// 创建会话层级目录
function create_session_dir($path, $depth)
{
    if ($depth < 1) {
        return;
    } else {
        $depth --;
    }
    $char = 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'
    );
    
    foreach ($char as $value) {
        if (! check_dir($path . '/' . $value, true)) {
            error('会话目录写入权限不足！');
        }
        create_session_dir($path . '/' . $value, $depth);
    }
}

// 中英混合的字符串截取,以一个汉字为一个单位长度，英文为半个
function substr_both($string, $strat, $length)
{
    $s = 0; // 起始位置
    $i = 0; // 实际Byte计数
    $n = 0; // 字符串长度计数
    $str_length = strlen($string); // 字符串的字节长度
    while (($n < $length) and ($i < $str_length)) {
        $ascnum = Ord(substr($string, $i, 1)); // 得到字符串中第$i位字符的ascii码
        if ($ascnum >= 224) { // 根据UTF-8编码规范，将3个连续的字符计为单个字符
            $i += 3;
            $n ++;
        } elseif ($ascnum >= 192) { // 根据UTF-8编码规范，将2个连续的字符计为单个字符
            $i += 2;
            $n ++;
        } else {
            $i += 1;
            $n += 0.5;
        }
        if ($s == 0 && $strat > 0 && $n >= $strat) {
            $s = $i; // 记录起始位置
        }
    }
    if ($n < $strat) { // 起始位置大于字符串长度
        return;
    }
    return substr($string, $s, $i);
}

// 中英混合的字符串长度,以一个汉字为一个单位长度，英文为半个
function strlen_both($string)
{
    $i = 0; // 实际Byte计数
    $n = 0; // 字符串长度计数
    $str_length = strlen($string); // 字符串的字节长度
    while ($i < $str_length) {
        $ascnum = Ord(substr($string, $i, 1)); // 得到字符串中第$i位字符的ascii码
        if ($ascnum >= 224) { // 根据UTF-8编码规范，将3个连续的字符计为单个字符
            $i += 3;
            $n ++;
        } elseif ($ascnum >= 192) { // 根据UTF-8编码规范，将2个连续的字符计为单个字符
            $i += 2;
            $n ++;
        } else {
            $i += 1;
            $n += 0.5;
        }
    }
    return $n;
}

// 获取地址参数
function query_string($unset = null)
{
    if (isset($_SERVER["QUERY_STRING"]) && ! ! $qs = $_SERVER["QUERY_STRING"]) {
        parse_str($qs, $output);
        unset($output['page']);
        $unset = strpos($unset, ',') ? explode(',', $unset) : $unset;
        
        if (is_array($unset)) {
            foreach ($unset as $value) {
                if (isset($output[$value])) {
                    unset($output[$value]);
                }
            }
        } else {
            if (isset($output[$unset])) {
                unset($output[$unset]);
            }
        }
        // 避免路径参数编码
        if (isset($output['p'])) {
            $p = 'p=' . $output['p'];
            unset($output['p']);
            $qs = $output ? $p . '&' . http_build_query($output) : $p;
        } else {
            $qs = http_build_query($output);
        }
    }
    return $qs ? '?' . $qs : '';
}

// 判断是否在子网
function network_match($ip, $network)
{
    if (strpos($network, '/') > 0) {
        $network = explode('/', $network);
        $move = 32 - $network[1];
        if ($network[1] == 0) {
            return true;
        }
        return ((ip2long($ip) >> $move) === (ip2long($network[0]) >> $move)) ? true : false;
    } elseif ($network == $ip) {
        return true;
    } else {
        return false;
    }
}

// 递归替换
function preg_replace_r($search, $replace, $subject)
{
    while (preg_match($search, $subject)) {
        $subject = preg_replace($search, $replace, $subject);
    }
    return $subject;
}

// 生成随机验证码
function create_code($len = 4)
{
    $charset = 'ABCDEFGHKMNPRTUVWXY23456789';
    $charset = str_shuffle($charset);
    $charlen = strlen($charset) - 1;
    $code = '';
    for ($i = 0; $i < $len; $i ++) {
        $code .= $charset[mt_rand(0, $charlen)];
    }
    return $code;
}
