HEX
Server: nginx/1.16.1
System: Linux VM-0-14-centos 4.18.0-348.7.1.el8_5.x86_64 #1 SMP Wed Dec 22 13:25:12 UTC 2021 x86_64
User: www (1000)
PHP: 8.3.31
Disabled: passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv
Upload Files
File: /www/wwwroot/www.sceytyg.com/wp-content/plugins/wpjam-basic/includes/class-wpjam-api.php
<?php
trait WPJAM_Call_Trait{
	public function call($name, ...$args){
		if(is_closure($name)){
			$cb	= $name;
		}elseif(try_remove_suffix($name, '_by_model')){
			$cb	= [$this->model, $name];
		}elseif(try_remove_suffix($name, '_by_prop')){
			$cb	= $this->$name;
		}else{
			$cb = [$this, $name];
		}

		return wpjam_call(wpjam_bind($cb, $this), ...$args);
	}

	public function try($name, ...$args){
		return wpjam_try([$this, 'call'], $name, ...$args);
	}

	public function catch($name, ...$args){
		return wpjam_catch([$this, 'call'], $name, ...$args);
	}

	protected function call_dynamic_method($name, ...$args){
		return $this->call(wpjam_dynamic_method(static::class, $name), ...$args);
	}

	public static function add_dynamic_method($name, $closure){
		wpjam_dynamic_method(static::class, $name, $closure);
	}

	public static function get_called(){
		return strtolower(static::class);
	}
}

trait WPJAM_Items_Trait{
	use WPJAM_Call_Trait;

	public function get_items($field=''){
		if($field == ':field'){
			return wpjam_get_annotation(static::class, 'items_field') ?: '_items';
		}

		return $this->{$field ?: $this->get_items(':field')} ?: [];
	}

	public function update_items($items, $field=''){
		$this->{$field ?: $this->get_items(':field')}	= $items;

		return $this;
	}

	public function process_items($cb, $field=''){
		try{
			return $this->update_items($this->call($cb, $this->get_items($field)), $field);
		}catch(Exception $e){
			return wpjam_catch($e);
		}
	}

	public function get_item($key, $field=''){
		return wpjam_get($this->get_items($field), $key);
	}

	public function get_item_arg($key, $arg, $field=''){	// deprecated
		return $this->get_item($key.'.'.$arg, $field);
	}

	public function add_item($key, ...$args){
		[$key, $item]	= ($args && !is_bool($key) && (is_scalar($key) || is_null($key))) ? [$key, array_shift($args)] : [null, $key];

		return $this->process_items(fn($items)=> wpjam_add_at($items, count($items), $key, $this->prepare_item($item, $key, 'add', ...$args)), ...$args);
	}

	public function add_items($items){
		return wpjam_map($items, [$this, 'add_item'], wp_is_numeric_array($items) ? 'v' : 'kv');
	}

	public function remove_item($item, $field=''){
		return $this->process_items(fn($items)=> array_diff($items, [$item]), $field);
	}

	public function edit_item($key, $item, $field=''){
		return $this->update_item($key, $item, $field);
	}

	public function update_item($key, $item, $field='', $action='update'){
		return $this->process_items(fn($items)=> array_replace($items, [$key=> $this->prepare_item($item, $key, $action, $field)]), $field);
	}

	public function set_item($key, $item, $field=''){
		return $this->update_item($key, $item, $field, 'set');
	}

	public function delete_item($key, $field=''){
		$res	= $this->process_items(fn($items)=> wpjam_except($items, $this->prepare_item(null, $key, 'delete', $field) ?? $key), $field);

		is_wp_error($res) || wpjam_call([$this, 'after_delete_item'], $key, $field);

		return $res;
	}

	public function del_item($key, $field=''){
		return $this->delete_item($key, $field);
	}

	public function move_item($orders, $field=''){
		if(wpjam_is_assoc_array($orders)){
			[$orders, $field]	= array_values(wpjam_pull($orders, ['item', '_field']));
		}

		return $this->process_items(fn($items)=> array_merge(wpjam_pull($items, $orders), $items), $field);
	}

	protected function prepare_item($item, $key, $action, $field=''){
		$field	= $field ?: $this->get_items(':field');
		$items	= $this->get_items($field);
		$add	= $action == 'add';

		if(isset($item)){
			method_exists($this, 'validate_item') && wpjam_if_error($this->validate_item($item, $key, $action, $field), 'throw');

			$add	&& ($max = wpjam_get_annotation(static::class, 'max_items')) && count($items) >= $max && wpjam_throw('quota_exceeded', '最多'.$max.'个');
			$item	= method_exists($this, 'sanitize_item') ? $this->sanitize_item($item, $key, $action, $field) : $item;
		}

		if(isset($key)){
			$label	= ['add'=>'添加', 'update'=>'编辑', 'delete'=>'删除'][$action] ?? '';
			$label	&& (wpjam_exists($items, $key) === $add) && wpjam_throw('invalid_item_key', '「'.$key.'」'.($add ? '已' : '不').'存在,无法'.$label);
		}else{
			$add || wpjam_throw('invalid_item_key', 'key不能为空');
		}

		return $item;
	}

	public static function get_item_actions(){
		$args	= [
			'row_action'	=> false,
			'data_callback'	=> fn($id)=> wpjam_try([static::class, 'get_item'], $id, ...array_values(wpjam_get_data_parameter(['i', '_field']))),
			'value_callback'=> fn()=> '',
			'callback'		=> function($id, $data, $action){
				$args	= array_values(wpjam_get_data_parameter(['i', '_field']));
				$args	= $action == 'del_item' ? $args : wpjam_add_at($args, 1, null, $data);

				return wpjam_try([static::class, $action], $id, ...$args);
			}
		];

		return [
			'add_item'	=>['page_title'=>'新增项目',	'title'=>'新增',	'dismiss'=>true]+array_merge($args, ['data_callback'=> fn()=> []]),
			'edit_item'	=>['page_title'=>'修改项目',	'dashicon'=>'edit']+$args,
			'del_item'	=>['page_title'=>'删除项目',	'dashicon'=>'no-alt',	'class'=>'del-icon',	'direct'=>true,	'confirm'=>true]+$args,
			'move_item'	=>['page_title'=>'移动项目',	'dashicon'=>'move',		'class'=>'move-item',	'direct'=>true]+wpjam_except($args, 'callback'),
		];
	}
}

class WPJAM_API{
	private $data	= [];

	private function __construct(){
		add_action('plugins_loaded', [$this, 'loaded'], 0);

		wpjam_hooks('gettext, gettext_with_context', fn($get, ...$args)=> $get === $args[0] ? wpjam_translate(...$args) : $get, 10, 4);

		get_locale() == 'zh_CN' && load_textdomain('wpjam', dirname(__DIR__).'/template/wpjam-zh_CN.l10n.php');

		wpjam_is_json_request() && wpjam_hooks('remove', [
			['the_title',		'convert_chars'],
			['init',			['wp_widgets_init', 'maybe_add_existing_user_to_blog']],
			['plugins_loaded',	['wp_maybe_load_widgets', 'wp_maybe_load_embeds', '_wp_customize_include', '_wp_theme_json_webfonts_handler']],
			['wp_loaded',		['_custom_header_background_just_in_time', '_add_template_loader_filters']]
		]);
	}

	public function loaded(){
		wpjam_activation();

		is_admin() && wpjam_admin();

		wpjam_register_bind('phone', '', ['domain'=>'@phone.sms']);

		wpjam_load_extends(get_template_directory().'/extends');

		wpjam_load_extends(dirname(__DIR__).'/extends', [
			'option'	=> 'wpjam-extends',
			'sitewide'	=> true,
			'title'		=> '扩展管理',
			'menu_page'	=> ['parent'=>'wpjam-basic', 'order'=>3, 'function'=>'tab', 'tabs'=>['extends'=>['order'=>20, 'title'=>'扩展管理', 'function'=>'option']]]
		]);

		wpjam_style('remixicon', [
			'src'		=> wpjam_get_static_cdn().'/remixicon/4.2.0/remixicon.min.css',
			'method'	=> is_admin() ? 'enqueue' : 'register',
			'priority'	=> 1
		]);

		wpjam_style('wpjam-static', ['src'=>'', 'method'=>'register', 'priority'=>1]);

		add_action('loop_start',	fn($query)=> $this->push('query', $query), 1);
		add_action('loop_end',		fn()=> $this->pop('query'), 999);

		wpjam_hook('tap', 'pre_do_shortcode_tag',	fn($pre, $tag)=> $this->push('shortcode', $tag), 1, 2);
		wpjam_hook('tap', 'do_shortcode_tag',		fn($res, $tag)=> $this->pop('shortcode'), 999, 2);

		wpjam_hooks('register_post_type_args, register_taxonomy_args', [
			'check'		=> fn($args)=> empty($args['_jam']) && (did_action('init') || empty($args['_builtin'])),
			'callback'	=> fn($args, $name, $object_type=null)=> ('wpjam_'.wpjam_remove_suffix(current_filter(), '_args'))($name, ['_jam'=>false]+array_filter(compact('object_type'))+$args)->to_array()
		], 999, 3);

		add_filter('root_rewrite_rules', fn($rewrite)=> $GLOBALS['wp_rewrite']->root ? $rewrite : array_merge(['([^/]+\.txt)?$'=>'index.php?module=txt&action=$matches[1]'], $rewrite));
	}

	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 push($field, $item){
		$this->data[$field]	??= [];

		return array_push($this->data[$field], $item);
	}

	public function pop($field){
		return $this->get($field) ? array_pop($this->data[$field]) : null;
	}

	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_Args implements ArrayAccess, IteratorAggregate, JsonSerializable{
	use WPJAM_Call_Trait;

	protected $args;

	public function __construct($args=[]){
		$this->args	= $args;
	}

	public function __get($key){
		$args	= $this->get_args();

		return wpjam_exists($args, $key) ? $args[$key] : ($key == 'args' ? $args : null);
	}

	public function __set($key, $value){
		$this->filter_args();

		$this->args[$key]	= $value;
	}

	public function __isset($key){
		return wpjam_exists($this->get_args(), $key) ?: ($this->$key !== null);
	}

	public function __unset($key){
		$this->filter_args();

		unset($this->args[$key]);
	}

	#[ReturnTypeWillChange]
	public function offsetGet($key){
		return $this->get_args()[$key] ?? null;
	}

	#[ReturnTypeWillChange]
	public function offsetSet($key, $value){
		$this->filter_args();

		if(is_null($key)){
			$this->args[]		= $value;
		}else{
			$this->args[$key]	= $value;
		}
	}

	#[ReturnTypeWillChange]
	public function offsetExists($key){
		return wpjam_exists($this->get_args(), $key);
	}

	#[ReturnTypeWillChange]
	public function offsetUnset($key){
		$this->filter_args();

		unset($this->args[$key]);
	}

	#[ReturnTypeWillChange]
	public function getIterator(){
		return new ArrayIterator($this->get_args());
	}

	#[ReturnTypeWillChange]
	public function jsonSerialize(){
		return $this->get_args();
	}

	protected function filter_args(){
		return $this->args	= $this->args ?: [];
	}

	public function get_args(){
		return $this->filter_args();
	}

	public function update_args($args, $replace=true){
		$this->args	= ($replace ? 'array_replace' : 'wp_parse_args')($this->get_args(), $args);

		return $this;
	}

	public function process_arg($key, $cb){
		if(!is_closure($cb)){
			return $this;
		}

		$value	= $this->call($cb, $this->get_arg($key));

		return is_null($value) ? $this->delete_arg($key) : $this->update_arg($key, $value);
	}

	public function get_arg($key, $default=null, $callback=false){
		$value	= wpjam_get($this->get_args(), $key);

		if($callback){
			$value	= is_closure($value) ? wpjam_bind($value, $this) : ($value ?? (is_string($key) ? wpjam_callback([$this->model, 'get_'.$key]) : null));
			$value	= $callback === 'callback' ? maybe_callback($value, $this->name) : $value;
		}

		return $value ?? $default;
	}

	public function update_arg($key, $value){
		$this->args	= wpjam_set($this->get_args(), $key, $value);

		return $this;
	}

	public function delete_arg($key, ...$args){
		if($args && is_string($key) && str_ends_with($key, '[]')){
			return $this->process_arg(substr($key, 0, -2), fn($value)=> is_array($value) ? array_diff($value, $args) : $value);
		}

		$this->args	= wpjam_except($this->get_args(), $key);

		return $this;
	}

	public function pull($key, ...$args){
		$this->filter_args();

		return wpjam_pull($this->args, $key, ...$args);
	}

	public function pick($keys){
		return wpjam_pick($this, $keys);
	}

	public function to_array(){
		return $this->get_args();
	}

	public function sandbox($cb, ...$args){
		try{
			$archive	= $this->get_args();

			return is_closure($cb) ? $this->call($cb, ...$args) : null;
		}finally{
			$this->args	= $archive;
		}
	}

	protected function parse_method($name){
		$cb = array_find([[$this->model, $name], $this->$name], fn($v)=> wpjam_callback($v));

		return is_closure($cb) ? wpjam_bind($cb, $this) : $cb;
	}

	public function call_method($name, ...$args){
		return ($cb = $this->parse_method($name)) ? wpjam_try($cb, ...$args) : (str_starts_with($name, 'filter_') ? array_shift($args) : null);
	}

	protected function error($code, $msg){
		return new WP_Error($code, $msg);
	}
}

class WPJAM_Register extends WPJAM_Args{
	use WPJAM_Items_Trait;

	public function __construct($name, $args=[]){
		$this->args	= $args	= ['name'=>$name]+$args;

		if($this->is_active() || !empty($args['active'])){
			$config	= static::registry('get_arg', 'config') ?: [];
			$model	= empty($config['model']) ? '' : ($args['model'] ?? '');
			$file	= ($model || !empty($args['hooks']) || !empty($args['init'])) ? wpjam_pull($args, 'file') : '';

			$file && is_file($file) && include_once $file;

			if($model){
				is_subclass_of($model, self::class) && trigger_error('「'.(is_object($model) ? get_class($model) : $model).'」是 '.self::class.' 子类');

				if($config['model'] === 'object' && !is_object($model)){
					if(class_exists($model, true)){
						$model = $args['model']	= new $model(array_merge($args, ['object'=>$this]));
					}else{
						trigger_error('model 无效');
					}
				}

				foreach(['hooks'=>'add_hooks', 'init'=>'init'] as $k => $m){
					($args[$k] ?? ($k == 'hooks' || ($config[$k] ?? false))) === true && method_exists($model, $m) && ($args[$k] = [$model, $m]);
				}
			}
		}

		$this->args	= $args;
	}

	public function builtin($key='', ...$args){
		$type	= wpjam_remove_prefix(get_class($this), 'WPJAM_');

		if(!$args && $key == 'title'){
			return ($labels	= $this->builtin('labels')) ? $labels->singular_name : $this->builtin('label');
		}

		if($key != 'name' && property_exists('WP_'.$type, $key)){
			$cb	= 'get_'.strtolower($type).($type == 'Post_Type' ? '_object' : '');

			if($object = $cb($this->name)){
				return $args ? ($object->$key = $args[0]) : $object->$key;
			}
		}
	}

	protected function filter_args(){
		$name		= $this->args['name'];
		$registry	= static::registry('');

		if($registry->called && !in_array($name, $registry->get_arg('filtered') ?: [])){
			$this->args	= apply_filters($registry->update_arg('filtered[]', $name)->name.'_args', $this->args, $name);
		}

		return $this->args;
	}

	public function get_arg($key, $default=null, $callback=true){
		return parent::get_arg($key, $default, $callback ? 'callback' : 'parse');
	}

	public function get_parent(){
		return $this->sub_name ? self::get($this->name) : null;
	}

	public function get_sub($name){
		return self::get($this->name.':'.$name);
	}

	public function get_subs(){
		return wpjam_array(self::get_by(['name'=>$this->name]), fn($k, $v)=> $v->sub_name);
	}

	public function register_sub($name, $args){
		return self::register($this->name.':'.$name, new static($this->name, array_merge($args, ['sub_name'=>$name])));
	}

	public function unregister_sub($name){
		return self::unregister($this->name.':'.$name);
	}

	public function is_active(){
		return true;
	}

	public static function registry($method, ...$args){
		$called		= static::class;
		$parts		= str_contains($method, ':') ? explode(':', $method) : [];
		$name		= strtolower($parts ? $parts[0] : $called);
		$method		= $parts ? $parts[1] : $method;
		$registry	= WPJAM_Registry::get_instance($name);

		$parts || $registry->called || $registry->update_args($method ? [
			'called'	=> $called,
			'config'	=> wpjam_get_annotation($called, 'config')+['model'=>true],
			'defaults'	=> method_exists($called, 'get_defaults') ? static::get_defaults() : []
		] : ($args ? $args[0] : []));

		return $method ? wpjam_catch([$registry, $method], ...$args) : $registry;
	}

	public static function register($name, $args=[]){
		return is_null($args) ? null : static::registry('add_object', $name, $args);
	}

	public static function unregister($name, $args=[]){
		static::registry('remove_object', $name, $args);
	}

	public static function get_registereds($args=[], $output='objects', $operator='and'){
		wpjam_map(static::registry('pull', 'defaults'), [static::class, 'register'], 'kv');

		$objects	= static::registry('get_objects', $args, $operator);

		return $output == 'names' ? array_keys($objects) : $objects;
	}

	public static function get_by(...$args){
		return self::get_registereds(...((!$args || is_array($args[0])) ? $args : [[$args[0]=> $args[1]]]));
	}

	public static function get($name, $by='', $top=''){
		if($name && !$by){
			return static::registry('get_object', $name) ?: static::register($name, static::registry('pull', 'defaults['.$name.']'));
		}

		if($name && $by == 'model' && strcasecmp($name, $top) !== 0){
			return array_find(static::get_registereds(), fn($v)=> is_string($v->model) && strcasecmp($name, $v->model) === 0) ?: static::get( get_parent_class($name), $by, $top);
		}
	}

	public static function get_setting_fields($args=[]){
		$objects	= array_filter(static::get_registereds(wpjam_pull($args, 'filter_args')), fn($v)=> !isset($v->active));
		$options	= wpjam_options($objects, $args);

		if(($args['type'] ?? '') == 'select'){
			$name	= wpjam_pull($args, 'name');
			$args	+= ['options'=>$options];

			return $name ? [$name => $args] : $args;
		}

		return $options;
	}

	public static function get_active($key=null){
		return wpjam_array(static::get_registereds(), fn($k, $v)=> ($v->active ?? $v->is_active()) ? [$k, $key ? $v->get_arg($key) : $v] : null, true);
	}

	public static function by_active(...$args){
		return self::call_active((did_action(current_filter()) ? 'on_' : 'filter_').wpjam_remove_suffix(current_filter(), 'wpjam_'), ...$args);
	}

	public static function call_active($method, ...$args){
		$type	= array_find(['filter', 'get'], fn($t)=> str_starts_with($method, $t.'_'));

		foreach(static::get_active() as $object){
			$res	= $object->call_method($method, ...$args);

			if($type == 'filter'){
				$args[0]	= $res;
			}elseif($type == 'get'){
				$return		= array_merge($return ?? [], is_array($res) ? $res : []);
			}
		}

		if($type == 'filter'){
			return $args[0];
		}elseif($type == 'get'){
			return $return ?? [];
		}
	}
}

class WPJAM_Registry extends WPJAM_Args{
	private static $instances	= [];
	private $objects			= [];

	public function add_object(...$args){
		$count	= count($this->objects);
		$called	= $this->called ?: 'WPJAM_Register';

		[$key, $object] 	= is_object($args[0]) ? [$args[0]->name ?? null, $args[0]] : (is_array($args[0]) ? [wpjam_pull($args[0], 'name') ?: ($args[1] ?: '__'.$count) , $args[0]] : $args);

		if($e = $key ? (is_numeric($key) ? '「'.$key.'」为纯数字' : (is_string($key) ? '' : '「'.var_export($key, true).'」不为字符串')) : '为空'){
			return trigger_error($this->name.'的注册 name'.$e) && false;
		}

		if(is_array($object)){
			if(!empty($object['admin']) && !is_admin()){
				return;
			}

			$object	= new $called($key, $object);
		}

		isset($this->objects[$key]) && trigger_error($this->name.'「'.$key.'」已经注册。');

		$this->objects[$key]	= $object;

		if($object->is_active() || $object->active){
			wpjam_hooks(maybe_callback($object->pull('hooks')));

			wpjam_init($object->pull('init'));

			method_exists($object, 'registered') && $object->registered();

			$this->called && $count == 0 && wpjam_hooks(wpjam_call($called.'::add_hooks'));
		}

		return $object;
	}

	public function remove_object($key){
		unset($this->objects[$key]);
	}

	public function get_object($key){
		return $this->objects[$key] ?? null;
	}

	public function get_objects(...$args){
		$objects	= wpjam_filter($this->objects, ...$args);
		$orderby	= $this->get_arg('config[orderby]');

		return $orderby ? wpjam_sort($objects, ($orderby === true ? 'order' : $orderby), ($this->get_arg('config[order]') ?? 'DESC'), 10) : $objects;
	}

	public static function get_instance($name){
		self::$instances || wpjam_hooks('wpjam_api, wpjam_admin_init', function(...$args){
			foreach(self::$instances as $name => $registry){
				if(current_action() == 'wpjam_api'){
					if($registry->get_arg('config[register_json]')){
						[$registry->called, 'call_active']('register_json', $args[0]);
					}
				}else{
					foreach(['menu_page', 'admin_load'] as $key){
						$registry->get_arg('config['.$key.']') && array_map('wpjam_add_'.$key, [$registry->called, 'get_active']($key));
					}
				}
			}
		});

		return self::$instances[$name] ??= new self(compact('name'));
	}
}

/**
* @config menu_page, admin_load, register_json, init, orderby
**/
#[config('menu_page', 'admin_load', 'register_json', 'init', 'orderby')]
class WPJAM_Option_Setting extends WPJAM_Register{
	public function __invoke(){
		flush_rewrite_rules();

		$submit	= wpjam_get_post_parameter('submit_name');
		$values	= $this->validate_by_fields(wpjam_get_data_parameter()) ?: [];
		$fix	= is_network_admin() ? 'site_option' : 'option';

		if($this->option_type == 'array'){
			if($submit == 'reset'){
				$values	= wpjam_diff([$this, 'get_'.$fix](), $values, 'key');
			}else{
				$values	= wpjam_filter(array_merge([$this, 'get_'.$fix](), $values), fn($v)=> !is_null($v), true);
				$values	= $this->call_method('sanitize_callback', $values, $this->name) ?? $values;
			}

			$cb	= $this->update_callback;
			$cb ? $cb($this->name, $values) : [$this, 'update_'.$fix]($values);
		}else{
			wpjam_map($values, ...($submit == 'reset' ? ['delete_'.$fix, 'k'] : ['update_'.$fix, 'kv']));
		}

		$errors	= array_filter(get_settings_errors(), fn($e)=> !in_array($e['type'], ['updated', 'success', 'info']));
		$errors	&& wp_die(implode('&emsp;', array_column($errors, 'message')));

		return [
			'type'		=> (!$this->ajax || $submit == 'reset') ? 'redirect' : $submit,
			'notice'	=> $submit == 'reset' ? '设置已重置。' : '设置已保存。'
		];
	}

	public function __call($method, $args){
		if(try_remove_suffix($method, '_fields')){
			if($method == 'render'){
				$fields	= array_shift($args);
			}elseif($method == 'get' || try_remove_suffix($method, '_by')){
				$fields	= array_merge(...array_column($this->get_sections($method !== 'validate'), 'fields'));

				if($method == 'get'){
					return $fields;
				}
			}

			return isset($fields) ? wpjam_fields($fields, ['value_callback'=>[$this, 'value_callback']])->$method(...$args) : null;
		}elseif(($option = try_remove_suffix($method, '_option')) || try_remove_suffix($method, '_setting')){
			if(try_remove_suffix($method, '_site')){
				$type		= '_site';
			}else{
				$blog_id	= try_remove_suffix($method, '_blog') ? array_shift($args) : $this->blog_id;
				$type		= $blog_id && is_multisite() ? '_blog' : '';
			}

			$cb_args	= $type == '_blog' ? [$blog_id] : [];

			if($option){
				$res	= ($method.$type.'_option')(...[...$cb_args, $this->name, ...($method == 'update' ? [wpjam_if_error($args[0], [])] : [])]);

				return $method == 'get' ? (wpjam_if_error($res, []) ?: []) : $res;
			}

			$data	= [$this, 'get'.$type.'_option'](...$cb_args) ?: [];
			$name	= $args[0] ?? null;

			if($method == 'get'){
				$site_default	= $type != '_site' && is_multisite() && $this->site_default;

				if(!$name){
					return array_merge($site_default ? $this->get_site_option() : [], $data);
				}

				if(is_array($name)){
					return wpjam_fill(array_filter($name), fn($n)=> [$this, 'get'.$type.'_setting'](...[...$cb_args, $n]));
				}

				$value	= is_array($data) ? wpjam_if_error(wpjam_get($data, $name), null) : null;

				if(is_null($value)){
					if($site_default){
						return $this->get_site_setting(...$args);
					}

					if(count($args) >= 2){
						return $args[1];
					}

					if($this->field_default){
						return wpjam_get(($this->_defaults ??= $this->get_defaults_by_fields()), $name);
					}
				}

				return is_string($value) ? str_replace("\r\n", "\n", trim($value)) : $value;
			}

			return [$this, 'update'.$type.'_option'](...[...$cb_args, $method == 'update' ? wpjam_reduce(is_array($name) ? $name : [$name=>$args[1]], fn($c, $v, $n)=> wpjam_set($c, $n, $v), $data) : wpjam_except($data, $name)]);
		}
	}

	public function get_arg($key, $default=null, $callback=true){
		$value	= parent::get_arg($key, $default, $callback);

		if($key == 'menu_page'){
			if(!$this->name || (is_network_admin() && !$this->site_default)){
				return;
			}

			if(!$value){
				if(!$this->post_type || !$this->title){
					return $value;
				}

				$value	= ['parent'=>wpjam_get_post_type_setting($this->post_type, 'plural'), 'order'=>1];
			}

			if(wp_is_numeric_array($value)){
				return wpjam_array($value, function($k, $v){
					if(!empty($v['tab_slug']) && !empty($v['plugin_page'])){
						return [$k, $v];
					}elseif(!empty($v['menu_slug'])){
						return [$k, $v+($v['menu_slug'] == $this->name ? ['menu_title'=>$this->title] : [])];
					}
				});
			}

			$value	+= ($value['function'] ??= 'option') == 'option' ? ['option_name'=>$this->name] : [];

			if(!empty($value['tab_slug'])){
				return ($value['plugin_page'] ??= $this->plugin_page) ? $value+['title'=>$this->title] : null;
			}

			$value	+= ['menu_slug'=>$this->plugin_page ?: $this->name, 'menu_title'=>$this->title];
		}elseif($key == 'admin_load'){
			$value	= wp_is_numeric_array($value) ? $value : ($value ? [$value] : []);
			$value	= array_map(fn($v)=> ($this->model && !isset($v['callback']) && !isset($v['model'])) ? $v+['model'=>$this->model] : $v, $value);
		}elseif($key == 'sections'){
			if(!$value || !is_array($value)){
				$id		= $this->type == 'section' ? (string)$this->section_id : ($this->current_tab ?: $this->sub_name ?: $this->name);
				$value	= [$id=>array_filter(['fields'=>$this->get_arg('fields', null, false)]) ?: $this->get_arg('section') ?: []];
			}

			$value	= wpjam_array($value, fn($k, $v)=> is_array($v) && isset($v['fields']) ? [$k, ['fields'=>maybe_callback($v['fields'] ?? [], $k, $this->name)]+$v] : null);
		}

		return $value;
	}

	public function get_current($output=''){
		$args	= wpjam_pick(wpjam_admin('vars'), ['plugin_page', 'current_tab']);

		return $output === 'args' ? $args : ($this->get_sub(wpjam_join(':', $args)) ?: $this);
	}

	protected function get_sections($all=false, $filter=true){
		$sections	= $this->get_arg('sections');
		$sections	= count($sections) == 1 ? array_map(fn($s)=> $s+['title'=>$this->title ?: ''], $sections) : $sections;
		$sections	= array_reduce($all ? $this->get_subs() : [], fn($c, $v)=> array_merge($c, $v->get_sections(false, false)), $sections);

		if(!$filter){
			return $sections;
		}

		$args		= $all ? [] : wpjam_map(self::get_current('args'), fn($v)=> ['value'=>$v, 'if_null'=>true]);
		$objects	= wpjam_sort(self::get_by(['type'=>'section', 'name'=>$this->name]+$args), 'order', 'desc', 10);

		foreach(array_reverse(array_filter($objects, fn($v)=> $v->order > 10))+$objects as $object){
			foreach(($object->get_arg('sections') ?: []) as $id => $section){
				$id			= $id ?: array_key_first($sections);
				$exist		= isset($sections[$id]) ? ($object->order > 10 ? wpjam_merge($section, $sections[$id]) : $sections[$id]) : [];	// 字段靠前
				$sections	= wpjam_set($sections, $id, wpjam_merge($exist, $section));
			}
		}

		return apply_filters('wpjam_option_setting_sections', array_filter($sections, fn($v)=> isset($v['title'], $v['fields'])), $this->name);
	}

	public function add_section(...$args){
		$keys	= ['model', 'fields', 'section'];
		$args	= is_array($args[0]) ? $args[0] : ['section_id'=>$args[0]]+(array_any($keys, fn($k)=> isset($args[1][$k])) ? $args[1] : ['fields'=>$args[1]]);
		$args	= array_any([...$keys, 'sections'], fn($k)=>isset($args[$k])) ? $args : ['sections'=>$args];

		return self::register(md5(wpjam_serialize($args)), new static($this->name, $args+['type'=>'section']));
	}

	public function value_callback($name=''){
		return $this->option_type == 'array' ? (is_network_admin() ? $this->get_site_setting($name) : $this->get_setting($name)) : get_option($name, null);
	}

	public function render($page){
		$nav	= $page->tab_slug ? '' : wpjam_tag('h2', ['nav-tab-wrapper', 'wp-clearfix']);
		$form	= wpjam_tag('form', ['method'=>'POST', 'id'=>'wpjam_option', 'novalidate']);
		$tabs	= [];

		foreach($this->get_sections() as $id => $sec){
			$nav && $nav->append('a', ['class'=>'nav-tab', 'href'=>'#tab_'.$id, 'data'=>wpjam_pick($sec, ['show_if'])], $sec['title']);

			$tabs[]	= wpjam_tag($nav ? 'div' : '', ['id'=>'tab_'.$id, 'class'=>'tab'])->append([
				[$nav ? 'h2' : 'h3', [], $sec['title']],
				wpjam_ob($sec['callback'] ?? '', $sec),
				wpautop($sec['summary'] ?? ''),
				$this->render_fields($sec['fields'])
			]);
		}

		$form->data('nonce', wp_create_nonce($this->option_group))->append([
			...(count($tabs) == 1 ? [$tabs[0]->tag('')] : $tabs),
			wpjam_tag('p', ['submit'])->append([
				get_submit_button('', 'primary', 'save', false),
				$this->reset ? get_submit_button('重置选项', 'secondary', 'reset', false) : ''
			])
		]);

		return count($tabs) > 1 && $nav ? $form->before($nav)->wrap('div', ['tabs']) : $form;
	}

	public function page_load(){
		wpjam_ajax('wpjam-option-action',	[
			'admin'			=> true,
			'callback'		=> $this,
			'nonce_action'	=> fn()=> $this->option_group,
			'allow'			=> fn()=> current_user_can($this->capability)
		]);
	}

	public static function create($name, $args){
		$args	= maybe_callback($args, $name)+[
			'option_group'	=> $name,
			'option_page'	=> $name,
			'option_type'	=> 'array',
			'capability'	=> 'manage_options',
			'ajax'			=> true
		];

		if($sub = wpjam_pick($args, ['plugin_page', 'current_tab'])){
			$rest	= wpjam_except($args, ['model', 'menu_page', 'admin_load', 'plugin_page', 'current_tab']);
		}else{
			$args	= ['primary'=>true]+$args;
		}

		if($object = self::get($name)){
			if(!$sub && $object->primary && trigger_error('option_setting'.'「'.$name.'」已经注册。'.var_export($args, true))){
				return $object;
			}

			$object->update_args($sub ? wpjam_except($rest, 'title') : $args);
		}else{
			$args['option_type'] == 'array' && !doing_filter('sanitize_option_'.$name) && is_null(get_option($name, null)) && add_option($name, []);

			$object	= self::register($name, $sub ? $rest : $args);
		}

		return $sub ? $object->register_sub(wpjam_join(':', $sub), $args) : $object;
	}

	public static function get_instance($name, ...$args){
		$name	= $name == 'option' ? array_shift($args) : $name;	// 兼容

		if($args && in_array($args[0], ['model', ''], true)){
			return self::get($name, ...$args);
		}

		$blog_id	= is_multisite() ? ($args[0] ?? 0) : 0;
		$object		= $blog_id ? (!is_numeric($blog_id) && trigger_error($name.':'.$blog_id) && false) : self::get($name);

		return $object ?: wpjam_var('option:'.wpjam_join('-', $name, $blog_id), fn()=> new static($name, ['blog_id'=>$blog_id]));
	}
}

class WPJAM_Option_Model{
	protected static function get_object(){
		return wpjam_option(static::class, 'model', self::class);
	}

	public static function __callStatic($method, $args){
		return ($object	= self::get_object()) ? [$object, $method](...$args) : null;
	}
}

class WPJAM_Meta_Type extends WPJAM_Register{
	public function __call($method, $args){
		if(in_array($method, ['get_data', 'add_data', 'update_data', 'delete_data', 'data_exists'])){
			$args	= [$this->name, ...$args];
			$cb		= str_replace('data', 'metadata', $method);
		}elseif(try_remove_suffix($method, '_by_mid')){
			$args	= [$this->name, ...$args];
			$cb		= $method.'_metadata_by_mid';
		}elseif(try_remove_suffix($method, '_meta')){
			$cb		= [$this, $method.'_data'];
		}elseif(str_contains($method, '_meta')){
			$cb		= [$this, str_replace('_meta', '', $method)];
		}

		return $cb(...$args);
	}

	public function get_options($args=[]){
		if($this->name == 'post'){
			$key	= 'post_type';
			$cb 	= 'wpjam_get_post_type_object';
		}elseif($this->name == 'term'){
			$key	= 'taxonomy';
			$cb 	= 'wpjam_get_taxonomy';
		}

		if(isset($key) && isset($args[$key])){
			$args[$key]	= ['value'=>($value = $args[$key]), 'if_null'=>true, 'callable'=>true];
			$object		= $cb($value);

			$object && ($this->call_option($value.'_base') || $this->call_option($value.'_base', [
				$key			=> $value,
				'title'			=> $this->name == 'post' ? '基础信息' : '快速编辑',
				'row_action'	=> $this->name == 'term',
				'action_name'	=> 'set',
				'page_title'	=> '设置'.$object->title,
				'fields'		=> [$object, 'get_fields'],
				'list_table'	=> $object->show_ui,
				'order'			=> 99,
			]));
		}

		if(isset($args['list_table'])){
			$args['title']		= true;
			$args['list_table']	= $args['list_table'] ? true : ['compare'=>'!==', 'value'=>'only'];
		}

		return wpjam_sort(wpjam_filter($this->get_arg('options[]'), $args), 'order', 'DESC', 10);
	}

	public function call_option($name, ...$args){
		$del	= try_remove_prefix($name, '-');
		$key	= 'options['.$name.']';

		if($del){
			return $this->delete_arg($key) && true;
		}

		if($args){
			$args	= $args[0];

			if($this->name == 'post'){
				$args	+= ['fields'=>[], 'priority'=>'default'];

				$args['post_type']	??= wpjam_pull($args, 'post_types') ?: null;
			}elseif($this->name == 'term'){
				$args['taxonomy']	??= wpjam_pull($args, 'taxonomies') ?: null;

				if(!isset($args['fields'])){
					$args['fields']		= [$name => wpjam_except($args, 'taxonomy')];
					$args['from_field']	= true;
				}
			}

			$this->update_arg($key, new WPJAM_Meta_Option(['name'=>$name, 'meta_type'=>$this->name]+$args));
		}

		return $this->get_arg($key);
	}

	public function get_table(){
		return _get_meta_table($this->name);
	}

	public function get_column($name='object_id'){
		if($name == 'object_id'){
			return $this->name.'_id';
		}elseif($name == 'id'){
			return 'user' == $this->name ? 'umeta_id' : 'meta_id';
		}

		return $name;
	}

	public function get_data_with_default($id, ...$args){
		if(!$args){
			return $this->get_data($id);
		}

		if($id && $args[0]){
			if(is_array($args[0])){
				return wpjam_array($args[0], fn($k, $v)=> [is_numeric($k) ? $v : $k, $this->get_data_with_default($id, ...(is_numeric($k) ? [$v, null] : [$k, $v]))]);
			}

			if($args[0] == 'meta_input'){
				trigger_error('meta_input');
				return wpjam_map($this->get_data($id), fn($v, $k)=> $this->get_data($id, $k, true));
			}

			if($this->data_exists($id, $args[0])){
				return $this->get_data($id, $args[0], true);
			}
		}

		return is_array($args[0]) ? [] : ($args[1] ?? null);
	}

	public function get_by_key($key, $value=null, $column=null){
		global $wpdb;

		$where	= array_filter([
			$key ? $wpdb->prepare('meta_key=%s', $key) : '',
			is_null($value) ? '' : $wpdb->prepare('meta_value=%s', maybe_serialize($value))
		]);

		if($where){
			$table	= $this->get_table();
			$where	= implode(' AND ', $where);
			$data	= $wpdb->get_results("SELECT * FROM {$table} WHERE {$where}", ARRAY_A) ?: [];

			return $data && $column ? array_first($data)[$this->get_column($column)] : array_map(fn($v)=> ['meta_value'=>maybe_unserialize($v['meta_value'])]+$v, $data);
		}

		return $column ? null : [];
	}

	public function update_data_with_default($id, $key, ...$args){
		if(is_array($key)){
			if(wpjam_is_assoc_array($key)){
				$defaults	= $args && is_array($args[0]) ? $args[0] : [];

				if(isset($key['meta_input']) && wpjam_is_assoc_array($key['meta_input'])){
					$this->update_data_with_default($id, wpjam_pull($key, 'meta_input'), wpjam_pull($defaults, 'meta_input'));
				}

				wpjam_map($key, fn($v, $k)=> $this->update_data_with_default($id, $k, $v, wpjam_pull($defaults, $k)));
			}

			return true;
		}

		[$value, $default]	= array_pad($args, 2, null);

		if(is_closure($value)){
			$value	= $value($this->get_data_with_default($id, $key, $default), $key, $id);
		}

		if(is_array($value)){
			if($value && (!is_array($default) || array_diff_assoc($default, $value))){
				return $this->update_data($id, $key, $value);
			}
		}else{
			if(isset($value) && ((is_null($default) && ($value || is_numeric($value))) || (!is_null($default) && $value != $default))){
				return $this->update_data($id, $key, $value);
			}
		}

		return $this->delete_data($id, $key);
	}

	public function delete_empty_data(){
		$wpdb	= $GLOBALS['wpdb'];
		$mids	= $wpdb->get_col("SELECT ".$this->get_column('id')." FROM ".$this->get_table()." WHERE meta_value = ''") ?: [];

		array_walk($mids, [$this, 'delete_by_mid']);
	}

	public function delete_by_key($key, $value=''){
		return delete_metadata($this->name, null, $key, $value, true);
	}

	public function delete_by_id($id){
		$wpdb	= $GLOBALS['wpdb'];
		$table	= $this->get_table();
		$column	= $this->get_column();
		$mids	= $wpdb->get_col($wpdb->prepare("SELECT meta_id FROM {$table} WHERE {$column} = %d ", $id)) ?: [];

		array_walk($mids, [$this, 'delete_by_mid']);
	}

	public function update_cache($ids){
		update_meta_cache($this->name, $ids);
	}

	public function cleanup(){
		$wpdb	= $GLOBALS['wpdb'];
		$key	= $this->object_key;
		$table	= $key ? $wpdb->{$this->name.'s'} : '';

		if(!$key){
			$model	= $this->object_model;

			if(!$model || !is_callable([$model, 'get_table'])){
				return;
			}

			$table	= $model::get_table();
			$key	= $model::get_primary_key();
		}

		if(is_multisite() && !str_starts_with($this->get_table(), $wpdb->prefix) && wpjam_lock($this->name.':meta_type:cleanup', DAY_IN_SECONDS, true)){
			return;
		}

		$mids	= $wpdb->get_col("SELECT m.".$this->get_column('id')." FROM ".$this->get_table()." m LEFT JOIN ".$table." t ON t.".$key." = m.".$this->get_column()." WHERE t.".$key." IS NULL") ?: [];

		array_walk($mids, [$this, 'delete_by_mid']);
	}

	public function create_table(){
		$table	= $this->get_table();
		$column	= $this->name.'_id';

		if($table != $GLOBALS['wpdb']->get_var("show tables like '{$table}'")){
			$GLOBALS['wpdb']->query("CREATE TABLE {$table} (
				meta_id bigint(20) unsigned NOT NULL auto_increment,
				{$column} bigint(20) unsigned NOT NULL default '0',
				meta_key varchar(255) default NULL,
				meta_value longtext,
				PRIMARY KEY (meta_id),
				KEY {$column} ({$column}),
				KEY meta_key (meta_key(191))
			)");
		}
	}

	public function register_keys(){
		if($this->name == 'post'){
			foreach(get_post_types(['show_in_rest'=>true]) as $post_type){
				foreach($this->get_options(['post_type'=>$post_type]) as $option){
					if(wpjam_is_assoc_array($option->fields)){
						wpjam_fields($option->fields)->register_meta($this->name, $post_type);
					}
				}
			}
		}
	}

	public function delete_if_default($check, $id, $key, $value){
		$type		= $this->name;
		$subtype	= $type == 'post' ? get_post_type($id) : ($type == 'term' ? get_term_taxonomy($id) : '');
		$registered	= $subtype ? (get_registered_meta_keys($type, $subtype)[$key] ?? null) : null;
		$registered	??= get_registered_meta_keys($type)[$key] ?? null;

		if($registered && array_key_exists('default', $registered) && $value === $registered['default']){
			return delete_metadata($type, $id, $key) || true;
		}

		return $check;
	}

	public function registered(){
		add_action('init', [$this, 'register_keys'], 99);

		wpjam_hooks('add_'.$this->name.'_metadata, update_'.$this->name.'_metadata', [$this, 'delete_if_default'], 10, 4);
	}

	public static function get_defaults(){
		return array_merge([
			'post'	=> ['object_model'=>'WPJAM_Post',	'object_column'=>'title',	'object_key'=>'ID'],
			'term'	=> ['object_model'=>'WPJAM_Term',	'object_column'=>'name',	'object_key'=>'term_id'],
			'user'	=> ['object_model'=>'WPJAM_User',	'object_column'=>'display_name','object_key'=>'ID'],
		], (is_multisite() ? [
			'blog'	=> ['object_key'=>'blog_id'],
			'site'	=> [],
		] : []));
	}
}

class WPJAM_Meta_Option extends WPJAM_Args{
	public function __get($key){
		$value	= parent::__get($key);

		if(isset($value)){
			return $value;
		}

		if($key == 'list_table'){
			return did_action('current_screen') && !empty($GLOBALS['plugin_page']);
		}elseif($key == 'show_in_rest'){
			return true;
		}elseif($key == 'show_in_posts_rest'){
			return $this->show_in_rest;
		}
	}

	public function __call($method, $args){
		if($method == 'prepare' && ($this->callback || $this->update_callback)){
			return [];
		}

		$id		= array_shift($args);
		$fields	= maybe_callback($this->fields, $id, $this->name);

		if($method == 'get_fields'){
			return $fields;
		}

		$object	= wpjam_fields($fields, array_merge($this->get_args(), ['id'=>$id]));

		if($method == 'callback'){
			$data	= wpjam_catch([$object, 'validate'], ...$args);

			if(is_wp_error($data) || !$data){
				return $data ?: true;
			}

			if($callback = $this->callback ?: $this->update_callback){
				$res	= is_callable($callback) ? call_user_func($callback, $id, $data, $fields) : false;

				return $res === false ? new WP_Error('invalid_callback') : $res;
			}

			return wpjam_update_metadata($this->meta_type, $id, $data, $object->get_defaults());
		}

		$res	= $object->$method(...$args);

		return $method == 'render' ? wpjam_echo(wpautop($this->summary ?: '').$res) : $res;
	}
}

class WPJAM_JSON extends WPJAM_Register{
	public function __invoke(){
		$method		= $this->method ?: $_SERVER['REQUEST_METHOD'];
		$attr		= $method != 'POST' && !str_ends_with($this->name, '.config') ? ['page_title', 'share_title', 'share_image'] : [];
		$response	= wpjam_try('apply_filters', 'wpjam_pre_json', [], $this, $this->name);
		$response	+= ['errcode'=>0, 'current_user'=>wpjam_try('wpjam_get_current_user', $this->pull('auth'))]+$this->pick($attr);

		if($this->modules){
			$modules	= maybe_callback($this->modules, $this->name, $this->args);
			$results	= array_map(['WPJAM_JSON_Module', 'parse'], wp_is_numeric_array($modules) ? $modules : [$modules]);
		}elseif($this->callback){
			$fields		= wpjam_try('maybe_callback', $this->fields ?: [], $this->name);
			$data		= $this->fields ? ($fields ? wpjam_fields($fields)->get_parameter($method) : []) : $this->args;
			$results[]	= wpjam_try($this->pull('callback'), $data, $this->name);
		}elseif($this->template){
			$results[]	= is_file($this->template) ? include $this->template : '';
		}else{
			$results[]	= wpjam_except($this->args, 'name');
		}

		$response	= array_reduce($results, fn($c, $v)=> array_merge($c, is_array($v) ? array_diff_key($v, wpjam_pick($c, $attr)) : []), $response);
		$response	= apply_filters('wpjam_json', $response, $this->args, $this->name);

		foreach($attr as $k){
			if(($v	= $response[$k] ?? '') || $k != 'share_image'){
				$response[$k]	= $k == 'share_image' ? wpjam_get_thumbnail($v, '500x400') : html_entity_decode($v ?: wp_get_document_title());
			}
		}

		return $response;
	}

	public static function die_handler($msg, $title='', $args=[]){
		wpjam_if_error($msg, 'send');

		$code	= $args['code'] ?? '';
		$data	= $code && $title ? ['modal'=>['title'=>$title, 'content'=>$msg]] : [];
		$code	= $code ?: $title;
		$item	= !$code && is_string($msg) ? wpjam_error($msg) : [];
		$item	= $item ?: ['errcode'=>($code ?: 'error'), 'errmsg'=>$msg]+$data;

		wpjam_send_json($item);
	}

	public static function redirect($name){
		header('X-Content-Type-Options: nosniff');

		rest_send_cors_headers(false);

		if('OPTIONS' === $_SERVER['REQUEST_METHOD']){
			status_header(403);
			exit;
		}

		ini_set('display_errors', 0);

		add_filter('wp_die_'.(array_find(['jsonp_', 'json_'], fn($v)=> call_user_func('wp_is_'.$v.'request')) ?: '').'handler', fn()=> [self::class, 'die_handler']);

		if(!try_remove_prefix($name, 'mag.')){
			return;
		}

		$name	= substr($name, str_starts_with($name, '.mag') ? 4 : 0);	// 兼容
		$name	= str_replace('/', '.', $name);
		$name	= wpjam_var('json', $name);
		$user	= wpjam_get_current_user();

		$user && !empty($user['user_id']) && wp_set_current_user($user['user_id']);

		do_action('wpjam_api', $name);

		wpjam_send_json(wpjam_catch(self::get($name) ?: wp_die('接口未定义', 'invalid_api')));
	}

	public static function get_defaults(){
		return array_fill_keys(['post.list', 'post.calendar', 'post.get'], ['modules'=>['WPJAM_JSON_Module', 'callback']])+[
			'media.upload'	=> ['modules'=>['callback'=>['WPJAM_JSON_Module', 'media']]],
			'site.config'	=> ['modules'=>['type'=>'config']],
		];
	}

	public static function get_current(){
		return wpjam_var('json');
	}

	public static function get_rewrite_rule(){
		return [
			['api/([^/]+)/(.*?)\.json?$',	['module'=>'json', 'action'=>'mag.$matches[1].$matches[2]'], 'top'],
			['api/([^/]+)\.json?$', 		'index.php?module=json&action=$matches[1]', 'top'],
		];
	}

	public static function __callStatic($method, $args){
		if(in_array($method, ['parse_post_list_module', 'parse_post_get_module'])){
			return wpjam_catch('WPJAM_JSON_Module::parse', [
				'type'	=> 'post_type',
				'args'	=> ['action'=>str_replace(['parse_post_', '_module'], '', $method)]+($args[0] ?? [])
			]);
		}
	}
}

class WPJAM_JSON_Module{
	public static function parse($module){
		$args	= $module['args'] ?? [];
		$args	= is_array($args) ? $args : wpjam_parse_shortcode_attr(stripslashes_deep($args), 'module');
		$parser	= ($module['callback'] ?? '') ?: (($type = $module['type'] ?? '') ? wpjam_callback(self::class.'::'.$type) : '');

		return $parser ? wpjam_try($parser, $args) : $args;
	}

	/* 规则:
	** 1. 分成主查询和子查询(sub=>1)
	** 2. 主查询支持 $_GET 参数,返回 next_cursor 和 total_pages,current_page
	** 3. $_GET 参数只适用于 post.list / term.list 只能用 $_GET 参数 mapping 来传递参数
	*/
	public static function post_type($args){
		$action	= wpjam_pull($args, 'action');

		if($action == 'upload'){
			return self::media($args, 'media');
		}

		static $query_vars;

		$wp		= $GLOBALS['wp'];
		$query	= $GLOBALS['wp_query'];
		$output	= wpjam_pull($args, 'output');
		$vars	= ($query_vars ??= $wp->query_vars);

		if(in_array($action, ['list', 'calendar'])){
			$sub	= $action == 'calendar' ? false : wpjam_pull($args, 'sub');
			$args	= array_diff_key($args, WPJAM_Post::get_default_args());

			if($sub){
				$query	= wpjam_query($args);
				$parsed	= wpjam_get_posts($query, $args);
			}else{
				$vars	= array_merge(wpjam_except($vars, ['module', 'action']), $args);

				if($action == 'calendar'){
					$vars	+= [
						'year'		=> (int)wpjam_get_parameter('year') ?: wpjam_date('Y'),
						'monthnum'	=> (int)wpjam_get_parameter('month') ?: wpjam_date('m'),
						'day'		=> (int)wpjam_get_parameter('day')
					];

					$args	+= ['format'=>'date'];
				}else{
					$number	= wpjam_find(['number', 'posts_per_page'], fn($v)=> $v, fn($k)=> (int)wpjam_get_parameter($k));
					$vars	+= ($number && $number != -1) ? ['posts_per_page'=> min($number, 100)] : [];
					$vars	+= array_filter(['offset'=>wpjam_get_parameter('offset')]);

					if($post__in = wpjam_get_parameter('post__in')){
						$vars['post__in']		= wp_parse_id_list($post__in);
						$vars['orderby']		??= 'post__in';
						$vars['posts_per_page']	??= -1;
					}
				}

				$wp->query_vars	= $vars = wpjam_parse_query_vars($vars, true);
				$wp->query_posts();

				$parsed	= wpjam_get_posts($query, $args);

				if($action != 'calendar'){
					$nopaging	= $query->get('nopaging');
					$json		= [
						'total'			=> $query->found_posts,
						'total_pages'	=> $nopaging ? ($query->found_posts ? 1 : 0) : $query->max_num_pages,
						'current_page'	=> $nopaging ? 1 : ($query->get('paged') ?: 1),
					];

					if(empty($vars['paged']) && empty($vars['s']) && in_array($vars['orderby'] ?? 'date', ['date', 'post_date'], true)){
						$json['next_cursor']	= ($parsed && $query->max_num_pages > 1) ? end($parsed)['timestamp'] : 0;
					}

					$is			= wpjam_is($query);
					$queried	= $query->get_queried_object();

					if(in_array($is, ['category', 'tag', 'tax'], true)){
						$json	+= ['current_taxonomy'=>($queried ? $queried->taxonomy : null)];
						$json	+= $queried ? ['current_term'=>wpjam_get_term($queried, $queried->taxonomy)] : [];
					}elseif($is === 'author'){
						$json	+= ['current_author'=>wpjam_get_user($query->get('author'))];
					}elseif($is === 'post_type_archive'){
						$json	+= ['current_post_type'=>($queried ? $queried->name : null)];
					}

					$json	+= is_string($is) ? ['is'=>$is] : [];
				}

				if(!$output && !empty($vars['post_type']) && is_string($vars['post_type'])){
					$output	= wpjam_get_post_type_setting($vars['post_type'], 'plural') ?: $vars['post_type'].'s';
				}
			}

			$output	= $output ?: 'posts';

			$json[$output]	= $parsed;

			return apply_filters('wpjam_posts_json', $json, $query, $output);
		}elseif($action == 'get'){
			$type	= ($args['post_type'] ??= '') === 'any' ? '' : $args['post_type'];
			$status	= $args['post_status'] ?? '';
			$vars	= ['cache_results'=>true]+($status ? ['status'=>$status] : [])+$vars;

			if($type){
				$var	= post_type_exists($type) ? (is_post_type_hierarchical($type) ? 'pagename' : 'name') : wp_die('invalid_post_type');
				$name	= $vars[$var] ?? '';
			}else{
				$map	= wp_list_pluck(get_post_types(['_builtin'=>false, 'query_var'=>true], 'objects'), 'query_var')+['post'=>'name', 'page'=>'pagename'];
				$type	= array_find_key($map, fn($v)=> !empty($vars[$v]));
				$name	= $type ? $vars[$map[$type]] : null;
			}

			if(!$name){
				$id		= ($args['id'] ?? 0) ?: (int)wpjam_get_parameter('id', ['required'=>true]);
				$type	??= get_post_type($id);
				$vars	= ['p'=>($type && (get_post_type($id) == $type)) ? $id : wp_die('invalid_post_id')]+$vars;
			}

			$vars['post_type']	= $type;
			$wp->query_vars		= $vars;
			$wp->query_posts();

			if(empty($id) && !$status && !$query->have_posts()){
				$id	= apply_filters('old_slug_redirect_post_id', null) ?: wp_die('invalid_post_id');

				$wp->query_vars	= ['post_type'=>'any', 'p'=>$id]+wpjam_except($vars, ['name', 'pagename']);
				$wp->query_posts();
			}

			$parsed	= $query->have_posts() ? array_first(wpjam_get_posts($query, $args)) : wp_die('invalid_parameter');
			$output	= $output ?: $parsed['post_type'];

			return wpjam_pull($parsed, ['share_title', 'share_image', 'share_data'])+[$output => $parsed];
		}
	}

	public static function media($args, $format=''){
		require_once ABSPATH.'wp-admin/includes/file.php';
		require_once ABSPATH.'wp-admin/includes/media.php';
		require_once ABSPATH.'wp-admin/includes/image.php';

		$media	= ($args['media'] ?? '') ?: 'media';
		$output	= ($args['output'] ?? '') ?: 'url';

		isset($_FILES[$media]) || wpjam_throw('invalid_parameter', [$media]);

		if($format == 'media'){
			$id		= wpjam_try('media_handle_upload', $media, (int)wpjam_get_post_parameter('post_id',	['default'=>0]));
			$url	= wp_get_attachment_url($id);
			$query	= wpjam_get_image_size($id);
		}else{
			$upload	= wpjam_upload($media);
			$url	= $upload['url'];
			$query	= wpjam_get_image_size($upload['file'], 'file');
		}

		return [$output => $query ? add_query_arg($query, $url) : $url];
	}

	public static function taxonomy($args){
		$object		= wpjam_get_taxonomy(wpjam_get($args, 'taxonomy')) ?: wpjam_throw('invalid_taxonomy');
		$mapping	= wpjam_array(wp_parse_args(wpjam_pull($args, 'mapping') ?: []), fn($k, $v)=> [$k, wpjam_get_parameter($v)], true);
		$args		= array_merge($args, $mapping);
		$number		= (int)wpjam_pull($args, 'number');
		$paged		= wpjam_pull($args, 'paged') ?: 1;
		$output		= wpjam_pull($args, 'output') ?: $object->plural;
		$terms		= wpjam_get_terms($args);

		if($terms && $number){
			$terms = array_slice($terms, $number * ($paged-1), $number);
		}

		return [$output	=> $terms ? array_values($terms) : []];
	}

	public static function setting($args){
		if($option	= $args['option_name'] ?? ''){
			$name	= $args['setting_name'] ?? ($args['setting'] ?? null);
			$output	= ($args['output'] ?? '') ?: ($name ?: $option);
			$object	= wpjam_option($option);
			$names	= $object && $object->option_type != 'array' ? [$option, $name] : [$name];

			return [$output => wpjam_get($object->option_type ? $object->prepare_by_fields() : $object->get_option(), array_filter($names) ?: null)];
		}
	}

	public static function data_type($args){
		$name	= wpjam_pull($args, 'data_type');
		$args	= wp_parse_args(($args['query_args'] ?? $args) ?: []);
		$object	= wpjam_get_data_type($name, $args) ?: wpjam_throw('invalid_data_type');

		return ['items'=>$object->query_items($args+['search'=>wpjam_get_parameter('s')])];
	}

	public static function config($args){
		return wpjam_get_config($args['group'] ?? '');
	}

	public static function callback($name, $args=[]){
		$output	= $args['output'] ?? null;

		[$type, $action]	= explode('.', $name);

		if($type == 'post'){
			$type	= wpjam_get_parameter('post_type');
			$output	??= $action == 'get' ? 'post' : 'posts';
		}

		$args		= ['post_type'=>$type, 'action'=>$action, 'output'=>$output]+array_intersect_key($args, WPJAM_Post::get_default_args());
		$modules[]	= ['type'=>'post_type',	'args'=>array_filter($args, fn($v)=> !is_null($v))];

		if($action == 'list' && $type && is_string($type) && !str_contains($type, ',')){
			foreach(get_object_taxonomies($type) as $tax){
				if(is_taxonomy_hierarchical($tax) && wpjam_get_taxonomy_setting($tax, 'show_in_posts_rest')){
					$modules[]	= ['type'=>'taxonomy',	'args'=>['taxonomy'=>$tax, 'hide_empty'=>0]];
				}
			}
		}

		return $modules;
	}
}

class WPJAM_AJAX extends WPJAM_Args{
	public function __invoke(){
		add_filter('wp_die_ajax_handler', fn()=> ['WPJAM_JSON', 'die_handler']);

		$cb	= $this->callback;

		(!$cb || !is_callable($cb)) && wp_die('invalid_callback');

		if($this->admin){
			$data	= $this->fields ? wpjam_fields($this->fields)->get_parameter('POST') : wpjam_get_post_parameter();
			$verify	= wpjam_get($data, 'action_type') !== 'form';
		}else{
			$data	= array_merge(wpjam_get_data_parameter(), wpjam_except(wpjam_get_post_parameter(), ['action', 'defaults', 'data', '_ajax_nonce']));
			$data	= array_merge($data, $this->fields ? wpjam_fields($this->fields)->validate($data, 'parameter') : []);
			$verify	= $this->verify !== false;
		}

		$action	= $verify ? $this->get_attr($this->name, $data, 'nonce_action') : '';
		$action && !check_ajax_referer($action, false, false) && wpjam_send_json(['errcode'=>'invalid_nonce', 'errmsg'=>'验证失败,请刷新重试。']);

		$this->allow && !wpjam_call($this->allow, $data) && wp_die('access_denied');

		return $cb($data, $this->name);
	}

	public static function get_attr($name, $data=[], $output=''){
		if($ajax = wpjam('ajax', $name)){
			$cb		= $ajax['nonce_action'] ?? '';
			$action	= $cb ? $cb($data) : (empty($ajax['admin']) ? $name.wpjam_join(':', wpjam_pick($data, $ajax['nonce_keys'] ?? [])) : '');

			return $output == 'nonce_action' ? $action : ['action'=>$name, 'data'=>$data]+($action ? ['nonce'=>wp_create_nonce($action)] : []);
		}
	}

	public static function create($name, $args){
		if(!wpjam('ajax')){
			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
			]);

			is_login() || add_filter('script_loader_src', fn($src, $handle)=> $handle == 'wpjam-ajax' && current_theme_supports('script', $handle) ? '' : $src, 10, 2);
		}

		if(wp_doing_ajax() && ($_REQUEST['action'] ?? '') == $name && (is_user_logged_in() || !empty($args['nopriv']))){
			add_action('wp_ajax_'.(is_user_logged_in() ? '' : 'nopriv_').$name, fn()=> wpjam_send_json(wpjam_catch(new static(['name'=>$name]+$args))));
		}

		return wpjam('ajax', $name, $args);
	}
}

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_Notice{
	public static function add($item, $type='admin', $id=''){
		if(!$id || ($type == 'admin' ? (!is_multisite() || get_site($id)) : get_userdata($id))){
			$item	= is_array($item) ? $item : ['notice'=>$item];
			$item	+= ['type'=>'error', 'notice'=>'', 'time'=>time(), 'key'=>md5(serialize($item))];

			return (self::get_instance($type, $id))->insert($item);
		}
	}

	public static function render($type){
		foreach((self::get_instance($type))->get_items() as $key => $item){
			$data	= ['notice_key'=>$key, 'notice_type'=>$type];
			$item	+= ['class'=>'is-dismissible', 'title'=>'', 'modal'=>0];
			$notice	= trim($item['notice']);
			$notice	.= !empty($item['admin_url']) ? (($item['modal'] ? "\n\n" : ' ').'<a href="'.add_query_arg($data, home_url($item['admin_url'])).'">点击查看<span class="dashicons dashicons-arrow-right-alt"></span></a>') : '';

			echo wpjam_tag('div', [
				'class' => $item['modal'] ? 'hidden notice-modal' : 'notice notice-'.$item['type'].' '.$item['class'],
				'data'	=> $item['modal'] ? ['title'=>$item['title'] ?: '消息'] : [],
			], wpautop($notice).wpjam_get_page_button('delete_notice', ['data'=>$data]))."\n";
		}
	}

	public static function callback(){
		if($key	= wpjam_get_data_parameter('notice_key')){
			return ($type = wpjam_get_data_parameter('notice_type')) == 'admin' && !current_user_can('manage_options') ? wp_die('bad_authentication') : (self::get_instance($type))->delete($key);
		}
	}

	public static function init(){
		add_action('all_admin_notices', function(){
			self::callback();
			self::render('user');

			current_user_can('manage_options') && self::render('admin');
		}, 9);

		wpjam_register_page_action('delete_notice', [
			'button_text'	=> '删除',
			'tag'			=> 'span',
			'class'			=> 'hidden delete-notice',
			'validate'		=> true,
			'direct'		=> true,
			'callback'		=> [self::class, 'callback'],
		]);
	}

	public static function get_instance($type='admin', $id=0){
		$filter	= fn($items)=> array_filter(($items ?: []), fn($v)=> $v['time']>(time()-MONTH_IN_SECONDS*3) && trim($v['notice']));
		$name	= 'wpjam_notices';
		$id		= (int)$id ?: ($type == 'user' ? get_current_user_id() : get_current_blog_id());
		$args	= $type == 'user' ? [
			'items_type'	=> 'meta',
			'meta_type'		=> 'user',
			'meta_key'		=> $name,
			'object_id'		=> $id,
			'get_items'		=> fn()=> $filter(get_user_meta($id, $name, true)),
		] : [
			'get_items'		=> fn()=> $filter(wpjam_get_option($name, $id)),
			'update_items'	=> fn($items)=> wpjam_update_option($name, $items, $id),
		];

		return wpjam_get_handler('notice:'.$type.':'.$id, $args+['primary_key'=>'key']);
	}
}

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){
		if(in_array($method, ['get_wp_error', 'get_error'])){
			return $this->error;
		}

		return [$this->error, $method](...$args);
	}
}