Files
webman-buildadmin-mall/app/functions.php
2026-03-19 18:07:18 +08:00

670 lines
23 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* BuildAdmin Webman 公共函数
*/
// mb_split 兼容mbstring 扩展未启用时Illuminate 的 Str::studly 会报错,用 preg_split 兜底
if (!function_exists('mb_split')) {
function mb_split(string $pattern, string $string, int $limit = -1): array
{
$result = @preg_split('#' . $pattern . '#u', $string, $limit);
return $result !== false ? $result : [];
}
}
use support\Response;
if (!function_exists('env')) {
/**
* 获取环境变量(兼容 dot 格式如 database.hostname
*/
function env(string $key, mixed $default = null): mixed
{
$value = $_ENV[$key] ?? getenv($key);
if ($value !== false && $value !== null) {
return $value;
}
if (strpos($key, '.') !== false) {
$parts = explode('.', $key);
$upper = strtoupper(implode('_', $parts));
$value = $_ENV[$upper] ?? getenv($upper);
if ($value !== false && $value !== null) {
return $value;
}
}
return $default;
}
}
if (!function_exists('__')) {
/**
* 语言翻译BuildAdmin 兼容)
*/
function __(string $name, array $vars = [], string $lang = ''): mixed
{
if (is_numeric($name) || !$name) {
return $name;
}
return function_exists('trans') ? trans($name, $vars, null, $lang ?: null) : $name;
}
}
use Symfony\Component\HttpFoundation\IpUtils;
if (!function_exists('get_sys_config')) {
/**
* 获取系统配置(从数据库或 config
* 需 Config 模型支持,否则从 config 读取
*/
function get_sys_config(string $name = '', string $group = '', bool $concise = true): mixed
{
if (class_exists(\app\admin\model\Config::class)) {
$configModel = \app\admin\model\Config::class;
if ($name) {
$config = $configModel::cache($name, null, $configModel::$cacheTag)->where('name', $name)->find();
return $config ? $config['value'] : null;
}
if ($group) {
$temp = $configModel::cache('group' . $group, null, $configModel::$cacheTag)->where('group', $group)->select()->toArray();
} else {
$temp = $configModel::cache('sys_config_all', null, $configModel::$cacheTag)->order('weigh desc')->select()->toArray();
}
if ($concise) {
$config = [];
foreach ($temp as $item) {
$config[$item['name']] = $item['value'];
}
return $config;
}
return $temp;
}
return config("sys_config.{$name}", null);
}
}
if (!function_exists('clear_config_cache')) {
/**
* 清理配置缓存Config 写入后调用)
*/
function clear_config_cache(): void
{
$cachePath = base_path() . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR . 'cache';
if (!is_dir($cachePath)) {
return;
}
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($cachePath, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $file) {
if ($file->isFile()) {
@unlink($file->getRealPath());
}
}
}
}
if (!function_exists('ip_check')) {
/**
* IP 检查
* @param string|null $ip 要检查的 IPnull 时从 request 获取
* @param \Webman\Http\Request|null $request
* @return Response|null 禁止访问时返回 Response否则 null
*/
function ip_check(?string $ip = null, $request = null): ?Response
{
if ($ip === null && $request) {
$ip = $request->getRealIp();
}
if (!$ip) {
return null;
}
$noAccess = get_sys_config('no_access_ip');
$noAccess = !$noAccess ? [] : array_filter(explode("\n", str_replace("\r\n", "\n", (string) $noAccess)));
if ($noAccess && IpUtils::checkIp($ip, $noAccess)) {
return response(json_encode(['msg' => 'No permission request']), 403, ['Content-Type' => 'application/json']);
}
return null;
}
}
if (!function_exists('get_auth_token')) {
/**
* 获取鉴权 token
* @param array $names 如 ['ba', 'token']
* @param \Webman\Http\Request|null $request
*/
function get_auth_token(array $names = ['ba', 'token'], $request = null): string
{
$request = $request ?? (function_exists('request') ? request() : null);
if (!$request) {
return '';
}
$separators = [
'header' => ['', '-'],
'param' => ['', '-', '_'],
'server' => ['_'],
];
$tokens = [];
foreach ($separators as $source => $sps) {
foreach ($sps as $sp) {
$key = ($source === 'server' ? 'http_' : '') . implode($sp, $names);
if ($source === 'header') {
$val = $request->header($key);
} elseif ($source === 'param') {
$val = $request->get($key) ?? $request->post($key);
} else {
$val = $_SERVER[$key] ?? null;
}
if ($val) {
$tokens[] = $val;
}
}
}
return $tokens[0] ?? '';
}
}
if (!function_exists('get_agent_jwt_payload')) {
/**
* 解析 Agent JWT authtoken返回 payloadagent_id、channel_id、admin_id 等)
* @param string $token authtoken
* @return array 成功返回 payload失败返回空数组
*/
function get_agent_jwt_payload(string $token): array
{
return \app\common\library\AgentJwt::decode($token);
}
}
if (!function_exists('get_controller_path')) {
/**
* 从 Request 或路由获取控制器路径(等价于 ThinkPHP controllerPath
* 优先从 $request->controllerWebman 路由匹配时设置)解析,否则从 path 解析
* @param \Webman\Http\Request|null $request
* @return string|null 如 auth/admin、user/user
*/
function get_controller_path($request = null): ?string
{
$request = $request ?? (function_exists('request') ? request() : null);
if (!$request) {
return null;
}
// 优先从路由匹配的 controller 解析Webman 在路由匹配后设置)
$controller = $request->controller ?? null;
if ($controller && is_string($controller)) {
foreach (['app\\admin\\controller\\', 'app\\api\\controller\\'] as $prefix) {
if (str_starts_with($controller, $prefix)) {
$relative = substr($controller, strlen($prefix));
$parts = explode('\\', $relative);
$path = [];
foreach ($parts as $p) {
$path[] = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $p));
}
return implode('/', $path);
}
}
}
// 回退:从 path 解析(如 admin/auth/admin/index -> auth/admin
$path = trim($request->path(), '/');
if (!$path) {
return null;
}
$parts = explode('/', $path);
if (count($parts) < 2) {
return $parts[0] ?? null;
}
return implode('/', array_slice($parts, 1, -1)) ?: $parts[1];
}
}
if (!function_exists('action_in_arr')) {
/**
* 检测当前方法是否在数组中(用于 noNeedLogin、noNeedPermission
* @param array $arr
* @param string|null $action 当前 actionnull 时从 request path 解析
*/
function action_in_arr(array $arr, ?string $action = null): bool
{
if (!$arr) {
return false;
}
$arr = array_map('strtolower', $arr);
if (in_array('*', $arr)) {
return true;
}
if ($action === null && function_exists('request')) {
$req = request();
$path = trim($req->path(), '/');
$parts = explode('/', $path);
$action = $parts[array_key_last($parts)] ?? '';
}
return $action ? in_array(strtolower($action), $arr) : false;
}
}
if (!function_exists('event_trigger')) {
/**
* 触发事件BuildAdmin 兼容,替代 Event::trigger
* @param string $event
* @param mixed ...$args
*/
function event_trigger(string $event, mixed ...$args): void
{
$listeners = config("events.listen.{$event}", []);
foreach ($listeners as $listener) {
try {
if (is_string($listener) && class_exists($listener)) {
$instance = new $listener();
if (method_exists($instance, 'handle')) {
$instance->handle(...$args);
}
} elseif (is_callable($listener)) {
$listener(...$args);
}
} catch (\Throwable $e) {
if (class_exists(\support\Log::class)) {
\support\Log::warning("[event_trigger] {$event}: " . $e->getMessage());
}
}
}
}
}
if (!function_exists('set_timezone')) {
/**
* 设置时区
*/
function set_timezone(?string $timezone = null): void
{
$defaultTimezone = config('app.default_timezone', 'Asia/Shanghai');
$timezone = $timezone ?? get_sys_config('time_zone');
if ($timezone && $defaultTimezone !== $timezone) {
date_default_timezone_set($timezone);
}
}
}
if (!function_exists('encrypt_password')) {
/**
* 加密密码(兼容旧版 md5
* @deprecated 使用 hash_password 代替
*/
function encrypt_password(string $password, string $salt = '', string $encrypt = 'md5'): string
{
return $encrypt($encrypt($password) . $salt);
}
}
if (!function_exists('hash_password')) {
/**
* 创建密码散列hash
*/
function hash_password(string $password): string
{
return password_hash($password, PASSWORD_DEFAULT);
}
}
if (!function_exists('verify_password')) {
/**
* 验证密码是否和散列值匹配
*/
function verify_password(string $password, string $hash, array $extend = []): bool
{
if (str_starts_with($hash, '$') || password_get_info($hash)['algoName'] !== 'unknown') {
return password_verify($password, $hash);
}
return encrypt_password($password, $extend['salt'] ?? '') === $hash;
}
}
if (!function_exists('full_url')) {
/**
* 获取资源完整 url 地址
*/
function full_url(string $relativeUrl = '', string|bool $domain = true, string $default = ''): string
{
$cdnUrl = config('buildadmin.cdn_url');
if (!$cdnUrl && function_exists('request')) {
$req = request();
$cdnUrl = $req ? '//' . $req->host() : '//localhost';
} elseif (!$cdnUrl) {
$cdnUrl = '//localhost';
}
if ($domain === true) {
$domain = $cdnUrl;
} elseif ($domain === false) {
$domain = '';
}
$relativeUrl = $relativeUrl ?: $default;
if (!$relativeUrl) {
return $domain;
}
$regex = "/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i";
if (preg_match('/^http(s)?:\/\//', $relativeUrl) || preg_match($regex, $relativeUrl) || $domain === false) {
return $relativeUrl;
}
$url = $domain . $relativeUrl;
$cdnUrlParams = config('buildadmin.cdn_url_params');
if ($domain === $cdnUrl && $cdnUrlParams) {
$separator = str_contains($url, '?') ? '&' : '?';
$url .= $separator . $cdnUrlParams;
}
return $url;
}
}
if (!function_exists('parse_name')) {
/**
* 命名转换ThinkPHP 兼容)
* @param string $name 名称
* @param int $type 0=转小写+下划线 1=驼峰 2=首字母大写驼峰
* @param string $delimiter 分隔符
*/
function parse_name(string $name, int $type = 0, string $delimiter = '_'): string
{
if ($type === 0) {
return strtolower(preg_replace('/([A-Z])/', $delimiter . '$1', lcfirst($name)));
}
if ($type === 1) {
$name = str_replace($delimiter, ' ', $name);
return lcfirst(str_replace(' ', '', ucwords($name)));
}
if ($type === 2) {
$name = str_replace($delimiter, ' ', $name);
return str_replace(' ', '', ucwords($name));
}
return $name;
}
}
if (!function_exists('root_path')) {
/**
* 根路径BuildAdmin 兼容,等价于 base_path
* 无参数时返回带尾部分隔符的路径,确保 root_path() . 'app' 拼接正确
* @param string $path 子路径
*/
function root_path(string $path = ''): string
{
$base = base_path($path);
if ($path === '' && $base !== '') {
return rtrim($base, DIRECTORY_SEPARATOR . '/') . DIRECTORY_SEPARATOR;
}
return $base;
}
}
if (!function_exists('app_path')) {
/**
* 应用目录路径Webman 兼容,用于 CRUD Helper 等)
* @param string $path 子路径
*/
function app_path(string $path = ''): string
{
$base = rtrim(base_path(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'app';
return $path ? $base . DIRECTORY_SEPARATOR . ltrim(str_replace('/', DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR) : $base;
}
}
if (!function_exists('get_controller_list')) {
/**
* 获取控制器文件列表(递归)
* @param string $app 应用名,默认 admin
*/
function get_controller_list(string $app = 'admin'): array
{
$controllerDir = root_path() . 'app' . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR . 'controller' . DIRECTORY_SEPARATOR;
return (class_exists(\ba\Filesystem::class) && is_dir($controllerDir))
? \ba\Filesystem::getDirFiles($controllerDir)
: [];
}
}
if (!function_exists('get_route_remark')) {
/**
* 获取当前路由后台菜单规则的备注信息
* 使用控制器 domain 翻译,以支持不同控制器对同一 key如 Remark lang的不同翻译
*/
function get_route_remark(): string
{
$controllerPath = get_controller_path() ?? '';
$actionName = '';
if (function_exists('request')) {
$req = request();
if ($req) {
$path = trim($req->path(), '/');
$parts = explode('/', $path);
$actionName = $parts[array_key_last($parts)] ?? '';
}
}
$path = str_replace('.', '/', $controllerPath);
$names = [$path];
if ($actionName) {
$names[] = $path . '/' . $actionName;
}
$remark = \support\think\Db::name('admin_rule')
->where('name', 'in', $names)
->value('remark');
$remarkStr = (string) ($remark ?? '');
if (!$remarkStr) {
return '';
}
return function_exists('trans') ? trans($remarkStr, [], $controllerPath ?: null) : $remarkStr;
}
}
if (!function_exists('keys_to_camel_case')) {
function keys_to_camel_case(array $array, array $keys = []): array
{
$result = [];
foreach ($array as $key => $value) {
$camelCaseKey = ($keys && in_array($key, $keys)) ? parse_name($key, 1, '_') : $key;
if (is_array($value)) {
$result[$camelCaseKey] = keys_to_camel_case($value);
} else {
$result[$camelCaseKey] = $value;
}
}
return $result;
}
}
if (!function_exists('get_upload_config')) {
function get_upload_config($request = null): array
{
event_trigger('uploadConfigInit', null);
$uploadConfig = config('upload', []);
$uploadConfig['max_size'] = \ba\Filesystem::fileUnitToByte($uploadConfig['max_size'] ?? '10M');
$request = $request ?? (function_exists('request') ? request() : null);
$upload = $request && isset($request->upload) ? $request->upload : null;
if (!$upload) {
$uploadConfig['mode'] = 'local';
return $uploadConfig;
}
unset($upload['cdn']);
return array_merge($upload, $uploadConfig);
}
}
if (!function_exists('filter')) {
function filter(string $string): string
{
$string = trim($string);
$string = strip_tags($string);
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8');
}
}
if (!function_exists('clean_xss')) {
function clean_xss(string $string): string
{
if (class_exists(\voku\helper\AntiXSS::class)) {
$antiXss = new \voku\helper\AntiXSS();
$antiXss->removeEvilAttributes(['style']);
$antiXss->setReplacement('cleanXss');
return $antiXss->xss_clean($string);
}
return strip_tags($string);
}
}
if (!function_exists('htmlspecialchars_decode_improve')) {
function htmlspecialchars_decode_improve(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401): string
{
return htmlspecialchars_decode($string, $flags);
}
}
if (!function_exists('get_ba_client')) {
/**
* 获取请求 BuildAdmin 开源社区的 Guzzle Client用于云端 CRUD 历史等)
*/
function get_ba_client(): \GuzzleHttp\Client
{
return new \GuzzleHttp\Client([
'base_uri' => config('buildadmin.api_url', 'https://api.buildadmin.com'),
'timeout' => 30,
'connect_timeout' => 30,
'verify' => false,
'http_errors' => false,
'headers' => [
'X-REQUESTED-WITH' => 'XMLHttpRequest',
'User-Agent' => 'BuildAdminClient',
]
]);
}
}
if (!function_exists('str_attr_to_array')) {
function str_attr_to_array(string $attr): array
{
if (!$attr) return [];
$attr = explode("\n", trim(str_replace("\r\n", "\n", $attr)));
$attrTemp = [];
foreach ($attr as $item) {
$item = explode('=', $item);
if (isset($item[0]) && isset($item[1])) {
$attrVal = $item[1];
if ($item[1] === 'false' || $item[1] === 'true') {
$attrVal = !($item[1] === 'false');
} elseif (is_numeric($item[1])) {
$attrVal = (float)$item[1];
}
if (strpos($item[0], '.') !== false) {
$attrKey = explode('.', $item[0]);
if (isset($attrKey[0]) && isset($attrKey[1])) {
$attrTemp[$attrKey[0]][$attrKey[1]] = $attrVal;
continue;
}
}
$attrTemp[$item[0]] = $attrVal;
}
}
return $attrTemp;
}
}
if (!function_exists('hsv2rgb')) {
function hsv2rgb($h, $s, $v): array
{
$r = $g = $b = 0;
$i = floor($h * 6);
$f = $h * 6 - $i;
$p = $v * (1 - $s);
$q = $v * (1 - $f * $s);
$t = $v * (1 - (1 - $f) * $s);
switch ($i % 6) {
case 0: $r = $v; $g = $t; $b = $p; break;
case 1: $r = $q; $g = $v; $b = $p; break;
case 2: $r = $p; $g = $v; $b = $t; break;
case 3: $r = $p; $g = $q; $b = $v; break;
case 4: $r = $t; $g = $p; $b = $v; break;
case 5: $r = $v; $g = $p; $b = $q; break;
}
return [floor($r * 255), floor($g * 255), floor($b * 255)];
}
}
if (!function_exists('build_suffix_svg')) {
function build_suffix_svg(string $suffix = 'file', ?string $background = null): string
{
$suffix = mb_substr(strtoupper($suffix), 0, 4);
$total = unpack('L', hash('adler32', $suffix, true))[1];
$hue = $total % 360;
[$r, $g, $b] = hsv2rgb($hue / 360, 0.3, 0.9);
$background = $background ?: "rgb($r,$g,$b)";
return '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#E2E5E7;" d="M128,0c-17.6,0-32,14.4-32,32v448c0,17.6,14.4,32,32,32h320c17.6,0,32-14.4,32-32V128L352,0H128z"/>
<path style="fill:#B0B7BD;" d="M384,128h96L352,0v96C352,113.6,366.4,128,384,128z"/>
<polygon style="fill:#CAD1D8;" points="480,224 384,128 480,128 "/>
<path style="fill:' . $background . ';" d="M416,416c0,8.8-7.2,16-16,16H48c-8.8,0-16-7.2-16-16V256c0-8.8,7.2-16,16-16h352c8.8,0,16,7.2,16,16 V416z"/>
<path style="fill:#CAD1D8;" d="M400,432H96v16h304c8.8,0,16-7.2,16-16v-16C416,424.8,408.8,432,400,432z"/>
<g><text><tspan x="220" y="380" font-size="124" font-family="Verdana, Helvetica, Arial, sans-serif" fill="white" text-anchor="middle">' . $suffix . '</tspan></text></g>
</svg>';
}
}
if (!function_exists('get_account_verification_type')) {
/**
* 获取可用的账户验证方式
* @return string[] email=电子邮件,mobile=手机短信验证
*/
function get_account_verification_type(): array
{
$types = [];
$sysMailConfig = get_sys_config('', 'mail');
$configured = true;
if (is_array($sysMailConfig)) {
foreach ($sysMailConfig as $item) {
if (!$item) {
$configured = false;
break;
}
}
} else {
$configured = false;
}
if ($configured) {
$types[] = 'email';
}
if (class_exists(\app\admin\library\module\Server::class)) {
$sms = \app\admin\library\module\Server::getIni(\ba\Filesystem::fsFit(root_path() . 'modules/sms/'));
if ($sms && ($sms['state'] ?? 0) == 1) {
$types[] = 'mobile';
}
}
return $types;
}
}
if (!function_exists('get_area')) {
function get_area($request = null): array
{
$request = $request ?? (function_exists('request') ? request() : null);
$province = $request ? $request->get('province', '') : '';
$city = $request ? $request->get('city', '') : '';
$where = ['pid' => 0, 'level' => 1];
if ($province !== '') {
$where['pid'] = $province;
$where['level'] = 2;
if ($city !== '') {
$where['pid'] = $city;
$where['level'] = 3;
}
}
return \support\think\Db::name('area')
->where($where)
->field('id as value,name as label')
->select()
->toArray();
}
}