$field(...$args); } if(try_suffix($field, '-', '[]')){ $method = $args ? (count($args) <= 2 && is_null(array_last($args)) ? 'delete' : 'add') : 'get'; }else{ $method = $args && (count($args) > 1 || is_array($args[0])) ? 'set' : 'get'; } return $this->$method($field, ...$args); } public function add($field, $key, ...$args){ [$key, $item] = $args ? [$key, $args[0]] : [null, $key]; if(isset($key) && !str_ends_with($key, '[]') && $this->get($field, $key) !== null){ return new WP_Error('invalid_key', '「'.$key.'」已存在,无法添加'); } return $this->set($field, $key, $item); } public function set($field, $key, ...$args){ $this->data[$field] = is_array($key) ? array_merge(($args && $args[0]) ? $this->get($field) : [], $key) : wpjam_set($this->get($field), $key ?? '[]', ...$args); return is_array($key) ? $key : $args[0]; } public function delete($field, ...$args){ if($args){ return $this->data[$field] = wpjam_except($this->get($field), $args[0]); } unset($this->data[$field]); } public function get($field, ...$args){ return $args ? wpjam_get($this->get($field), ...$args) : ($this->data[$field] ?? []); } public static function get_instance(){ static $object; return $object ??= new self(); } public static function __callStatic($method, $args){ $function = 'wpjam_'.$method; if(function_exists($function)){ return $function(...$args); } } } class WPJAM_Callback{ private $reflections = []; private $annotations = []; public function __invoke($cb, ...$args){ if(is_null($cb)){ return $this; } if(is_string($cb)){ if(in_array($cb, ['', 'try', 'catch', 'method'], true)){ return $this->call($cb, ...$args); } if(method_exists($this, $cb)){ return $this->$cb(...$args); } } return $this->parse($args ? 'try' : '', $cb, ...$args); } public function call($type, $cb, ...$args){ $res = $this->parse($type, $cb, $args); if(is_array($res)){ [$cb, $args] = $res; return $cb(...$args); } return $res; } public function parse($type, $cb, ...$args){ if(is_string($cb) && preg_match('/::|->/', $cb, $m)){ $sep = $m[0]; $static = $sep == '::'; $cb = explode($sep, $cb, 2); }elseif(!is_array($cb)){ if($type == 'method'){ $cb = [$cb, array_shift($args[0])]; } } if(is_array($cb)){ if(!$cb[0] || (is_string($cb[0]) && !class_exists($cb[0]))){ return $this->invalid($type, $cb[0], 'model'); } $exists = $cb[1] && method_exists(...$cb); if($type == 'method' && !$exists){ return; } }else{ $exists = $cb && is_callable($cb); } if(!$args){ return $exists ? $cb : null; } $args = $args[0]; if(is_array($cb) && is_string($cb[0])){ $sep ??= '::'; $static ??= $exists ? $this->reflection($cb, 'isStatic') : ($cb[1] ? method_exists($cb[0], '__callStatic') : null); if(is_null($static) || (!$exists && !method_exists($cb[0], '__call'.($static ? 'Static' : '')))){ return $this->invalid($type, implode($sep, $cb)); } if(!$static){ if($cb[1] == 'value_callback' && count($args) == 2){ $args = array_reverse($args); } $ins = [$cb[0], 'get_instance']; $num = $this->reflection($ins, 'NumberOfRequiredParameters'); if(is_null($num) || count($args) < $num){ return $this->invalid($type, implode($sep, $cb)); } $cb[0] = $ins(...array_splice($args, 0, $num ?: 1)); if(!$cb[0]){ return $this->invalid($type, $cb[0], 'id'); } } $cb = ($public ?? true) ? $cb : $this->reflection($cb, 'Closure')($static ? null : $cb[0]); }else{ if(!is_callable($cb)){ return $this->invalid($type, $cb); } } return [$cb, $args]; } private function invalid($type, $msg, $code='callback'){ if(in_array($type, ['try', 'catch'])){ $code = 'invalid_'.$code; return $type == 'catch' ? new WP_Error($code, [$msg]) : wpjam_throw($code, [$msg]); } } public function value_callback($args, $name){ $path = (array)$name; $name = array_shift($path); $id = $args['id'] ?? null; $cb = $args['value_callback'] ?? ''; $mt = $id ? ($args['meta_type'] ?? '') : ''; if(!$cb && ($model = $args['model'] ?? '')){ if($id && $path && $name == 'meta_input' && ($mt = $mt ?: $this->call('method', $model.'::get_meta_type'))){ $name = array_shift($path); }else{ $cb = $this->parse('', [$model, 'value_callback']); } } foreach([$cb, $mt] as $i => $t){ $value = $t ? wpjam_if_error(($i === 0 ? $this->call('', $t, $name, $id) : wpjam_get_metadata($t, $id, $name)), null) : null; $value = $path ? wpjam_get($value ?: [], $path) : $value; if(isset($value)){ return $value; } } } public function render($cb){ return wpautop(is_array($cb) ? (is_object($cb[0]) ? get_class($cb[0]).'->' : $cb[0].'::').(string)$cb[1] : (is_object($cb) ? get_class($cb) : $cb)); } public function uniqid($cb){ return _wp_filter_build_unique_id(null, $cb, null); } public function reflection($cb, ...$args){ if($cb === 'class'){ $type = 'class'; $class = array_shift($args); $key = class_exists($class) ? strtolower($class) : null; }else{ $type = 'cb'; $cb = $this->parse('try', is_array($cb) && !is_string($cb[0]) ? [get_class($cb[0]), $cb[1]] : $cb); $key = $cb ? $this->uniqid($cb) : null; } if($key){ $param = $args ? array_shift($args) : ''; $ref = $this->reflections[$type][$key] ??= $type == 'class' ? new ReflectionClass($class) : (is_array($cb) ? new ReflectionMethod(...$cb) : new ReflectionFunction($cb)); return $param ? [$ref, (array_find(['get', 'is', 'has', 'in'], fn($v)=> str_starts_with($param, $v)) ? '' : 'get').$param](...$args) : $ref; } } public function annotation($class, $key=''){ $data = $this->annotations[strtolower($class)] ??= (function($ref){ if(method_exists($ref, 'getAttributes')){ foreach($ref->getAttributes() as $attr){ $k = $attr->getName(); $v = $attr->getArguments(); $v = ($v && wp_is_numeric_array($v) && ($k == 'config' ? is_array($v[0]) : count($v) == 1)) ? $v[0] : $v; $data[$k] = $v ?: null; } }elseif(preg_match_all('/@([a-z0-9_]+)\s+([^\r\n]*)/i', ($ref->getDocComment() ?: ''), $matches, PREG_SET_ORDER)){ foreach($matches as $m){ $k = $m[1]; $v = trim($m[2]) ?: null; $data[$k] = ($v && $k == 'config') ? wp_parse_list($v) : $v; } } $data['config'] = wpjam_array($data['config'] ?? [], fn($k, $v)=> is_numeric($k) ? (str_contains($v, '=') ? explode('=', $v, 2) : [$v, true]) : [$k, $v]); return $data; })($this->reflection('class', $class)); return wpjam_get($data, $key ?: null); } public static function get_instance(){ static $object; return $object ??= new self(); } } class WPJAM_Query{ private $type; private function __construct($type){ $this->type = $type; } public function is($query, ...$args){ if($query->is_main_query()){ if($args){ return array_any(wp_parse_list(array_shift($args)), fn($t)=> [$query, 'is_'.$t](...$args)); } return $query->is_front_page() ? 'home' : (array_find(['feed', 'author', 'category', 'tag', 'tax', 'post_type_archive', 'search', 'date', 'archive', '404', 'page', 'single', 'attachment'], fn($t)=> [$query, 'is_'.$t]()) ?: true); } } public function query($vars, &$args=[]){ if($this->type == 'term'){ $number = (int)wpjam_pull($vars, 'number'); $paged = wpjam_pull($vars, 'paged') ?: 1; $args += wpjam_pull($vars, ['format', 'parse']); $terms = $this->parse($vars, $args); return $terms && $number ? array_slice($terms, $number * ($paged-1), $number) : $terms; } if(!empty($vars['related_query'])){ $post = get_post(wpjam_pull($vars, 'post') ?? get_the_ID()); $type = get_post_type($post); $tt_ids = []; foreach($post ? get_object_taxonomies($type) : [] as $tax){ if($terms = $tax == 'post_format' ? [] : get_the_terms($post, $tax)){ $type = array_merge((array)$type, get_taxonomy($tax)->object_type); $tt_ids = array_merge($tt_ids, array_column($terms, 'term_taxonomy_id')); } } if(!$tt_ids){ return false; } $vars += ['post_status'=>'publish', 'post__not_in'=>[$post->ID], 'post_type'=>array_unique($type), 'orderby'=>'related', 'term_taxonomy_ids'=>wpjam_filter($tt_ids, 'unique')]; } $vars = $this->parse_vars($vars); if($args){ $vars = array_filter(['posts_per_page'=>wpjam_pull($args, 'number')])+$vars; $vars = wpjam_pull($args, ['post_type', 'orderby', 'posts_per_page'])+$vars; $vars = ($days = wpjam_pull($args, 'days')) ? wpjam_set($vars, 'date_query[]', [ 'column' => wpjam_pull($args, 'column') ?: 'post_date_gmt', 'after' => wpjam_date('Y-m-d', time()-DAY_IN_SECONDS*$days).' 00:00:00' ]) : $vars; } return new WP_Query($vars+['no_found_rows'=>true, 'ignore_sticky_posts'=>true]); } public function parse($vars, $args=[]){ $format = wpjam_pull($args, 'format'); $parse = wpjam_pull($args, 'parse'); if(is_null($parse)){ $parse = true; $args += $this->type == 'post' ? ['options_required'=>false, 'taxonomy_required'=>false] : []; } if($this->type == 'term'){ $object = ($tax = $vars['taxonomy'] ?? '') && is_string($tax) ? wpjam_get_taxonomy($tax) : null; $terms = $vars['terms'] ?? null; $depth = $args['depth'] ?? ($object ? $object->max_depth : null); if($depth != -1 && $object && $object->hierarchical){ $nest = ['max_depth'=>($depth ?? (int)$object->levels), 'format'=>$format]; $nest += $parse ? ['item_callback'=>'wpjam_get_term'] : ['fields'=>['id'=>'term_id']]; if(($parent = (int)wpjam_pull($vars, 'parent')) && !($nest['top'] = get_term($parent))){ return []; } if($terms){ $ids = array_column($terms, 'term_id'); $terms = WPJAM_Term::get_by_ids(array_unique(array_reduce($ids, fn($c, $id)=> array_merge($c, get_ancestors($id, $tax, 'taxonomy')), $ids))); } } return wpjam_then( ($terms ?? get_terms($vars+['hide_empty'=>false])) ?: [], fn($v)=> isset($nest) ? wpjam_nest($v, $nest) : ($parse ? array_values(array_map('wpjam_get_term', $v)) : $v) ); } $query = is_object($vars) ? $vars : $this->query($vars, $args); if(!$query || !$parse){ return $query ? $query->posts : []; } $parsed = []; $args += ['thumbnail_size'=>wpjam_pull($args, 'size')]; $args += $query->get('related_query') ? ['filter'=>'wpjam_related_post_json'] : []; while($query->have_posts()){ $query->the_post(); if($item = wpjam_get_post(get_the_ID(), $args+['query'=>$query])){ $parsed = wpjam_set($parsed, ($format == 'date' ? wpjam_at($item['date'], ' ', 0) : '').'[]', $item); } } if(!$query->is_main_query()){ wp_reset_postdata(); } return $parsed; } public function parse_vars($vars, $param=false){ if(($type = $vars['post_type'] ?? '') && is_string($type) && str_contains($type, ',')){ $vars['post_type'] = wp_parse_list($type); } if($param === 'calendar'){ $args = ['year'=>['year', 'Y'], 'monthnum'=>['month', 'm'], 'day'=>['day', '']]; $vars += wpjam_map($args, fn($v)=> (int)wpjam_param($v[0]) ?: ($v[1] ? wpjam_date($v[1]) : null)); }elseif($param){ $number = wpjam_find(['number', 'posts_per_page'], fn($v)=> $v, fn($k)=> ($v = (int)wpjam_param($k)) > 0 ? $v : 0); $ids = wpjam_param('post__in'); $vars += array_filter(['offset'=>wpjam_param('offset'), 'posts_per_page'=>min($number, 100)]); $vars += $ids ? ['include'=>$ids, 'orderby'=>'post__in'] : []; } foreach(get_taxonomies([], 'objects') as $tax => $object){ if(in_array($tax, ['category', 'post_tag']) || !$object->_builtin){ $qk = wpjam_get_taxonomy_query_key($tax); $keys = $tax == 'category' ? ['category_id', 'cat_id'] : [$qk]; if($value = ($param ? array_reduce($keys, fn($c, $k)=> (int)wpjam_param($k) ?: $c, 0) : 0) ?: wpjam_pull($vars, $qk)){ $term_ids[$tax] = $value; } } } if(!empty($vars['taxonomy']) && empty($vars['term']) && ($value = wpjam_pull($vars, 'term_id'))){ if(is_numeric($value)){ $term_ids[wpjam_pull($vars, 'taxonomy')] = $value; }else{ $vars['term'] = $value; } } foreach(array_filter($term_ids ?? []) as $tax => $value){ if($tax == 'category' && $value != 'none'){ $vars['cat'] = $value; }else{ $vars['tax_query'][] = ['taxonomy'=>$tax, 'field'=>'term_id']+($value == 'none' ? ['operator'=>'NOT EXISTS'] : ['terms'=>[$value]]); } } foreach(wpjam_pull($vars, ['include', 'exclude']) as $k => $v){ if($ids = wp_parse_id_list($v)){ $vars[$k == 'include' ? 'post__in' : 'post__not_in'] = $ids; $k == 'include' && ($vars['posts_per_page'] = count($ids)); } } foreach(['cursor'=>'before', 'since'=>'after'] as $k => $v){ if($value = (int)(($param ? wpjam_param($k) : 0) ?: wpjam_pull($vars, $k))){ $vars['date_query'][] = [$v => wpjam_date('Y-m-d H:i:s', $value)]; $vars['ignore_sticky_posts'] = true; } } return $vars; } public function render($vars, $args=[]){ $cb = wpjam_fill(['item_callback', 'wrap_callback'], fn($k)=> $args[$k] ?? [$this, $k]); $query = is_object($vars) ? $vars : $this->query($vars, $args); $args += ['query'=>$query]; return $query ? $cb['wrap_callback'](implode(wpjam_map($query->posts, fn($p, $i)=> $cb['item_callback']($p->ID, $args+['i'=>$i]))), $args) : ''; } public function item_callback($post_id, $args){ $args += ['title_number'=>'', 'excerpt'=>false, 'thumb'=>true, 'size'=>'thumbnail', 'thumb_class'=>'wp-post-image', 'wrap_tag'=>'li']; $title = get_the_title($post_id); $item = wpjam_wrap($title); $args['title_number'] && $item->before('span', ['title-number'], zeroise($args['i']+1, strlen(count($args['query']->posts))).'. '); ($args['thumb'] || $args['excerpt']) && $item->wrap('h4'); $args['thumb'] && $item->before(get_the_post_thumbnail($post_id, $args['size'], ['class'=>$args['thumb_class']])); $args['excerpt'] && $item->after(wpautop(get_the_excerpt($post_id))); return $item->wrap('a', ['href'=>get_permalink($post_id), 'title'=>strip_tags($title)])->wrap($args['wrap_tag'])->render(); } public function wrap_callback($output, $args){ if(!$output){ return ''; } $args += ['title'=>'', 'div_id'=>'', 'class'=>[], 'thumb'=>true, 'wrap_tag'=>'ul']; $output = wpjam_wrap($output); $args['wrap_tag'] && $output->wrap($args['wrap_tag'])->add_class($args['class'])->add_class($args['thumb'] ? 'has-thumb' : ''); $args['title'] && $output->before($args['title'], 'h3'); $args['div_id'] && $output->wrap('div', ['id'=>$args['div_id']]); return $output->render(); } public static function get_instance($type){ static $objects = []; return $objects[$type] ??= new self($type); } } class WPJAM_Extend{ private $dir; private $option; public function __construct($dir, $args){ $this->dir = $dir; $this->option = ($args['option'] ?? '') ? wpjam_register_option($args['option'], $args+[ 'ajax' => false, 'site_default' => is_multisite() && ($args['sitewide'] ?? false), 'fields' => [$this, 'get_fields'], 'sanitize_callback' => [$this, 'sanitize'] ]) : null; } public function __call($method, $args){ $dir = $this->dir; $option = $this->option; $active = get_option('active_plugins') ?: []; $data = $option && $method == 'load' ? array_merge(array_filter($option->get_option()), $option->site_default ? array_filter($option->get_site_option()) : []) : ($method == 'sanitize' ? $args[0] : array_filter(scandir($dir), fn($v)=> !in_array($v, ['.', '..', 'extends.php']))); foreach($data as $k => $v){ $n = is_numeric($k) ? $v : $k; $f = str_ends_with($n, '.php') ? $n : ''; $n = $f ? substr($n, 0, -4) : $n; $e = $f ?: $n.(is_dir($dir.'/'.$n) ? '/'.$n : '').'.php'; $f = $n ? $dir.'/'.$e : ''; $d = is_file($f) ? wpjam_get_file_data($f) : ''; if($method == 'load'){ if($d && (is_admin() || empty($d['Admin'])) && !in_array($e, $active)){ include_once $f; } }elseif($method == 'get_fields'){ $v = $d && $d['Name'] && (!$option->site_default || is_network_admin() || !$option->get_site_setting($n)) ? [ 'value' => [$option, 'get_'.(is_network_admin() ? 'site_': '').'setting']($n), 'title' => wpjam_wrap($d['Name'], $d['URI'] ? 'a' : '', ['href'=>$d['URI'], 'target'=>"_blank"]), 'label' => $d['Description'] ] : ''; } if($d && $v){ $result[$n] = $v; } } return $result ?? []; } } class WPJAM_Param{ private $data = []; public function __invoke($type, ...$args){ $name = $args && is_scalar($args[0]) ? array_shift($args) : null; $args = $args[0] ?? []; $value = $this->get_by($type, $name); if(isset($name)){ $value ??= $args['default'] ?? wpjam_default($name); if($name && ($field = wpjam_except($args, ['default', 'send']))){ $field = wpjam_field(['key'=>$name]+(($field['type'] ?? '') ? [] : ['_schema'=>[]])+$field); $value = wpjam_catch([$field, 'validate'], $value, 'parameter'); ($args['send'] ?? true) && wpjam_if_error($value, 'send'); } }else{ $fields = $args && is_array($args) ? wpjam_fields($args) : $args; $value = $fields ? array_merge($value, $fields->validate($value, 'parameter')) : $value; } return $value; } private function get_by($type, $name=''){ $type = strtolower($type ?: 'get'); if($type == 'data'){ if($name && isset($_GET[$name])){ return wp_unslash($_GET[$name]); } }elseif($type != 'defaults'){ $data = ['post'=>$_POST, 'request'=>$_REQUEST][$type] ?? $_GET; if($name){ if(isset($data[$name])){ return wp_unslash($data[$name]); } if($_POST || $type == 'get'){ return null; } }else{ if($data || $type != 'post'){ return wp_unslash($data); } } $type = 'input'; } $data = $this->data[$type] ??= $this->get_data($type); return $name ? wpjam_get($data, $name) : $data; } private function get_data($type){ if($type == 'input'){ $v = file_get_contents('php://input'); $v = $v && is_string($v) ? @wpjam_json_decode($v) : $v; $data = is_array($v) ? $v : []; }else{ foreach(array_unique(['defaults', $type]) as $t){ $v = $this->get_by('request', $t) ?? []; $v = $v && is_string($v) && str_starts_with($v, '{') ? wpjam_json_decode($v) : wp_parse_args($v ?: []); $data = wpjam_merge($data ?? [], $v); } } return $data; } public static function get_instance(){ static $object; return $object ??= new self(); } } class WPJAM_Http{ public static function request($url, $args=[], $err=[]){ $throw = wpjam_pull($args, 'throw'); $field = wpjam_pull($args, 'field'); try{ $result = wpjam_try('wp_safe_remote_request', $url, self::prepare($args)); $result = self::parse($result, $args, $err); return $field ? wpjam_get($result, $field) : $result; }catch(Exception $e){ $error = wpjam_fill(['code', 'message', 'data'], fn($k)=> [$e, 'get_error_'.$k]()); if(apply_filters('wpjam_http_response_error_debug', true, $error['code'], $error['message'])){ trigger_error(var_export(array_filter(['url'=>$url, 'error'=>array_filter($error), 'body'=>$args['body'] ?? '']), true)); } if($throw){ throw $e; } return wpjam_catch($e); } } private static function prepare($args){ $args += ['body'=>[], 'sslverify'=>false]; $method = strtoupper($args['method'] ?? '') ?: ($args['body'] ? 'POST' : 'GET'); if($method == 'FILE'){ wpjam_once('pre_http_request', fn($pre, $args, $url)=> (new WP_Http_Curl())->request($url, $args), 1, 3); $method = $args['body'] ? 'POST' : 'GET'; }elseif($method != 'GET'){ $key = 'content-type'; $type = 'application/json'; $args['headers'] = array_change_key_case($args['headers'] ?? []); if(array_first(wpjam_pull($args, ['json_encode', 'need_json_encode']))){ $args['headers'][$key] = $type; } if(str_contains($args['headers'][$key] ?? '', $type) && is_array($args['body'])){ $args['body'] = wpjam_json_encode($args['body'] ?: new stdClass); } } return ['method'=>$method]+$args; } private static function parse($result, $args, $err){ $code = $result['response']['code']; $body = &$result['body']; if(!wpjam_compare($code, 'between', [200, 299])){ wpjam_throw($code, $code.' - '.$result['response']['message'].'-'.var_export($body, true)); } if($body && empty($args['stream'])){ if(str_contains(wp_remote_retrieve_header($result, 'content-disposition'), 'attachment;')){ $body = wpjam_bits($body); }elseif(wpjam_pull($args, 'json_decode') !== false && str_starts_with($body, '{') && str_ends_with($body, '}')){ $body = wpjam_then(wpjam_json_decode($body), fn($res)=> self::error($res, $err+['errmsg'=>'errmsg']), fn()=> $body); } } return $result; } private static function error($res, $err){ if(($code = wpjam_pull($res, $err['errcode'] ?? 'errcode')) && $code != ($err['success'] ?? '0')){ wpjam_throw($code, wpjam_pull($res, $err['errmsg']), wpjam_pull($res, $err['detail'] ?? 'detail') ?? array_filter($res)); } if(strtolower($res[$err['errmsg']] ?? '') == 'ok'){ unset($res[$err['errmsg']]); } return $res; } } class WPJAM_Error{ public static function parse($code='', $msg=''){ if(is_wp_error($code)){ $err = $code; $code = $err->get_error_code(); $msg = $err->get_error_message(); $data = ['errcode'=>$code, 'errmsg'=>$msg]+array_filter(is_array($data = $err->get_error_data()) ? $data : ['errdata'=>$data]); }elseif(is_array($code)){ $data = $code; $code = $data['errcode'] ??= 0; $msg = $data['errmsg'] ?? []; } if(isset($data)){ return self::can($code, $msg) ? array_merge($data, self::parse($code, $msg)) : $data; } $args = $msg ?: []; $err = $code; if($item = self::get($code)){ $msg = maybe_closure($item['errmsg'], $args); }elseif(try_prefix($err, '-', 'invalid_')){ $err = $err == 'id' ? 'ID' : str_replace(['_id', '_'], [' ID', ' '], $err); $msg = 'Invalid '.$err.($args ? ': %s.' : '.'); if(did_action('init')){ if(($trans = __($msg, 'wpjam')) != $msg){ $msg = $trans; }else{ $msg = __('Invalid %s'.($args ? ': %s.': '.'), 'wpjam'); $args = [__($err, 'wpjam'), ...$args]; } } }elseif(try_suffix($err, '-', '_required')){ $msg = __('%s is empty or invalid.', 'wpjam'); $trans = __($args && $err == 'value' ? '%s\'s value' : $err, 'wpjam'); $args = [$args ? sprintf($trans.($err == 'value' ? '' : '%s'), ...$args) : $trans]; }elseif(try_suffix($err, '-', '_occupied')){ $msg = __('%s is already in use by another account.', 'wpjam'); $args = [__($err, 'wpjam')]; }else{ $msg = str_replace('_', ' ', $err); } if(!$msg){ return []; } return ['errcode'=>$code, 'errmsg'=>(($args && str_contains($msg, '%')) ? sprintf($msg, ...$args) : $msg)]+$item; } public static function can($code, $msg=''){ return $code && (!$msg || is_array($msg)); } public static function callback($code, $msg, $data, $error){ if(self::can($code, $msg) && count($error->get_error_messages($code)) <= 1 && ($item = self::parse($code, $msg))){ $error->remove($code); $error->add($code, $item['errmsg'], empty($item['modal']) ? $data : array_merge((is_array($data) ? $data : []), ['modal'=>$item['modal']])); } } public static function __callStatic($method, $args){ static $object; $object ??= wpjam_hook('wp_error_added', ['callback'=>[self::class, 'callback']], 10, 4); $key = 'errors['.(array_shift($args) ?: '').']'; return $method == 'get' ? ($object->get_arg($key) ?: []) : $object->update_arg($key, ['errmsg'=>$args[0], 'modal'=>$args[1] ?? []]); } } class WPJAM_Hook extends WPJAM_Args{ public function __invoke(...$args){ if($this->check && !($this->check)(...$args)){ // del 2026-08-30 trigger_error(var_export($this->check, true)); return $args[0]; } $value = $args[0] ?? null; $hook = $this->hook = current_filter(); $args = ($this->op || $this->maybe || $this->tap) ? wpjam_rotate($args, 1) : $args; $result = $this->bind ? $this->call('callback', ...$args) : wpjam_call($this->callback, ...$args); $result = $this->op ? wpjam_operate($value, $this->op, $result) : $result; if($this->once && !($this->maybe && (did_action($hook) ? $result === false : is_null($result)))){ if($filter = $GLOBALS['wp_filter'][$hook] ?? null){ unset($filter->callbacks[$filter->current_priority()][wpjam_callback('uniqid', $this)]); } } return $this->echo ? wpjam_echo($result) : ($this->tap ? $value : ($this->maybe ? ($result ?? $value) : $result)); } public static function batch($action, $name, ...$args){ if(is_array($name) || str_contains($name ?: '', ',')){ return wpjam_call( $action === 'remove' ? 'wpjam_filter' : 'wpjam_map', array_filter(is_array($name) ? $name : wp_parse_list($name)), fn($n)=> self::batch($action, ...(is_array($n) ? $n : [$n, ...$args])) ); } if($name && $args){ $cb = array_shift($args); $multi = wp_is_numeric_array($cb) && !is_callable($cb); $result = wpjam_map($multi ? $cb : [$cb], fn($cb)=> self::$action($name, $cb, ...$args)); return $multi ? $result : $result[0]; } } public static function add($name, $cb, ...$args){ if(wpjam_is_assoc_array($cb)){ $cb = new static($cb); }else{ if($attr = $name === 'once' ? [$name=>true] : []){ $name = $cb; $cb = array_shift($args); } if($type = in_array($cb, ['+', '-', '.'], true) ? 'op' : (in_array($cb, ['tap', 'maybe', 'echo', 'bind'], true) ? $cb : '')){ $attr += [$type=>$cb]; $cb = array_shift($args); } $cb = is_callable($cb) ? $cb : fn()=> $cb; $cb = $attr ? new static($attr+['callback'=>$cb]) : $cb; } add_filter($name, $cb, ...$args); return $cb; } public static function remove(...$args){ return count($args) > 1 && ($args[2] ??= has_filter(...$args)) !== false && remove_filter(...$args); } } class WPJAM_AJAX extends WPJAM_Args{ public function callback(){ add_filter('wp_die_ajax_handler', fn()=> 'wpjam_die_handler'); $data = wpjam_params('post'); if(!$this->admin){ $data = array_merge(wpjam_params('data'), wpjam_except($data, ['action', 'defaults', 'data', '_ajax_nonce'])); } if($fields = $this->fields){ $data = array_merge($data, wpjam_fields($fields)->validate($data, 'parameter')); } if(($this->verify ?? true) && ($action = $this->nonce_action($data)) && !check_ajax_referer($action, false, false)){ wp_die('验证失败,请刷新重试。', 'invalid_nonce'); } if(($allow = $this->allow) && !wpjam_call($allow, $data)){ wp_die('access_denied'); } return wpjam_send_json(wpjam_catch($this->callback, $data, $this->name)); } public function nonce_action($args){ $cb = $this->nonce_action; return $cb ? $cb($args) : ($this->admin ? '' : $this->name.wpjam_join(':', wpjam_pick($args, $this->nonce_keys ?: []))); } public function registered(){ if(wp_doing_ajax() && wpjam_param('action', 'request') == $this->name){ $part = is_user_logged_in() ? '' : ($this->nopriv ? 'nopriv_' : false); $part !== false && add_action('wp_ajax_'.$part.$this->name, [$this, 'callback']); } } public static function register($name, $args=[]){ return wpjam_register(static::class, $name, $args); } public static function get($name){ return wpjam_get_registered(static::class, $name); } public static function add_hooks(){ wpjam_script('wpjam-ajax', [ 'for' => 'wp,login', 'src' => wpjam_url(dirname(__DIR__).'/static/ajax.js'), 'deps' => ['jquery'], 'data' => 'var ajaxurl = "'.admin_url('admin-ajax.php').'";', 'position' => 'before', 'priority' => 1 ]); } } class WPJAM_File extends WPJAM_Args{ public function __invoke($value, $to, $from=null){ $from ??= is_numeric($value) ? 'id' : 'file'; return $from == $to ? $value : ($from == 'id' ? $this->from_id($value, $to) : $this->convert($value, $to, $from)); } protected function convert($value, $to, $from){ if(in_array($to, ['size', 'ext'])){ $file = $from == 'file' ? $value : $this->convert($value, 'file', $from); if($to == 'ext'){ return pathinfo($file, PATHINFO_EXTENSION); } return file_exists($file) && ($size = wp_getimagesize($file)) ? ['width'=>$size[0], 'height'=>$size[1]] : $this->from_id($this->convert($file, 'id', 'file'), 'size'); } if($path = $this->to_path($value, $from)){ return $to == 'id' ? wpjam_get_by_meta('post', '_wp_attached_file', ltrim($path, '/'), 'object_id') : (['url'=>$this->baseurl,'file'=>$this->basedir][$to] ?? '').$path; } } protected function to_path($value, $from='file'){ if($from == 'file'){ $base = $this->basedir; }elseif($from == 'url'){ $value = parse_url($value, PHP_URL_PATH); $base = parse_url($this->baseurl, PHP_URL_PATH); }else{ return $value; } return try_prefix($value, '-', $base) ? $value : null; } protected function from_id($id, $to='file'){ if($to == 'url'){ return wp_get_attachment_url($id); } if($id && get_post_type($id) == 'attachment'){ if(in_array($to, ['meta', 'size'])){ $meta = wp_get_attachment_metadata($id); $meta = is_array($meta) ? $meta : []; return $to == 'meta' ? $meta : wpjam_pick($meta, ['width', 'height']); } $file = get_attached_file($id, true); return $to == 'file' ? $file : $this->convert($file, $to, 'file'); } } public function restore($id, $url=''){ if(is_dir(dirname($file))){ mkdir(dirname($file), 0777, true); } wpjam_remote_request($url ?: $this->from_id($id, 'url'), ['throw'=>true, 'stream'=>true, 'filename'=>$file]); return $file; } public function attach($file, $args=[]){ require_once ABSPATH.'wp-admin/includes/image.php'; $meta = wp_read_image_metadata($file); $title = $meta ? trim($meta['title']) : ''; $title = ($title && !is_numeric(sanitize_title($title))) ? $title : preg_replace('/\.[^.]+$/', '', wp_basename($file)); $pid = $args['post_id'] ?? 0; $id = wpjam_try('wp_insert_attachment', [ 'post_title' => $title, 'post_content' => $meta ? (trim($meta['caption']) ?: '') : '', 'post_parent' => $pid, 'post_mime_type' => $args['type'] ?? mime_content_type($file), 'guid' => $args['url'] ?? $this->convert($file, 'url', 'file'), ], $file, $pid, true); wp_update_attachment_metadata($id, wp_generate_attachment_metadata($id, $file)); return $id; } public function mimes($accept){ $allowed = get_allowed_mime_types(); $types = []; foreach(wpjam_lines($accept ?: '', ',', fn($v)=> strtolower($v)) as $v){ if(str_ends_with($v, '/*')){ $prefix = substr($v, 0, -1); $types += array_filter($allowed, fn($m)=> str_starts_with($m, $prefix)); }elseif(str_contains($v, '/')){ $ext = array_search($v, $allowed); $types += $ext ? [$ext => $v] : []; }elseif(($v = ltrim($v, '.')) && preg_match('/^[a-z0-9]+$/', $v)){ $ext = array_find_key($allowed, fn($m, $ext)=> str_contains($ext, '|') ? in_array($v, explode('|', $ext)) : $v == $ext); $types += $ext ? wpjam_pick($allowed, [$ext]) : []; } } return $types; } public function upload($name, $args=[]){ require_once ABSPATH.'wp-admin/includes/file.php'; if(!empty($args['accept'])){ $args['mimes'] = $this->mimes($args['accept']) ?: wpjam_throw('upload_error', '无效的文件类型'); } if($bits = wpjam_pull($args, 'bits')){ $mime = preg_match('/data:([^;]+);base64,/i', $bits, $m) ? $m[1] : wpjam_throw('upload_error', '无效的 data URI 格式'); $ext = (empty($args['mimes']) || in_array($mime, $args['mimes'])) ? array_search($mime, get_allowed_mime_types()) : ''; $bits = $ext ? base64_decode(trim(substr($bits, strlen($m[0])))) : wpjam_throw('upload_error', '不允许的文件类型'); $upload = wp_upload_bits(explode_last('.', $name)[0].'.'.(explode('|', $ext)[0]), null, $bits); }else{ if(is_array($name)){ $action = 'sideload'; }else{ $action = 'upload'; $name = $_FILES[$name] ?? wpjam_throw('invalid_parameter', [$name]); } if(wpjam_pull($args, 'media')){ require_once ABSPATH.'wp-admin/includes/media.php'; require_once ABSPATH.'wp-admin/includes/image.php'; return wpjam_try('media_handle_'.$action, $name, ($args['post_id'] ?? 0)); } $upload = wpjam_call('wp_handle_'.$action, $name, $args+['test_form'=>false]); } return empty($upload['error']) ? $upload+['path'=>$this->to_path($upload['file'])] : wpjam_throw('upload_error', $upload['error']); } public function download($url, $args=[]){ $media = $args['media'] ?? false; $field = ($args['field'] ?? '') ?: ($media ? 'id' : 'file'); $result = wpjam_get_by_meta('post', 'source_url', $url, 'object_id'); if(!$result || get_post_type($result) != 'attachment'){ try{ $tmp = wpjam_try('download_url', $url); $upload = ['name'=>($args['name'] ?? '') ?: md5($url).'.'.wpjam_at(wp_get_image_mime($tmp), '/', 1), 'tmp_name'=>$tmp]; $result = $this->upload($upload, wpjam_pick($args, ['media', 'post_id'])); if(!$media){ return $result[$field]; } update_post_meta($result, 'source_url', $url); }catch(Exception $e){ $tmp && @unlink($tmp); return wpjam_catch($e); } } return $this->from_id($result, $field); } public function parse($file, $type='data'){ $data = $file ? array_reduce(['URI', 'Name'], fn($c, $k)=> wpjam_set($c, $k, ($c[$k] ?? '') ?: ($c['Plugin'.$k] ?? '')), get_file_data($file, [ 'Admin' => 'Admin', 'Name' => 'Name', 'URI' => 'URI', 'PluginName' => 'Plugin Name', 'PluginURI' => 'Plugin URI', 'Version' => 'Version', 'Description' => 'Description' ])) : []; return $type == 'summary' ? ($data ? str_replace('。', ',', $data['Description']).'详细介绍请点击:'.$data['Name'].'。' : '') : $data; } public static function get_instance(){ static $object; return $object ??= new self(wp_get_upload_dir()); } } class WPJAM_Qrcode extends WPJAM_Args{ public function cache($action, ...$args){ return [$this->handler, 'cache_'.$action](...$args); } public function verify($scene, $code){ $qrcode = $this->cache('get', $scene.'_scene'); if(!$qrcode || !$code || empty($qrcode['openid']) || $code != $qrcode['code']){ return new WP_Error('invalid_'.($qrcode ? 'code' : 'qrcode')); } $this->cache('delete', $scene.'_scene'); return $qrcode; } public function scan($scene, $openid){ $qrcode = $this->cache('get', $scene.'_scene'); if(!$qrcode || (!empty($qrcode['openid']) && $qrcode['openid'] != $openid)){ return new WP_Error('invalid_qrcode'); } $this->cache('delete', $qrcode['key'].'_qrcode'); if(!empty($qrcode['id']) && ($cb = $qrcode['bind_callback'] ?? '') && is_callable($cb)){ return $cb($openid, $qrcode['id']); } $this->cache('set', $scene.'_scene', ['openid'=>$openid]+$qrcode, 1200); return $qrcode['code']; } public function create($key, $args=[]){ return $this->cache('get', $key.'_qrcode') ?: wpjam_tap(wpjam_call($this->creator, $key, $args), fn($v)=> wpjam_map([ $key.'_qrcode', $v['scene'].'_scene' ], fn($k)=> $this->cache('set', $k, $v, 1200))); } public function get_fields($action='login'){ if($action == 'bind'){ $args = ['id'=>get_current_user_id()]; $args = [md5('bind_'.$args['id']), $args]; }else{ $args = [wp_generate_password(32, false, false)]; } return wpjam_then($this->create(...$args), fn($v)=> [ 'qrcode' => ['type'=>'view', 'title'=>$this->title, 'value'=>''], 'code' => ['type'=>'number', 'title'=>'验证码', 'class'=>'input', 'required', 'size'=>20], 'scene' => ['type'=>'hidden', 'value'=>$v['scene']] ]); } } class WPJAM_Widget extends WP_Widget{ public function __construct($id, $name, $options){ parent::__construct($id, $name, $options+['show_instance_in_rest'=>false]); } public function widget($args, $instance){ $widget = $this->widget_options['widget']; if($output = $widget($instance)){ $title = wpjam_pull($instance, 'title'); $output = ($title ? $args['before_title'].$title.$args['after_title'] : '').$output; echo $args['before_widget'].$output.$args['after_widget']; } } public function form($instance){ echo str_replace('fieldset', 'span', wpjam_fields(wpjam_map(maybe_callback($this->widget_options['fields']), fn($v, $k)=> $v+[ 'id' => $this->get_field_id($k), 'name' => $this->get_field_name($k), 'value' => $instance[$k] ?? null ]))->render(['wrap_tag'=>'p'])); } } class WPJAM_Exception extends Exception{ private $error; public function __construct($msg, $code=null, ?Throwable $previous=null){ $error = $this->error = is_wp_error($msg) ? $msg : new WP_Error($code ?: 'error', $msg); $code = $error->get_error_code(); parent::__construct($error->get_error_message(), (is_numeric($code) ? (int)$code : 1), $previous); } public function __call($method, $args){ return in_array($method, ['get_wp_error', 'get_error']) ? $this->error : [$this->error, $method](...$args); } }