PluginProbe
WPJAM Basic / trunk
WPJAM Basic vtrunk
wpjam-basic / public / wpjam-utils.php

wpjam-utils.php in WPJAM Basic trunk, at public/wpjam-utils.php

1,565 lines 41.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if(!function_exists('base64_urlencode')){
3 function base64_urlencode($str){
4 return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
5 }
6 }
7
8 if(!function_exists('base64_urldecode')){
9 function base64_urldecode($str){
10 return base64_decode(str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '='));
11 }
12 }
13
14 // JWT
15 function wpjam_generate_jwt($payload, $header=[]){
16 if(is_array($payload)){
17 $jwt = implode('.', array_map(fn($v)=> base64_urlencode(wpjam_json_encode($v)), [$header+['typ'=>'JWT'], $payload]));
18
19 return $jwt.'.'.wpjam_generate_signature('hmac-sha256', $jwt); // 'alg'=>'HS256'
20 }
21 }
22
23 function wpjam_verify_jwt($token){
24 [$header, $payload, $sign] = explode('.', $token)+['', '', ''];
25
26 //iat 签发时间不能大于当前时间
27 //nbf 时间之前不接收处理该Token
28 //exp 过期时间不能小于当前时间
29 return hash_equals(wpjam_generate_signature('hmac-sha256', $header.'.'.$payload), $sign)
30 && ($data = wpjam_json_decode(base64_urldecode($payload)))
31 && !array_any(['iat'=>'>', 'nbf'=>'>', 'exp'=>'<'], fn($v, $k)=> isset($data[$k]) && wpjam_compare($data[$k], $v, time()))
32 ? $data
33 : false;
34 }
35
36 function wpjam_get_jwt($key='access_token', $required=false){
37 return ($header = $_SERVER['HTTP_AUTHORIZATION'] ?? '') && try_prefix($header, '-', 'Bearer')
38 ? trim($header)
39 : wpjam_param($key, ['required'=>$required]);
40 }
41
42 // Crypt
43 function wpjam_encrypt($text, $args){
44 $de = $args['de'] ?? false;
45 $params = [$args['method'] ?? '', $args['key'] ?? '', $args['options'] ?? '', $args['iv'] ?? ''];
46 $text = $de ? openssl_decrypt($text, ...$params) : $text;
47
48 foreach($de ? ['pkcs7', 'weixin'] : ['weixin', 'pkcs7'] as $pad){
49 if($arg = $pad == 'pkcs7'
50 ? (($args['options'] ?? '') == OPENSSL_ZERO_PADDING ? ($args['block_size'] ?? '') : '')
51 : (($args['pad'] ?? '') == 'weixin' ? trim($args['appid'] ?? '') : '')
52 ){
53 $text = wpjam_pad($text, ($de ? '-' : '').$pad, $arg);
54 }
55 }
56
57 return $de ? $text : openssl_encrypt($text, ...$params);
58 }
59
60 function wpjam_decrypt($text, $args){
61 return wpjam_encrypt($text, $args+['de'=>true]);
62 }
63
64 function wpjam_pad($text, $type, ...$args){
65 if($type == 'pkcs7'){
66 $pad = $args[0] - (strlen($text) % $args[0]);
67 $text .= str_repeat(chr($pad), $pad);
68 }elseif($type == '-pkcs7'){
69 $pad = ord(substr($text, -1));
70 $text = ($pad > 0 && $pad < $args[0]) ? substr($text, 0, -1 * $pad) : $text;
71 }elseif($type == 'weixin'){
72 $text = wp_generate_password(16, false).pack("N", strlen($text)).$text.$args[0];
73 }elseif($type == '-weixin'){
74 $length = (unpack("N", substr($text, 16, 4)))[1];
75
76 return (($appid = substr($text, $length + 20)) != $args[0])
77 ? new WP_Error('invalid_appid', 'Appid 校验「'.$appid.'」「'.$args[0].'」错误')
78 : substr($text, 20, $length);
79 }
80
81 return $text;
82 }
83
84 function wpjam_generate_signature($algo='sha1', ...$args){
85 if($algo == 'sha1'){
86 return sha1(implode(wpjam_sort($args, SORT_STRING)));
87 }elseif($algo == 'hmac-sha256'){
88 return base64_urlencode(hash_hmac('sha256', $args[0], wp_salt(), true));
89 }
90 }
91
92 // JSON
93 function wpjam_json_encode($data){
94 return wp_json_encode($data, JSON_UNESCAPED_UNICODE);
95 }
96
97 function wpjam_json_decode($json, $assoc=true){
98 $result = ($json = wpjam_strip_control_chars($json)) ? json_decode($json, $assoc) : new WP_Error('empty_json', 'JSON �
99 容不能为空!');
100 $result ??= str_contains($json, '\\') ? json_decode(stripslashes($json), $assoc) : $result;
101
102 if(is_null($result)){
103 trigger_error('json_decode_error['.json_last_error().']:'.($msg = json_last_error_msg())."\n".var_export($json, true));
104
105 return new WP_Error('json_decode_error', $msg);
106 }
107
108 return $result;
109 }
110
111 function wpjam_send_json($data=[], $code=null){
112 if($data === true || $data === []){
113 $data = ['errcode'=>0];
114 }elseif($data === false || is_null($data)){
115 $data = ['errcode'=>'-1', 'errmsg'=>'error'];
116 }elseif(is_wp_error($data) || wpjam_is_assoc_array($data)){
117 $data = wpjam_error($data);
118 }
119
120 $data = wpjam_json_encode($data);
121 $jsonp = wp_is_jsonp_request();
122
123 if(!headers_sent()){
124 isset($code) && status_header($code);
125
126 wpjam_doing_debug() || @header('Content-Type: application/'.($jsonp ? 'javascript' : 'json').'; charset='.get_option('blog_charset'));
127 }
128
129 echo $jsonp ? '/**/'.$_GET['_jsonp'].'('.$data.')' : $data; exit;
130 }
131
132 function wpjam_import($file, $columns=[]){
133 $dir = wp_get_upload_dir()['basedir'];
134 $file = ($file && !str_starts_with($file, $dir) ? $dir : '').$file;
135
136 if(!$file || !file_exists($file)){
137 return new WP_Error('file_not_exists', '文件不存在');
138 }
139
140 $ext = wpjam_at($file, '.', -1);
141
142 if($ext == 'csv'){
143 $columns = wpjam_reduce($columns, fn($c, $v, $k)=> $c+[trim($v)=>$k, $k=>$k], []);
144
145 if(($handle = fopen($file, 'r')) !== false){
146 while(($row = fgetcsv($handle)) !== false){
147 if(!array_filter($row)){
148 continue;
149 }
150
151 if(($encoding ??= mb_detect_encoding(implode('', $row), mb_list_encodings(), true)) != 'UTF-8'){
152 $row = array_map(fn($v) => mb_convert_encoding($v, 'UTF-8', 'GBK'), $row);
153 }
154
155 if(isset($map)){
156 $data[] = array_map(fn($i)=> preg_replace('/="([^"]*)"/', '$1', $row[$i]), $map);
157 }else{
158 $row = array_map(fn($v)=> trim(trim($v), "\xEF\xBB\xBF"), $row);
159 $map = $columns ? wpjam_array($row, fn($i, $k)=> isset($columns[$k]) ? [$columns[$k], $i] : null) : array_flip($row);
160 }
161 }
162
163 fclose($handle);
164 }
165 }else{
166 $data = file_get_contents($file);
167 $data = ($ext == 'txt' && is_serialized($data)) ? maybe_unserialize($data) : $data;
168 }
169
170 unlink($file);
171
172 return $data ?? [];
173 }
174
175 function wpjam_export($file, $data, $columns=[]){
176 $handle = fopen('php://output', 'w');
177 $ext = wpjam_at($file, '.', -1);
178
179 header('Content-Disposition: attachment;filename='.$file);
180 header('Content-Type: text/'.($ext == 'txt' ? 'plain' : $ext));
181 header('Pragma: no-cache');
182 header('Expires: 0');
183
184 if($ext == 'csv'){
185 fwrite($handle, chr(0xEF).chr(0xBB).chr(0xBF));
186
187 $columns && fputcsv($handle, $columns);
188
189 array_walk($data, fn($item)=> fputcsv($handle, $columns ? wpjam_map($columns, fn($k)=> $item[$k] ?? '', 'k') : $item));
190 }elseif($ext == 'txt'){
191 fputs($handle, is_scalar($data) ? $data : maybe_serialize($data));
192 }
193
194 fclose($handle);
195
196 exit;
197 }
198
199 function wpjam_columnar($items, $columns=[], $dict=[]){
200 if(!$items){
201 return [];
202 }
203
204 $columns= $columns ?: array_keys(array_first($items));
205 $dict = $dict ? wpjam_fill(array_intersect($dict, $columns), fn($k)=> array_values(array_unique(array_column($items, $k)))) : [];
206 $rows = [];
207
208 foreach($items as $id => $item){
209 if($dict){
210 foreach(array_intersect_key($item, $dict) as $k => $v){
211 $item[$k] = array_search($v, $dict[$k]);
212 }
213 }
214
215 $rows[$id] = array_map(fn($k)=> $item[$k] ?? null, $columns);
216 }
217
218 return ['columns'=>$columns, 'rows'=>$rows]+($dict ? ['dict'=>$dict] : []);
219 }
220
221 function wpjam_records($data){
222 if(!$data || empty($data['columns']) || empty($data['rows'])){
223 return [];
224 }
225
226 $dict = $data['dict'] ?? [];
227 $items = [];
228
229 foreach($data['rows'] as $id => $row){
230 $row = array_combine($data['columns'], $row);
231
232 if($dict){
233 foreach(array_intersect_key($row, $dict) as $k => $v){
234 $row[$k] = $dict[$k][$v];
235 }
236 }
237
238 $items[$id] = $row;
239 }
240
241 return $items;
242 }
243
244 function wpjam_compress($data, $base64=true, $level=6){
245 $text = gzcompress(wpjam_json_encode($data), $level);
246
247 return $base64 ? base64_encode($text) : $text;
248 }
249
250 function wpjam_uncompress($text, $base64=true){
251 return wpjam_json_decode(gzuncompress($base64 ? base64_decode($text) : $text));
252 }
253
254 // User agent
255 function wpjam_get_user_agent(){
256 return $_SERVER['HTTP_USER_AGENT'] ?? '';
257 }
258
259 function wpjam_get_ip(){
260 return $_SERVER['REMOTE_ADDR'] ?? '';
261 }
262
263 function wpjam_parse_user_agent($agent=null, $referer=null){
264 $agent ??= wpjam_get_user_agent();
265 $rule = array_find([
266 ['iPhone', 'iOS'],
267 ['iPad', 'iOS'],
268 ['iPod', 'iOS'],
269 ['Android'],
270 ['Windows NT', 'Windows'],
271 ['Macintosh'],
272 ['Windows Phone'],
273 ['BlackBerry'],
274 ['BB10', 'BlackBerry'],
275 ['Symbian'],
276 ], fn($v)=> stripos($agent, $v[0]));
277
278 $os = $rule ? ($rule[1] ?? $rule[0]) : 'unknown';
279
280 if($os == 'iOS'){
281 if(preg_match('/OS (.*?) like Mac OS X[\)]{1}/i', $agent, $m)){
282 $ua = [(float)trim(str_replace('_', '.', $m[1])), $rule[0]];
283 }
284 }elseif($os == 'Android'){
285 if(preg_match('/Android ([0-9\.]{1,}?); (.*?) Build\/(.*?)[\)\s;]{1}/i', $agent, $m) && !empty($m[1]) && !empty($m[2])){
286 $ua = [trim($m[1]), str_contains($m[2], ';') ? wpjam_at(trim($m[2]), ';', 1) : trim($m[2])];
287 }
288 }
289
290 $rule = array_find([
291 ['lynx'],
292 ['safari', '/version\/([\d\.]+).*safari/i'],
293 ['edge', '/edge\/([\d\.]+)/i'],
294 ['chrome', '/chrome\/([\d\.]+)/i'],
295 ['firefox', '/firefox\/([\d\.]+)/i'],
296 ['opera', '/(?:opera).([\d\.]+)/i'],
297 ['opr/', '/(?:opr).([\d\.]+)/i', 'opera'],
298 ['msie', '', 'ie'],
299 ['trident', '', 'ie'],
300 ['gecko'],
301 ['nav']
302 ], fn($v)=> stripos($agent, $v[0]));
303
304 return ['os'=>$os, 'os_version'=>$ua[0] ?? 0, 'device'=>$ua[1] ?? '']+array_combine(
305 ['browser', 'browser_version'],
306 $rule ? [($rule[2] ?? '') ?: $rule[0], !empty($rule[1]) && preg_match($rule[1], $agent, $m) ? (float)(trim($m[1])) : 0] : ['', 0]
307 )+array_combine(
308 ['app', 'app_version'],
309 preg_match('/MicroMessenger\/(.*?)\s/', $agent, $m) ? [str_contains($referer ?? ($_SERVER['HTTP_REFERER'] ?? ''), 'https://servicewechat.com') ? 'weapp' : 'weixin', (float)$m[1]] : ['', 0]
310 );
311 }
312
313 function wpjam_parse_ip($ip=''){
314 $ip = $ip ?: ($_SERVER['REMOTE_ADDR'] ?? '');
315
316 if($ip == 'unknown' || !$ip){
317 return false;
318 }
319
320 $default = ['ip'=>$ip]+array_fill_keys(['country', 'region', 'city'], '');
321
322 if(!file_exists(WP_CONTENT_DIR.'/uploads/17monipdb.dat')){
323 return $default;
324 }
325
326 $nip = gethostbyname($ip);
327 $ipdot = explode('.', $nip);
328
329 if($ipdot[0] < 0 || $ipdot[0] > 255 || count($ipdot) !== 4){
330 return $default;
331 }
332
333 static $cache = [];
334
335 if(!$cache){
336 $fp = fopen(WP_CONTENT_DIR.'/uploads/17monipdb.dat', 'rb');
337 $offset = unpack('Nlen', fread($fp, 4));
338 $index = fread($fp, $offset['len'] - 4);
339 $cache = ['fp'=>$fp, 'offset'=>$offset, 'index'=>$index];
340
341 register_shutdown_function(fn()=> fclose($fp));
342 }
343
344 $fp = $cache['fp'];
345 $offset = $cache['offset'];
346 $index = $cache['index'];
347 $nip2 = pack('N', ip2long($nip));
348 $start = (int)$ipdot[0]*4;
349 $start = unpack('Vlen', $index[$start].$index[$start+1].$index[$start+2].$index[$start+3]);
350
351 for($start = $start['len']*8+1024; $start < $offset['len']-1024-4; $start+=8){
352 if($index[$start].$index[$start+1].$index[$start+2].$index[$start+3] >= $nip2){
353 $index_offset = unpack('Vlen', $index[$start+4].$index[$start+5].$index[$start+6]."\x0");
354 $index_length = unpack('Clen', $index[$start+7]);
355
356 fseek($fp, $offset['len']+$index_offset['len']-1024);
357
358 $data = explode("\t", fread($fp, $index_length['len']));
359 $data = array_slice(array_pad($data, 3, ''), 0, 3);
360
361 return ['ip'=>$ip]+array_combine(['country', 'region', 'city'], $data);
362 }
363 }
364
365 return $default;
366 }
367
368 // $a, $args
369 // $a, $b
370 // $a, $op, $b, $strict=false
371 function wpjam_compare($a, $op, ...$args){
372 if(wpjam_is_assoc_array($op)){
373 return wpjam_is_assoc_array($a) && isset($op['key'])
374 ? wpjam_match($a, $op)
375 : wpjam_compare($a, $op['compare'] ?? '', $op['value'] ?? null, (bool)($args['strict'] ?? false));
376 }
377
378 $is = is_array($op) || !$args;
379 $b = $is ? $op : array_shift($args);
380 $op = $is ? '' : $op;
381 $strict = in_array($op, ['!==', '===']) ? true : (bool)array_shift($args);
382 $op = $op ? strtoupper(['!=='=>'!=', '==='=>'=', '=='=>'='][$op] ?? $op) : (is_array($b) ? 'IN' : '=');
383 $inv = ['!='=>'=', '<='=>'>', '>='=>'<', 'NOT IN'=>'IN', 'NOT BETWEEN'=>'BETWEEN', 'NOT LIKE'=>'LIKE', 'NOT REGEXP'=>'REGEXP'][$op] ?? '';
384
385 if($inv){
386 return !wpjam_compare($a, $inv, $b, $strict);
387 }
388
389 $b = in_array($op, ['IN', 'BETWEEN']) ? wp_parse_list($b) : (is_string($b) ? trim($b) : $b);
390
391 switch($op){
392 case '=': return $strict ? $a === $b : $a == $b;
393 case '>': return $a > $b;
394 case '<': return $a < $b;
395 case '<=>': return $a <=> $b;
396 case 'IN': return is_array($a) ? array_all($a , fn($v)=> in_array($v, $b, $strict)) : in_array($a, $b, $strict);
397 case 'LIKE': return str_contains((string)$a, str_replace('%', '', (string)$b));
398 case 'BETWEEN': return $a >= $b[0] && $a <= ($b[1] ?? $b[0]);
399 case 'REGEXP': return (bool)preg_match('/'.str_replace('/', '\/', (string)$b).'/', (string)$a);
400 }
401 }
402
403 function wpjam_operate($a, $op, $b){
404 if(is_array($a)){
405 switch($op){
406 case '+': return array_merge($a, $b);
407 case '-': return wpjam_is_assoc_array($a) && wp_is_numeric_array($b) ? wpjam_except($a, $b) : array_diff($a, $b);
408 }
409 }else{
410 switch($op){
411 case '+': return $a + $b;
412 case '-': return $a - $b;
413 case '*': return $a * $b;
414 case '/': return $a / $b;
415 case '%': return $a % $b;
416 case '**': return $a ** $b;
417 case '.': return $a.$b;
418 default: return wpjam_compare($a, $op, $b);
419 }
420 }
421 }
422
423 function wpjam_calc(...$args){
424 if(wpjam_is_assoc_array($args[0])){
425 $item = $args[0];
426 $formulas = $args[1];
427 $if_errors = $args[2] ?? [];
428 $item = array_diff_key($item, $formulas);
429
430 foreach($formulas as $key => $formula){
431 foreach($formula as &$t){
432 if(str_starts_with($t, '$')){
433 $k = substr($t, 1);
434 $v = $item[$k] ?? null;
435 $t = $v === null || $v === '' ? 0 : (is_numeric($v) ? $v : wpjam_format($v, '-,', false));
436 $t = $t === false ? ($if_errors[$k] ?? false) : $t;
437
438 if($t === false){
439 $item[$key] = $if_errors[$key] ?? '!无法计算';
440 goto calced;
441 }
442 }
443 }
444
445 try{
446 $item[$key] = wpjam_calc($formula);
447 }catch(Throwable $e){
448 $item[$key] = $if_errors[$key] ?? ($e instanceof DivisionByZeroError ? '!除零错误' : '!'.$e->getMessage());
449 }
450
451 calced:;
452
453 if(!$item[$key]){
454 unset($item[$key]);
455 }
456 }
457
458 return $item;
459 }
460
461 $exp = $args[0];
462 $item = $args[1] ?? [];
463 $calc = [];
464
465 foreach(is_array($exp) ? $exp : wpjam_formula(...$args) as $t){
466 if(is_numeric($t) || $t === '|'){
467 $calc[] = $t;
468 }elseif(try_prefix($t, '-', '$')){
469 $calc[] = $item[$t] ?? 0;
470 }elseif(in_array($t, ['sin', 'cos', 'abs', 'max', 'min', 'sqrt', 'pow', 'round', 'floor', 'ceil', 'fmod'])){
471 $calc[] = $t(...(array_slice(array_splice($calc, array_last(array_keys($calc, '|'))), 1) ?: wpjam_throw('invalid_calc', '函数「'.$t.'」无有效参数')));
472 }else{
473 $v = array_pop($calc);
474
475 if(in_array($t, ['/', '%']) && in_array((string)$v, ['0', '0.0', ''])){
476 throw new DivisionByZeroError('Division by zero');
477 }
478
479 $calc[] = wpjam_operate(array_pop($calc), $t, $v);
480 }
481 }
482
483 return count($calc) === 1 ? $calc[0] : wpjam_throw('invalid_calc', '计算栈剩余�
484 �素数还有'.count($calc).'个');
485 }
486
487 function wpjam_formula(...$args){
488 if(is_array($args[0])){
489 $fields = array_shift($args);
490
491 if($args){
492 $render = fn($key)=> '字段'.($fields[$key]['title'] ?? '').'「'.$key.'」'.'�
493 �式「'.$fields[$key]['formula'].'」';
494 $key = $args[0];
495 $parsed = $args[1];
496 $path = $args[2] ?? [];
497
498 if(isset($parsed[$key])){
499 return $parsed;
500 }
501
502 if(in_array($key, $path)){
503 wpjam_throw('invalid_formula', '�
504 �式嵌套:'.implode(' → ', wpjam_map(array_slice($path, array_search($key, $path)), fn($k)=> $render($k))));
505 }
506
507 $path[] = $key;
508 $formula = wpjam_formula($fields[$key]['formula'], $fields, $render($key).'错误');
509 $parsed = array_reduce($formula, fn($c, $t)=> try_prefix($t, '-', '$') && !empty($fields[$t]['formula']) ? wpjam_formula($fields, $t, $c, $path) : $c, $parsed);
510
511 return $parsed+[$key => $formula];
512 }
513
514 return wpjam_reduce($fields, fn($c, $v, $k)=> empty($v['formula']) ? $c : wpjam_formula($fields, $k, $c), []);
515 }
516
517 $formula = trim(str_replace("\xc2\xa0", ' ', $args[0]));
518 $fields = $args[1] ?? [];
519 $error = $args[2] ?? '';
520 $throw = fn($msg)=> wpjam_throw('invalid_formula', $error.':'.$msg);
521 $functions = ['sin', 'cos', 'abs', 'ceil', 'pow', 'sqrt', 'pi', 'max', 'min', 'fmod', 'round'];
522 $precedence = ['+'=>1, '-'=>1, '**'=>2, '*'=>2, '/'=>2, '%'=>2, '>='=>3, '<='=>3, '!='=>3, '=='=>3, '>'=>3, '<'=>3,];
523 $signs = implode('|', array_map(fn($v)=> preg_quote($v, '/'), [...array_keys($precedence), '(', ')', ',']));
524 $formula = preg_split('/\s*('.$signs.')\s*/', $formula, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
525 $output = $stack = [];
526 $pt = null;
527
528 foreach($formula as $i => $t){
529 $nt = $formula[$i+1] ?? null;
530
531 if(is_numeric($t)){
532 $output[] = str_ends_with($t, '.') ? $throw('无效的数字「'.$t.'」') : (float)$t;
533 }elseif($t[0] === '$'){
534 $output[] = isset($fields[substr($t, 1)]) ? $t : $throw('「'.$t.'」未定义');
535 }elseif(in_array($t, $functions, true)){
536 $stack[] = $t;
537 $output[] = '|';
538 }elseif($t === '('){
539 $stack[] = $t;
540 }elseif($t === ')' || $t === ','){
541 if(!in_array('(', $stack, true) || ($t === ',' && ($pt === '(' || !$nt || $nt === ','))){
542 $throw(($t === ')' ? '未匹�
543 �' : '无效').'的「'.$t.'」');
544 }
545
546 while(array_last($stack) !== '('){
547 $output[] = array_pop($stack);
548 }
549
550 if($t === ')'){
551 array_pop($stack);
552
553 in_array(array_last($stack), $functions) && array_push($output, array_pop($stack));
554 }
555 }elseif(isset($precedence[$t])){
556 $r = ((!$pt || in_array($pt, ['(', ','], true)) ? 1 : 0)+((!$nt || in_array($nt, [')', ','], true) || isset($precedence[$nt])) ? 2 : 0);
557
558 if(in_array($t, ['+', '-'], true) && $r == 1){
559 $output[] = 0;
560 }elseif($r){
561 $throw('操作符「'.$t.'」缺少操作数');
562 }
563
564 while($stack && ($v = array_last($stack)) !== '('){
565 if(in_array($v, $functions) || $precedence[$t] <= $precedence[$v]){
566 $output[] = array_pop($stack);
567 }else{
568 break;
569 }
570 }
571
572 $stack[] = $t;
573 }else{
574 $throw('无效的符号「'.$t.'」');
575 }
576
577 $pt = $t;
578 }
579
580 return array_merge($output, array_reverse(in_array('(', $stack, true) ? $throw('未匹�
581 �的「(」') : $stack));
582 }
583
584 function wpjam_format($value, $format, ...$args){
585 if(is_array($value) && is_array($format)){
586 return wpjam_reduce($format ?: [], fn($c, $v, $k)=> isset($c[$k]) ? wpjam_set($c, $k, wpjam_format($c[$k], ...$v)) : $c, $value);
587 }
588
589 if(is_numeric($value)){
590 if($format == ','){
591 return number_format(trim($value), (int)($args[0] ?? 2));
592 }elseif($format == '%'){
593 return round($value * 100, ($args[0] ?? 2) ?: 2).'%';
594 }elseif(!$format && $args && is_numeric($args[0])){
595 return round($value, $args[0]);
596 }
597
598 return $value / 1;
599 }elseif(in_array($format, ['-,', '-%'])){
600 if(is_string($value)){
601 $value = str_replace(',', '', trim($value));
602
603 if($format == '-%' && try_suffix($value, '-', '%')){
604 $value = is_numeric($value) ? $value / 100 : $value;
605 }
606 }
607
608 return is_numeric($value) ? $value / 1 : ($args ? $args[0] : $value);
609 }
610
611 return $value;
612 }
613
614 function wpjam_match($item, ...$args){
615 if(!$args || is_null($args[0])){
616 return true;
617 }
618
619 $args = wpjam_parse_show_if(is_string($args[0]) ? $args : $args[0]);
620 $value = wpjam_get($item, $args['key']);
621 $value2 = $args['value'] ?? null;
622
623 if(!isset($args['compare'])){
624 if(!empty($args['callable']) && (is_closure($value) || (is_callable($value) && is_array($value)))){
625 return $value($value2, $item);
626 }
627
628 if(isset($args['if_null']) && is_null($value)){
629 return $args['if_null'];
630 }
631 }
632
633 if(is_array($value) || !empty($args['swap'])){
634 [$value, $value2] = [$value2, $value];
635 }
636
637 return wpjam_compare($value, $args['compare'] ?? null, $value2, (bool)($args['strict'] ?? false));
638 }
639
640 function wpjam_matches($arr, $args, $op='AND'){
641 $op = strtoupper($op);
642
643 if(!in_array($op, ['AND', 'ALL', 'OR', 'ANY', 'NOT'])){
644 return false;
645 }
646
647 if(!is_callable($args)){
648 return wpjam_matches($args, fn($v, $k)=> wpjam_match($arr, ...(wpjam_is_assoc_array($v) ? [$v+['key'=>$k]] : [$k, $v])), $op);
649 }
650
651 if(in_array($op, ['AND', 'ALL'], true)){
652 return array_all($arr, $args);
653 }elseif(in_array($op, ['OR', 'ANY'], true)){
654 return array_any($arr, $args);
655 }else{
656 return !array_all($arr, $args);
657 }
658 }
659
660 // Array
661 function wpjam_is_assoc_array($arr){
662 return is_array($arr) && !wp_is_numeric_array($arr);
663 }
664
665 function wpjam_array($arr=null, $cb=null, $skip_null=false){
666 if(!$cb){
667 if(is_object($arr)){
668 if(method_exists($arr, 'to_array')){
669 return $arr->to_array();
670 }elseif($arr instanceof Traversable){
671 return iterator_to_array($arr);
672 }else{
673 return ($arr instanceof JsonSerializable) && is_array($data = $arr->jsonSerialize()) ? $data : [];
674 }
675 }
676
677 return (array)$arr;
678 }
679
680 $data = [];
681
682 foreach($arr as $k => $v){
683 $r = $cb($k, $v);
684
685 if(is_scalar($r)){
686 $k = $r;
687 }elseif(is_array($r) && count($r) >= 2 && !($skip_null && is_null($r[1]))){
688 [$k, $v] = $r;
689 }else{
690 continue;
691 }
692
693 $data = wpjam_set($data, $k, $v, '[]');
694 }
695
696 return $data;
697 }
698
699 function wpjam_fill($keys, $cb){
700 return wpjam_array($keys, fn($i, $k)=> [$k, $cb($k, $i)], true);
701 }
702
703 function wpjam_pick($arr, $args){
704 return wpjam_array($args, fn($i, $k)=> [$k, wpjam_get($arr, $k)], true);
705 }
706
707 function wpjam_entries($items, $key=null, $value=null){
708 $key ??= 0;
709 $value ??= (int)($key === 0);
710
711 return wpjam_array($items, fn($k, $v)=> [null, [$key=>$k, $value=>$v]]);
712 }
713
714 function wpjam_column($items, $key=null, $index=null){
715 return wpjam_array($items, fn($k, $v)=> [
716 is_null($index) || $index === false ? null : ($index === true ? $k : $v[$index]),
717 is_array($key) ? wpjam_array($key, fn($i, $j)=> [wpjam_is_assoc_array($key) ? $i : $j, wpjam_get($v, $j)], true) : wpjam_get($v, $key)
718 ]);
719 }
720
721 function wpjam_map($arr, $cb, $args=[]){
722 if($arr){
723 $args = is_bool($args) || $args === 'deep' ? ['deep'=>(bool)$args] : (is_string($args) ? ['mode'=>$args] : $args);
724 $mode = str_split(in_array($args['mode'] ?? '', ['vk', 'kv', 'k', 'v']) ? $args['mode'] : 'vk');
725 $deep = $args['deep'] ?? false;
726 $all = in_array($deep, [true, '*'], true);
727
728 foreach($arr as $k => &$v){
729 $d = is_array($v) && ($all || (is_string($deep) && is_array($v[$deep] ?? '')));
730 $v = (!$d || $deep === '*') ? $cb(...array_map(fn($c) => $c === 'k' ? $k : $v, $mode)) : $v;
731 $v = $d ? ($all ? wpjam_map($v, $cb, $args) : [$deep => wpjam_map($v[$deep], $cb, $args)]+$v) : $v;
732 }
733
734 unset($v);
735 }
736
737 return $arr;
738 }
739
740 function wpjam_reduce($arr, $cb, $carry=null, $key='', $args=[]){
741 $depth = $args['depth'] ??= 0;
742
743 foreach(wpjam_array($arr) as $k => $v){
744 $carry = $cb($carry, $v, $k, $args);
745
746 if($key && (empty($args['max_depth']) || $args['max_depth'] > $depth+1) && is_array($v)){
747 $sub = $key === true ? $v : wpjam_get($v, $key);
748 $carry = is_array($sub) ? wpjam_reduce($sub, $cb, $carry, $key, ['depth'=>$depth+1]+$args) : $carry;
749 }
750 }
751
752 return $carry;
753 }
754
755 function wpjam_nest($items, $options=[], $parent=null, $depth=0){
756 if(!$items){
757 return [];
758 }
759
760 $fields = array_filter($options['fields'] ?? [])+['id'=>'id', 'name'=>'name', 'parent'=>'parent', 'children'=>'children'];
761
762 if($parent === null){
763 $group = wpjam_group($items, $fields['parent']);
764 $group[0] = array_filter([$options['top'] ?? '']) ?: ($group[0] ?? array_first($group));
765
766 return wpjam_nest($group, $options, 0, 0);
767 }
768
769 $cb = $options['item_callback'] ?? '';
770 $max = $options['max_depth'] ?? 0;
771 $format = $options['format'] ?? '';
772 $parsed = [];
773
774 foreach(wpjam_pull($items, $parent) ?: [] as $item){
775 $item = $cb ? $cb($item) : $item;
776 $children = (!$max || $max > $depth+1) ? wpjam_nest($items, $options, wpjam_get($item, $fields['id']), $depth+1) : [];
777 $parsed[] = wpjam_set($item, ...($format == 'flat'
778 ? [$fields['name'], str_repeat('&emsp;', $depth).wpjam_get($item, $fields['name'])]
779 : [$fields['children'], $children]
780 ));
781
782 if($format == 'flat'){
783 $parsed = array_merge($parsed, $children);
784 }
785 }
786
787 return $parsed;
788 }
789
790 function wpjam_at($arr, $index, ...$args){
791 if(is_string($arr)){
792 [$sep, $index] = is_int($index) ? [$args[0] ?? '', $index] : [$index, $args[0] ?? 0];
793
794 $sep && ($arr = explode($sep, $arr));
795 }
796
797 if(is_array($arr) || is_string($arr)){
798 $count = is_array($arr) ? count($arr) : strlen($arr);
799 $index = $index >= 0 ? $index : $count + $index;
800
801 if($index >= 0 && $index < $count){
802 return is_string($arr) ? $arr[$index] : $arr[array_keys($arr)[$index]];
803 }
804 }
805 }
806
807 function wpjam_add_at($arr, $index, $key, ...$args){
808 if(!$args && !is_array($key)){
809 $args = [$key];
810 $key = null;
811 }
812
813 if(is_null($key)){
814 array_splice($arr, $index, 0, $args);
815
816 return $arr;
817 }
818
819 return array_replace(array_slice($arr, 0, $index, true), (is_array($key) ? $key : [$key=>$args[0] ?? '']))+array_slice($arr, $index, null, true);
820 }
821
822 function wpjam_rotate($arr, $step=1){
823 if(($count = count($arr)) <= 1 || ($step %= $count) === 0){
824 return $arr;
825 }
826
827 $step = $step < 0 ? $count + $step : $step;
828
829 return [...array_slice($arr, $step), ...array_slice($arr, 0, $step)];
830 }
831
832 function wpjam_find($arr, $cb, ...$args){
833 $output = 'value';
834
835 if($args){
836 if(is_callable($args[0])){
837 $output = 'result';
838 $mapper = $args[0];
839 }else{
840 $output = $args[0];
841 }
842 }
843
844 $cb = wpjam_is_assoc_array($cb) ? fn($v)=> wpjam_matches($v, $cb) : ($cb ?: fn()=> true);
845 $cb = $cb === true ? fn($v)=> $v : $cb;
846
847 if(!$cb){
848 return;
849 }
850
851 if($output == 'value'){
852 return array_find($arr, $cb);
853 }
854
855 if($output == 'key'){
856 return array_find_key($arr, $cb);
857 }
858
859 if($output == 'index'){
860 return array_search(array_find_key($arr, $cb), array_keys($arr));
861 }
862
863 if($output == 'result'){
864 foreach($arr as $k => $v){
865 $v = $mapper($v, $k);
866
867 if($cb($v)){
868 return $v;
869 }
870 }
871 }
872 }
873
874 function wpjam_group($arr, $field){
875 $cb = is_closure($field) ? $field : fn($v) => wpjam_get($v, $field);
876
877 return wpjam_reduce($arr, fn($c, $v, $k)=> wpjam_set($c, [$cb($v, $k), $k], $v), []);
878 }
879
880 function wpjam_pull(&$arr, $key, ...$args){
881 $value = (is_array($key) ? 'wpjam_pick' : 'wpjam_get')($arr, $key, ...$args);
882 $arr = wpjam_except($arr, $key);
883
884 return $value;
885 }
886
887 function wpjam_assoc($arr){
888 if(!wp_is_numeric_array($arr) && (!($sub = wpjam_filter($arr, fn($v, $k) => is_int($k))) || !array_is_list($sub))){
889 return $arr;
890 }
891
892 return wpjam_array($arr, fn($k, $v)=> is_int($k) ? $v : $k);
893 }
894
895 function wpjam_except($arr, $key, ...$args){
896 if(is_object($arr)){
897 foreach((array)$key as $k){
898 unset($arr->$k);
899 }
900
901 return $arr;
902 }
903
904 if(!is_array($arr)){
905 trigger_error(var_export($arr, true));
906 return $arr;
907 }
908
909 if(is_array($key) || wpjam_exists($arr, $key)){
910 return array_diff_key($arr, is_array($key) ? array_flip($key) : [$key=>'']);
911 }
912
913 if(str_ends_with($key, '[]') && $args){
914 return wpjam_set($arr, substr($key, 0, -2), array_diff(wpjam_get($arr, $key, [], '[]'), $args), '[]');
915 }
916
917 $key = wpjam_keys($key, ...$args);
918 $sub = &$arr;
919
920 while($key){
921 $k = array_shift($key);
922
923 if(empty($key)){
924 unset($sub[$k]);
925 }elseif(wpjam_exists($sub, $k)){
926 $sub = &$sub[$k];
927 }else{
928 break;
929 }
930 }
931
932 return $arr;
933 }
934
935 function wpjam_merge($arr, $data){
936 foreach($data as $k => $v){
937 $arr[$k] = ((wpjam_is_assoc_array($v) || $v === []) && isset($arr[$k]) && wpjam_is_assoc_array($arr[$k])) ? wpjam_merge($arr[$k], $v) : $v;
938 }
939
940 return $arr;
941 }
942
943 function wpjam_diff($arr, $data, $compare='value'){
944 if($compare == 'value' && array_is_list($arr) && array_is_list($data)){
945 return array_values(array_diff($arr, $data));
946 }
947
948 foreach($data as $k => $v){
949 if(isset($arr[$k])){
950 if(wpjam_is_assoc_array($v) && wpjam_is_assoc_array($arr[$k])){
951 $arr[$k] = wpjam_diff($arr[$k], $v, $compare);
952
953 if(!$arr[$k]){
954 unset($arr[$k]);
955 }
956 }else{
957 if($compare == 'key' || $arr[$k] == $v){
958 unset($arr[$k]);
959 }
960 }
961 }
962 }
963
964 return $arr;
965 }
966
967 function wpjam_toggle($arr, $data){
968 return array_merge(array_diff($arr, $data), array_diff($data, $arr));
969 }
970
971 function wpjam_filter($arr, $cb=null, ...$args){
972 $list = array_is_list($arr);
973
974 if($cb === 'unique'){
975 $arr = array_unique($arr);
976 }elseif($cb && is_array($cb) && !is_callable($cb)){
977 $arr = wpjam_is_assoc_array($cb) ? array_filter($arr, fn($v)=> wpjam_matches($v, $cb, ...$args)) : array_intersect_key($arr, array_flip($cb));
978 }elseif($cb){
979 $arr = ($args[0] ?? ($cb == 'isset')) ? array_map(fn($v)=> is_array($v) ? wpjam_filter($v, $cb, true) : $v, $arr) : $arr;
980 $arr = array_filter($arr, $cb === 'isset' ? fn($v)=> !is_null($v) : $cb, ARRAY_FILTER_USE_BOTH);
981 }else{
982 $arr = array_filter($arr);
983 }
984
985 return $list ? array_values($arr) : $arr;
986 }
987
988 function wpjam_sort($arr, ...$args){
989 if(count($arr) <= 1){
990 return $arr;
991 }
992
993 if(!$args || is_int($args[0])){
994 sort($arr, ...$args);
995
996 return $arr;
997 }
998
999 if(in_array($args[0], ['', 'k', 'a', 'kr', 'ar', 'r'], true)){
1000 (array_shift($args).'sort')($arr, ...$args);
1001
1002 return $arr;
1003 }
1004
1005 $is_asc = fn($v)=> is_int($v) ? $v === SORT_ASC : strtolower($v) === 'asc';
1006
1007 if(wpjam_is_assoc_array($args[0])){
1008 $args = wpjam_reduce($args[0], fn($carry, $order, $field)=>[
1009 ...$carry,
1010 ($column = array_column($arr, $field)),
1011 $is_asc($order) ? SORT_ASC : SORT_DESC,
1012 is_numeric(array_first($column)) ? SORT_NUMERIC : SORT_REGULAR
1013 ], []);
1014 }elseif(is_callable($args[0]) || is_string($args[0])){
1015 $field = $args[0];
1016 $order = $args[1] ?? '';
1017
1018 if(is_callable($field)){
1019 $column = array_map($field, ($order === 'key' ? array_keys($arr) : $arr));
1020 $flag = $args[2] ?? SORT_NUMERIC;
1021 }else{
1022 $default= $args[2] ?? 0;
1023 $column = array_map(fn($item)=> wpjam_get($item, $field, $default), $arr);
1024 $flag = is_numeric($default) ? SORT_NUMERIC : SORT_REGULAR;
1025 }
1026
1027 $args = [$column, ($is_asc($order) ? SORT_ASC : SORT_DESC), $flag];
1028 }
1029
1030 array_push($args, range(1, count($arr)), SORT_ASC, SORT_NUMERIC);
1031
1032 if(wp_is_numeric_array($arr)){
1033 $keys = array_keys($arr);
1034 $args[] = &$keys;
1035 }
1036
1037 $args[] = &$arr;
1038
1039 array_multisort(...$args);
1040
1041 return isset($keys) ? array_combine($keys, $arr) : $arr;
1042 }
1043
1044 function wpjam_exists($arr, $key){
1045 return is_array($arr) ? array_key_exists($key, $arr) : (is_object($arr) ? isset($arr->$key) : false);
1046 }
1047
1048 function wpjam_keys($key, ...$args){
1049 if(!$args){
1050 return wpjam_keys($key, '[]') ?: (wpjam_keys($key, '.') ?: []);
1051 }
1052
1053 $keys = [];
1054
1055 if($args[0] == '.'){
1056 if(str_contains($key, '.')){
1057 return explode('.', $key);
1058 }
1059 }elseif($args[0] == '[]'){
1060 $total = strlen($key);
1061 $cursor = 0;
1062
1063 while($cursor < $total){
1064 $offset = $keys ? 1 : 0;
1065 $len = $offset && $key[$cursor] !== '[' ? 0 : strcspn($key, $offset ? ']' : '[', $cursor);
1066 $sub = ($len > $offset && $len < $total-$cursor) ? substr($key, $cursor+$offset, $len-$offset) : '';
1067
1068 if($sub === '' || str_contains($sub, $offset ? '[' : ']')){
1069 return [];
1070 }
1071
1072 $keys[] = $sub;
1073 $cursor += $len + $offset;
1074 }
1075 }
1076
1077 return $keys;
1078 }
1079
1080 function wpjam_names($name){
1081 return is_array($name)
1082 ? array_shift($name).($name ? '['.implode('][', $name).']' : '')
1083 : ($name ? (wpjam_keys($name, '[]') ?: [$name]) : []);
1084 }
1085
1086 function wpjam_get($arr, $key, $default=null, ...$args){
1087 if(is_object($arr)){
1088 return $arr->$key ?? $default;
1089 }
1090
1091 if(!is_array($arr)){
1092 trigger_error(var_export($arr, true));
1093 return $default;
1094 }
1095
1096 if(!is_array($key)){
1097 if(isset($key) && wpjam_exists($arr, $key)){
1098 return $arr[$key];
1099 }
1100
1101 if(is_null($key)){
1102 return $arr;
1103 }
1104
1105 if(!$args || $args[0] === '[]'){
1106 if($key === '[]'){
1107 return $arr;
1108 }
1109
1110 if(str_ends_with($key, '[]')){
1111 $value = wpjam_get($arr, substr($key, 0, -2), $default, '[]');
1112
1113 return is_object($value) ? [$value] : (array)$value;
1114 }
1115 }
1116
1117 $key = wpjam_keys($key, ...$args);
1118 }
1119
1120 return _wp_array_get($arr, $key, $default);
1121 }
1122
1123 function wpjam_set($arr, $key, ...$args){
1124 if(wpjam_is_assoc_array($key)){
1125 if(!$args){ // del 2026-12-31
1126 return wpjam_reduce($key, fn($c, $v, $k)=> wpjam_set($c, $k, $v, ...$args), $arr);
1127 }
1128
1129 trigger_error(var_export($key, true));
1130 }
1131
1132 $value = array_shift($args);
1133
1134 if(is_object($arr)){
1135 $arr->$key = $value;
1136
1137 return $arr;
1138 }
1139
1140 if(!is_array($arr)){
1141 trigger_error(var_export($arr, true));
1142 return $arr;
1143 }
1144
1145 if(!is_array($key)){
1146 if(isset($key) && wpjam_exists($arr, $key)){
1147 $arr[$key] = $value;
1148
1149 return $arr;
1150 }
1151
1152 if(is_null($key)){
1153 $arr[] = $value;
1154
1155 return $arr;
1156 }
1157
1158 if(!$args || $args[0] === '[]'){
1159 if($key === '[]'){
1160 $arr[] = $value;
1161
1162 return $arr;
1163 }
1164
1165 if(str_ends_with($key, '[]')){
1166 $items = wpjam_get($arr, $key, [], '[]');
1167 $items[] = $value;
1168
1169 return wpjam_set($arr, substr($key, 0, -2), $items, '[]');
1170 }
1171 }
1172
1173 $key = wpjam_keys($key, ...$args) ?: [$key];
1174 }
1175
1176 _wp_array_set($arr, $key, $value);
1177
1178 return $arr;
1179 }
1180
1181 function wpjam_pack($value, $key){
1182 return is_null($key) ? $value : wpjam_set([], $key, $value);
1183 }
1184
1185 function wpjam_some($arr, $cb){
1186 foreach($arr as $k => $v){
1187 if($cb($v, $k)){
1188 return true;
1189 }
1190 }
1191
1192 return false;
1193 }
1194
1195 function wpjam_every($arr, $cb){
1196 foreach($arr as $k => $v){
1197 if(!$cb($v, $k)){
1198 return false;
1199 }
1200 }
1201
1202 return true;
1203 }
1204
1205 function wpjam_lines($str, ...$args){
1206 $sep = ($args && is_closure($args[0]) ? '' : array_shift($args)) ?: "\n";
1207 $cb = array_shift($args);
1208 $lines = [];
1209
1210 foreach(explode($sep, (string)$str) as $v){
1211 $v = trim($v);
1212 $v = $cb ? $cb($v) : $v;
1213
1214 if(!is_blank($v)){
1215 $lines[] = $v;
1216 }
1217 }
1218
1219 return $lines;
1220 }
1221
1222 if(!function_exists('array_pull')){
1223 function array_pull(&$arr, $key, ...$args){
1224 return wpjam_pull($arr, $key, ...$args);
1225 }
1226 }
1227
1228 if(!function_exists('array_except')){
1229 function array_except($array, ...$keys){
1230 return wpjam_except($array, (($keys && is_array($keys[0])) ? $keys[0] : $keys));
1231 }
1232 }
1233
1234 if(!function_exists('array_first')){
1235 function array_first($array){
1236 return $array === [] ? null : $array[array_key_first($array)];
1237 }
1238 }
1239
1240 if(!function_exists('array_last')){
1241 function array_last($array){
1242 return $array === [] ? null : $array[array_key_last($array)];
1243 }
1244 }
1245
1246 function wpjam_move($arr, $id, $data){
1247 $arr = array_values($arr);
1248 $index = array_search($id, $arr);
1249 $arr = wpjam_diff($arr, [$id]);
1250
1251 $index === false && wpjam_throw('invalid_id', '无效的 ID');
1252
1253 if(isset($data['pos'])){
1254 $index = $data['pos'];
1255 }elseif(!empty($data['up'])){
1256 $index == 0 && wpjam_throw('invalid_position', '已经是第一个了,不可上移了!');
1257
1258 $index--;
1259 }elseif(!empty($data['down'])){
1260 $index == count($arr) && wpjam_throw('invalid_position', '已经最后一个了,不可下移了!');
1261
1262 $index++;
1263 }else{
1264 $k = array_find(['next', 'prev'], fn($k)=> isset($data[$k]));
1265 $index = ($k && isset($data[$k])) ? array_search($data[$k], $arr) : false;
1266
1267 $index === false && wpjam_throw('invalid_position', '无效的移动位置');
1268
1269 $index += $k == 'prev' ? 1 : 0;
1270 }
1271
1272 return wpjam_add_at($arr, $index, null, $id);
1273 }
1274
1275 // Bit
1276 function wpjam_has_bit($value, $bit){
1277 return ((int)$value & (int)$bit) == $bit;
1278 }
1279
1280 function wpjam_add_bit($value, $bit){
1281 return (int)$value | (int)$bit;
1282 }
1283
1284 function wpjam_remove_bit($value, $bit){
1285 return (int)$value & (~(int)$bit);
1286 }
1287
1288 // UUID
1289 function wpjam_create_uuid(){
1290 $chars = md5(uniqid(mt_rand(), true));
1291
1292 return implode('-', array_map(fn($v)=> substr($chars, ...$v), [[0, 8], [8, 4], [12, 4], [16, 4], [20, 12]]));
1293 }
1294
1295 // Str
1296 if(!function_exists('explode_last')){
1297 function explode_last($sep, $str, $limit=2){
1298 $parts = explode($sep, $str);
1299 $count = count($parts);
1300
1301 return $limit >= $count ? $parts : [implode($sep, array_splice($parts, 0, $count - $limit + 1)), ...$parts];
1302 }
1303 }
1304
1305 if(!function_exists('try_fix')){
1306 function try_fix($type, &$str, ...$args){
1307 $action = count($args) > 1 && in_array($args[0], ['+', '-']) ? array_shift($args) : '+';
1308 $fix = array_shift($args);
1309 $has = $type == 'prefix' ? str_starts_with($str, $fix) : str_ends_with($str, $fix);
1310 $res = $has !== ($action == '+');
1311
1312 if($res){
1313 $str = $type == 'prefix'
1314 ? ($has ? substr($str, strlen($fix)) : $fix.$str)
1315 : ($has ? substr($str, 0, -strlen($fix)) : $str.$fix);
1316 }
1317
1318 return $res;
1319 }
1320 }
1321
1322 if(!function_exists('try_prefix')){
1323 function try_prefix(&$str, ...$args){
1324 return try_fix('prefix', $str, ...$args);
1325 }
1326 }
1327
1328 if(!function_exists('try_suffix')){
1329 function try_suffix(&$str, ...$args){
1330 return try_fix('suffix', $str, ...$args);
1331 }
1332 }
1333
1334 function wpjam_prefix($str, ...$args){
1335 try_prefix($str, ...$args);
1336
1337 return $str;
1338 }
1339
1340 function wpjam_suffix($str, ...$args){
1341 try_suffix($str, ...$args);
1342
1343 return $str;
1344 }
1345
1346 function wpjam_join($sep, ...$args){
1347 return join($sep, array_filter(($args && is_array($args[0])) ? $args[0] : $args));
1348 }
1349
1350 function wpjam_remove_pre_tab($str, $times=1){
1351 return preg_replace('/^\t{'.$times.'}/m', '', $str);
1352 }
1353
1354 function wpjam_preg_replace($pattern, $replace, $subject, $limit=-1, &$count=null, $flags=0){
1355 $result = is_closure($replace) ? preg_replace_callback($pattern, $replace, $subject, $limit, $count, $flags) : preg_replace($pattern, $replace, $subject, $limit, $count);
1356
1357 if(is_null($result)){
1358 trigger_error(preg_last_error_msg());
1359 return $subject;
1360 }
1361
1362 return $result;
1363 }
1364
1365 function wpjam_serialize($data){
1366 return maybe_serialize(wpjam_sort(wpjam_map($data, fn($v)=> is_closure($v) ? spl_object_hash($v) : $v, true), 'k'));
1367 }
1368
1369 function wpjam_unserialize($serialized, $cb=null){
1370 if($serialized){
1371 $result = @unserialize($serialized, ['allowed_classes'=>false]);
1372
1373 if(!$result){
1374 $fixed = preg_replace_callback('!s:(\d+):"(.*?)";!', fn($m)=> 's:'.strlen($m[2]).':"'.$m[2].'";', $serialized);
1375 $result = @unserialize($fixed, ['allowed_classes'=>false]);
1376
1377 $result && $cb && $cb($fixed);
1378 }
1379
1380 return $result;
1381 }
1382 }
1383
1384 // 去掉非 utf8mb4 字符
1385 function wpjam_strip_invalid_text($text){
1386 return $text ? iconv('UTF-8', 'UTF-8//IGNORE', $text) : '';
1387 }
1388
1389 // 去掉 4字节 字符
1390 function wpjam_strip_4_byte_chars($text){
1391 return $text ? preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $text) : '';
1392 // \xEF\xBF\xBD 常用来表示未知、未识别或不可表示的字符
1393 }
1394
1395 // 移除 除了 line feeds 和 carriage returns 所有控制字符
1396 function wpjam_strip_control_chars($text){
1397 return $text ? preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F]/u', '', $text) : '';
1398 // /[\x00-\x09\x0B\x0C\x0E-\x1F\x80-\x9F]/u
1399 }
1400
1401 //获取第一段
1402 function wpjam_get_first_p($text){
1403 $text = wp_strip_all_tags($text);
1404 $line = strtok($text, "\n");
1405
1406 while($line !== false){
1407 if($line = trim($line)){
1408 return $line;
1409 }
1410
1411 $line = strtok("\n");
1412 }
1413
1414 return '';
1415 }
1416
1417 function wpjam_unicode_decode($text){
1418 return wpjam_preg_replace('/\\\\u(?!00[0-7][0-9A-F])[0-9a-fA-F]{4}/i', fn($m)=> json_decode('"'.$m[0].'"') ?: $m[0], $text);
1419 }
1420
1421 function wpjam_zh_urlencode($url){
1422 return $url ? wpjam_preg_replace('/[\x{4e00}-\x{9fa5}]+/u', fn($m)=> urlencode($m[0]), $url) : '';
1423 }
1424
1425 // 检查非法字符
1426 function wpjam_blacklist_check($text, $name='�
1427 容'){
1428 $pre = $text ? apply_filters('wpjam_pre_blacklist_check', null, $text, $name) : false;
1429
1430 if(isset($pre)){
1431 return $pre;
1432 }
1433
1434 $key = strtok(get_option('disallowed_keys'), "\n");
1435
1436 while($key !== false){
1437 if(($key = trim($key)) && stripos($text, $key) !== false){
1438 return true;
1439 }
1440
1441 $key = strtok("\n");
1442 }
1443
1444 return false;
1445 }
1446
1447 function wpjam_expandable($str, $num=10, $name=null){
1448 if(is_a($str, 'WPJAM_Tag') || count(explode("\n", $str)) > $num){
1449 static $index = 0;
1450
1451 $name = 'expandable_'.($name ?? (++$index));
1452
1453 return '<div class="expandable-container"><input type="checkbox" class="button" id="'.esc_attr($name).'" /><div class="inner">'.$str.'</div></div>';
1454 }
1455
1456 return $str;
1457 }
1458
1459 function wpjam_get_current_page_url(){
1460 return set_url_scheme('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
1461 }
1462
1463 // Date
1464 function wpjam_date($format, $ts=null){
1465 $ts ??= time();
1466 $dt = $ts ? date_create('@'.$ts) : null;
1467
1468 return $dt ? $dt->setTimezone(wp_timezone())->format($format) : '';
1469 }
1470
1471 function wpjam_strtotime($str){
1472 $dt = $str ? date_create($str, wp_timezone()) : null;
1473
1474 return $dt ? $dt->getTimestamp() : 0;
1475 }
1476
1477 function wpjam_human_time_diff($from, $to=0){
1478 return sprintf(__('%s '.(($to ?: time()) > $from ? 'ago' : 'from now'), 'wpjam'), human_time_diff($from, $to));
1479 }
1480
1481 function wpjam_human_date_diff($from, $to=0){
1482 $zone = wp_timezone();
1483 $to = $to ? date_create($to, $zone) : current_datetime();
1484 $from = date_create($from, $zone);
1485 $day = [
1486 0 => __('Today', 'wpjam'),
1487 -1 => __('Yesterday', 'wpjam'),
1488 -2 => __('The day before yesterday', 'wpjam'),
1489 1 => __('Tomorrow', 'wpjam'),
1490 2 => __('The day after tomorrow', 'wpjam')
1491 ][(int)$to->diff($from)->format('%R%a')] ?? '';
1492
1493 return $day ?: ($from->format('W') == $to->format('W') ? __($from->format('l'), 'wpjam') : $from->format(__('F, Y', 'wpjam')));
1494 }
1495
1496 // Video
1497 function wpjam_get_video_mp4($id_or_url){
1498 if(filter_var($id_or_url, FILTER_VALIDATE_URL)){
1499 if(preg_match('#http://www.miaopai.com/show/(.*?).htm#i',$id_or_url, $matches)){
1500 return 'http://gslb.miaopai.com/stream/'.esc_attr($matches[1]).'.mp4';
1501 }
1502
1503 return ($id = wpjam_get_qqv_id($id_or_url)) ? wpjam_get_qqv_mp4($id) : wpjam_zh_urlencode($id_or_url);
1504 }
1505
1506 return wpjam_get_qqv_mp4($id_or_url);
1507 }
1508
1509 function wpjam_get_qqv_mp4($vid, $cache=true){
1510 strlen($vid) > 20 && wpjam_throw('error', '无效的�
1511 �讯视频');
1512
1513 if($cache){
1514 return wpjam_transient('qqv_mp4:'.$vid, fn()=> wpjam_get_qqv_mp4($vid, false), HOUR_IN_SECONDS*6);
1515 }
1516
1517 $resp = wpjam_remote_request('http://vv.video.qq.com/getinfo?otype=json&platform=11001&vid='.$vid, ['timeout'=>4, 'throw'=>true]);
1518 $resp = trim(substr($resp, strpos($resp, '{')),';');
1519 $resp = wpjam_try('wpjam_json_decode', $resp);
1520
1521 empty($resp['vl']) && wpjam_throw('error', '�
1522 �讯视频不存在或�
1523 为收费视频!');
1524
1525 $u = $resp['vl']['vi'][0];
1526
1527 return $u['ul']['ui'][0]['url'].$u['fn'].'?vkey='.$u['fvkey'];
1528 }
1529
1530 function wpjam_get_qqv_id($id_or_url){
1531 if(filter_var($id_or_url, FILTER_VALIDATE_URL)){
1532 return wpjam_find(['page', 'cover/.*'], true, fn($v)=> preg_match('#https://v.qq.com/x/'.$v.'/(.*?).html#i', $id_or_url, $m) ? $m[1] : '') ?: '';
1533 }
1534
1535 return $id_or_url;
1536 }
1537
1538 function wpjam_is_mobile_number($number){
1539 return preg_match('/^0{0,1}(1[3,5,8][0-9]|14[5,7]|166|17[0,1,3,6,7,8]|19[8,9])[0-9]{8}$/', $number);
1540 }
1541
1542 function wpjam_set_cookie($key, $value, $expire=DAY_IN_SECONDS){
1543 if(is_null($value)){
1544 unset($_COOKIE[$key]);
1545
1546 $expire = time()-YEAR_IN_SECONDS;
1547 }else{
1548 $_COOKIE[$key] = $value;
1549
1550 $expire += $expire < time() ? time() : 0;
1551 }
1552
1553 setcookie($key, $value ?? '', $expire, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
1554
1555 COOKIEPATH != SITECOOKIEPATH && setcookie($key, $value ?? '', $expire, SITECOOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
1556 }
1557
1558 function wpjam_clear_cookie($key){
1559 wpjam_set_cookie($key, null);
1560 }
1561
1562 function wpjam_get_filter_name($name, $type){
1563 return (str_starts_with($name, 'wpjam') ? '' : 'wpjam_').str_replace('-', '_', $name).'_'.$type;
1564 }
1565