HEX
Server: nginx/1.22.1
System: Linux VM-16-9-centos 3.10.0-1160.99.1.el7.x86_64 #1 SMP Wed Sep 13 14:19:20 UTC 2023 x86_64
User: www (1001)
PHP: 7.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/softfox.com.cn/wp-content/plugins/wpjam-basic/includes/class-wpjam-field.php
<?php
class WPJAM_Field extends WPJAM_Attr{
	protected function __construct($args){
		$this->args	= $args;

		$this->_data_type	= wpjam_get_data_type_object($this->data_type);

		if($this->_data_type){
			$this->query_args	= $this->_data_type->parse_query_args($this) ?: new StdClass;
		}

		$this->prepend_name($this->pull('prepend_name'));
	}

	public function __get($key){
		if($key == 'default'){
			return $this->get_arg('show_in_rest.default', $this->value);
		}elseif($key == 'names'){
			return wpjam_parse_name($this->name);
		}elseif($key == 'editable'){
			return $this->show_admin_column !== 'only' && !$this->disabled && !$this->readonly;
		}elseif($key == '_for'){
			if(!$this->is(['view', 'mu', 'fieldset', 'img', 'uploader', 'radio', 'checkbox'])
				|| ($this->is('checkbox') && !$this->options)
			){
				return $this->id;
			}
		}

		$value	= parent::__get($key);

		if(is_null($value)){
			if($key == '_title'){
				return $this->title.'「'.$this->key.'」';
			}elseif($key == '_schema'){
				return $this->parse_schema();
			}elseif($key == 'show_in_rest'){
				return $this->editable;
			}
		}

		if($key == 'show_if'){
			if($value && !is_object($value)){
				return $this->show_if = $this->parse_show_if($value);
			}
		}elseif($key == 'sortable'){
			return $this->editable && $value !== false ? 'sortable' : '';
		}elseif(in_array($key, ['min', 'max', 'minlength', 'maxlength', 'max_items', 'min_items'])){
			return is_numeric($value) ? $value : null;
		}

		return $value;
	}

	public function __call($method, $args){
		if(strpos($method, '_by_')){
			list($method, $type)	= explode('_by_', $method);

			if($this->{'_'.$type}){
				if($type == 'data_type'){
					$args[]	= array_merge((array)$this->query_args, ['title'=>$this->_title]);
				}

				return wpjam_try([$this->{'_'.$type}, $method], ...$args);
			}
		}elseif($method == 'get_schema'){
			return $this->_schema;
		}

		trigger_error($method);
	}

	public function is($type, $strict=false){
		$type	= (array)$type;

		if(!$strict){
			if(in_array('fieldset', $type) && is_a($this, 'WPJAM_Fieldset')){
				return true;
			}

			if(in_array('mu', $type) && is_a($this, 'WPJAM_MU_Field')){
				return true;
			}

			if(in_array('view', $type)){
				$type	= array_merge($type, ['hr', 'br']);
			}
		}

		return in_array($this->type, (array)$type, $strict);
	}

	public function prepend_name($prepend){
		if($prepend){
			$this->name	= $prepend.'['.implode('][', $this->names).']';
		}
	}

	protected function prepare_schema(){
		$type	= $this->type;
		$schema	= ['type'=>'string'];

		if($type == 'email'){
			$schema['format']	= 'email';
		}elseif($type == 'color'){
			$schema['format']	= 'hex-color';
		}elseif($type == 'url'){
			$schema['format']	= 'uri';
		}elseif(in_array($type, ['number', 'range'])){
			$step	= $this->step ?? 1;

			if($step == 'any' || strpos($step, '.')){
				$schema['type']	= 'number';
			}else{
				$schema['type']	= 'integer';

				if($step && $step != 1){
					$schema['multipleOf']	= $step;
				}
			}
		}elseif($type == 'timestamp'){
			$schema['type']	= 'integer';
		}elseif($type == 'checkbox'){
			$schema['type']	= 'boolean';
		}

		return $schema;
	}

	protected function parse_schema($schema=null){
		if(is_null($schema)){
			$schema	= $this->prepare_schema();
			$map	= [];

			if(in_array($schema['type'], ['number', 'integer'])){
				$map	= [
					'min'	=> 'minimum',
					'max'	=> 'maximum',
				];
			}elseif($schema['type'] == 'string'){
				$map	= [
					'minlength'	=> 'minLength',
					'maxlength'	=> 'maxLength',
					'pattern'	=> 'pattern',
				];
			}elseif($schema['type'] == 'array'){
				$map	= [
					'max_items'		=> 'maxItems',
					'min_items'		=> 'minItems',
					'unique_items'	=> 'uniqueItems',
				];
			}

			foreach($map as $key => $attr){
				if(isset($this->$key)){
					$schema[$attr]	= $this->$key;
				}
			}

			$_schema	= $this->get_arg('show_in_rest.schema');
			$_type		= $this->get_arg('show_in_rest.type');

			if(is_array($_schema)){
				$schema	= merge_deep($schema, $_schema);
			}

			if($_type){
				if($schema['type'] == 'array' && $_type != 'array'){
					$schema['items']['type']	= $_type;
				}else{
					$schema['type']	= $_type;
				}
			}

			if($this->required && !$this->show_if){	// todo 以后可能要改成 callback
				$schema['required']	= true;
			}
		}

		$type	= $schema['type'];

		if($type != 'object'){
			unset($schema['properties']);
		}elseif($type != 'array'){
			unset($schema['items']);
		}

		if(isset($schema['enum'])){
			if($type == 'integer'){
				$callback	= 'intval';
			}elseif($type == 'number'){
				$callback	= 'floatval';
			}else{
				$callback	= 'strval';
			}

			$schema['enum']	= array_map($callback, $schema['enum']);
		}elseif(isset($schema['properties'])){
			$schema['properties']	= $this->map($schema['properties'], 'parse_schema');
		}elseif(isset($schema['items'])){
			$schema['items']	= $this->parse_schema($schema['items']);
		}

		return $schema;
	}

	protected function parse_show_if($show_if){
		$show_if	= wpjam_parse_show_if($show_if);

		if($show_if){
			foreach(['postfix', 'prefix'] as $fix){
				$value	= array_pull($show_if, $fix, $this->{'_'.$fix});

				if($value){
					$show_if['key']	= wpjam_call('wpjam_add_'.$fix, $show_if['key'], $value);
				}
			}
		}

		return $show_if;
	}

	public function validate($value){
		$this->required($value);

		$value	= $this->try('validate_value', $value);

		$this->required($value);

		if(is_array($value) || is_populated($value)){	// 空值只需用 required 验证
			wpjam_try('rest_validate_value_from_schema', $value, $this->_schema, $this->_title);
		}

		return $value;
	}

	protected function required($value){
		if($this->required && is_blank($value)){
			throw new WPJAM_Exception([$this->_title], 'value_required');
		}
	}

	public function validate_value($value){
		if($this->_data_type){
			$value	= $this->validate_value_by_data_type($value);
		}

		if($this->is('timestamp')){
			$value	= wpjam_strtotime($value);
		}

		return $this->sanitize_value($value);
	}

	protected function sanitize_value($value, $schema=null){
		$schema	= $schema ?: $this->_schema;
		$type	= array_get($schema, 'type');

		if(is_null($value)){
			if(array_get($schema, 'required')){
				$value	= false;
			}
		}else{
			$value	= parent::sanitize_value($value);
		}

		if($type == 'array'){
			$schema	= array_get($schema, 'items');
			$value	= $this->map($value, 'sanitize_value', $schema);
		}elseif($type == 'integer'){
			if(is_numeric($value)){
				$value	= (int)$value;
			}
		}elseif($type == 'number'){
			if(is_numeric($value)){
				$value	= (float)$value;
			}
		}elseif($type == 'string'){
			if(is_scalar($value)){
				$value	= (string)$value;
			}
		}elseif($type == 'null'){
			if(is_blank($value)){
				$value	= null;
			}
		}elseif($type == 'boolean'){
			if(is_scalar($value) || is_null($value)){
				$value	= rest_sanitize_boolean($value);
			}
		}

		return $value;
	}

	public function pack($value){
		return array_reduce(array_reverse($this->names), function($value, $sub){ return [$sub => $value]; }, $value);
	}

	public function unpack($data){
		return _wp_array_get($data, $this->names);
	}

	protected function value_callback($args){
		if(!$args || ($this->is('view') && !is_null($this->value))){
			return $this->default;
		}

		$value	= null;
		$name	= $this->names[0];
		$id		= array_pull($args, 'id');
		$arg	= $id ?? $args;

		if($this->value_callback && is_callable($this->value_callback)){
			$value	= wpjam_value_callback($this->value_callback, $name, $arg);
		}elseif($args){
			if(!empty($args['data']) && isset($args['data'][$name])){
				$value	= $args['data'][$name];
			}elseif(!empty($args['value_callback']) && $arg !== false){
				$value	= wpjam_value_callback($args['value_callback'], $name, $arg);
			}elseif(!empty($args['meta_type']) && $id){
				$value	= wpjam_get_metadata($args['meta_type'], $id, $name);
			}
		}

		if(!is_wp_error($value) && !is_null($value)){
			$value	= $this->unpack([$name=>$value]);

			if(!is_null($value)){
				return $value;
			}
		}

		return $this->default;
	}

	public function prepare($args){
		$value	= $this->value_callback($args);
		$value	= rest_sanitize_value_from_schema($value, $this->_schema, $this->_title);
		$value	= $this->prepare_value($value);

		return $value;
	}

	public function prepare_value($value){
		return $this->_data_type ? $this->prepare_value_by_data_type($value, $this->parse_required) : $value;
	}

	public function wrap($tag, $args=[]){
		$args	= wpjam_array($args);
		$class	= array_merge(array_wrap($this->wrap_class), array_wrap($args->pull('wrap_class')));
		$class	= array_merge($class, [$this->disabled, $this->readonly, ($this->is('hidden') ? 'hidden' : '')]);
		$data	= ['show_if'=>$this->show_if];

		if($data['show_if']){
			$class[]	= 'show_if';
			$tag		= $tag ?: 'span';
		}

		$field	= $this->render($args, false);

		if($this->title){
			$label	= wpjam_tag('label', ['for'=>$this->_for], $this->title);

			if(in_array('sub-field', $class)){
				$label->push_arg('class', 'sub-field-label');
				$field->wrap('div', ['sub-field-detail']);
			}
		}else{
			$label	= '';
		}

		if($tag == 'tr'){
			if($label){
				$label->wrap('th', ['scope'=>'row']);
			}

			$field->wrap('td', ['colspan'=>($label ? false : 2)]);
		}elseif($tag == 'p'){
			$label	.= $label ? wpjam_tag('br') : '';
		}

		return $field->before($label)->wrap($tag, ['class'=>$class, 'data'=>$data, 'id'=>$tag.'_'.esc_attr($this->id)]);
	}

	public function render($args=[], $to_string=true){
		if(is_null($this->class)){
			if($this->is('textarea')){
				$this->class	= ['large-text'];
			}elseif($this->is(['text', 'password', 'url', 'image', 'file', 'mu-image', 'mu-file'], true)){
				$this->class	= ['regular-text'];
			}
		}

		$this->class	= array_wrap($this->class);
		$this->value	= $this->value_callback($args);

		$rendered	= $this->render_component();
		$rendered	= $args ? $this->after_render($rendered) : $rendered;

		return $to_string ? (string)$rendered : $rendered;
	}

	protected function render_component(){
		$args	= [];
		$value	= $this->value;

		if($this->is('hr')){
			return wpjam_tag('hr');
		}elseif($this->is('view')){
			if($this->options){
				$options	= wpjam_parse_options($this->options);
				$value		= $value ? [$value] : ['', 0];

				foreach($value as $v){
					if(isset($options[$v])){
						return $options[$v];
					}
				}
			}

			return $this->value;
		}elseif($this->is(['editor', 'textarea'])){
			$this->cols	= $this->cols ?: 50;
			$this->rows	= $this->rows ?: ($this->is('editor') ? 12 : 6);

			if($this->is('editor')){
				if(!wp_doing_ajax()){
					return wpjam_tag('div', ['style'=>$this->style], wpjam_ob_get_contents('wp_editor', $value, $this->id, [
						'textarea_name'	=> $this->name,
						'textarea_rows'	=> $this->rows
					]));
				}

				$args	= [
					'class'	=> ['editor', 'large-text'],
					'data'	=> ['settings'=>[
						'tinymce'		=> true,
						'quicktags'		=> true,
						'mediaButtons'	=> current_user_can('upload_files')
					]]
				];
			}

			return $this->tag('textarea', $args, esc_textarea(implode("\n", (array)$value)));
		}

		$query	= $label = '';

		if($this->is('color')){
			$args	= ['type'=>'text', 'class'=>'color'];
		}elseif($this->is('checkbox')){
			$label	= $this->label;
			$args	= ['value'=>1,	'checked'=>($value == 1)];
		}elseif($this->is('timestamp')){
			$args	= ['type'=>'datetime-local', 'value'=>($value ? wpjam_date('Y-m-d\TH:i', $value) : '')];
		}elseif($this->_data_type){
			$query	= $this->query_label_by_data_type($this->value) ?: '';
			$args	= ['class'=>['autocomplete', ($query ? 'hidden' : '')]];
			$query	= $this->render_query($query);
		}

		return $this->tag('input', $args)->after($label)->after($query);
	}

	protected function render_query($label){
		return self::get_icon('dismiss')->after($label, 'span', ['query-text'])->wrap('span', array_merge($this->class, ['query-title']));
	}

	protected function after_render($tag){
		$tag	= wpjam_wrap_tag($tag)->before($this->before)->after($this->after);

		if($this->_for && ($this->before || $this->after || $this->label)){
			$tag->wrap('label', ['id'=>'label_'.$this->_for, 'for'=>$this->_for]);
		}

		if($this->description){
			$tag->after($this->description, 'p', ['description']);
		}

		if($this->buttons){
			$tag->after(' '.implode(' ', $this->map($this->buttons, 'create')));
		}

		return $tag;
	}

	protected function tag($tag='input', $attr=[], $text=''){
		$class	= array_merge($this->class, array_wrap(array_pull($attr, 'class')), ['field-key-'.$this->key]);
		$attr	= merge_deep($this->get_args(), $attr);
		$data	= array_merge(array_wrap(array_pull($attr, 'data'), []), array_pulls($attr, ['key', 'data_type', 'query_args']));
		$attr	= array_merge($attr, ['data'=>$data, 'class'=>$class]);
		$attr	= array_except($attr, ['default', 'options', 'title', 'label', 'before', 'after', 'description', 'item_type', 'item_class', 'max_items', 'min_items', 'unique_items', 'direction', 'group', 'buttons', 'button_text', 'custom_input', 'size', 'post_type', 'taxonomy', 'sep', 'fields', 'mime_types', 'drap_drop', 'parse_required', 'show_if', 'show_in_rest', 'sortable_column', 'column_style', 'show_admin_column', 'wrap_class']);

		if($tag == 'input'){
			if(!isset($attr['inputmode'])){
				if(in_array($attr['type'], ['url', 'tel', 'email', 'search'])){
					$attr['inputmode']	= $attr['type'];
				}elseif($attr['type'] == 'number'){
					$step	= $attr['step'] ?? 1;

					$attr['inputmode']	= ($step == 'any' || strpos($step, '.')) ? 'decimal' : 'numeric';
				}
			}
		}else{
			$attr	= array_except($attr, ['type', 'value']);
		}

		return wpjam_tag($tag, $attr, $text);
	}

	public function affix($affix_by, $i=null, $item=null){
		$prepend	= $affix_by->name;
		$prefix		= $affix_by->key.'__';
		$postfix	= '';

		if(isset($i)){
			$prepend	.= '['.$i.']';
			$postfix	= $this->_postfix = '__'.$i;

			if(is_array($item) && isset($item[$this->name])){
				$this->value	= $item[$this->name];
			}
		}

		$this->prepend_name($prepend);

		$this->_prefix	= $prefix.$this->_prefix ;
		$this->id		= $prefix.$this->id.$postfix;
		$this->key		= $prefix.$this->key.$postfix;
	}

	public function callback($args=[]){
		return $this->render($args);
	}

	public static function get_icon($name){
		return array_reduce(wp_parse_list($name), function($icon, $name){
			if($name == 'sortable'){
				$args	= ['span', ['dashicons', 'dashicons-menu']];
			}elseif($name == 'multiply'){
				$args	= ['span', ['dashicons', 'dashicons-no-alt']];
			}elseif($name == 'dismiss'){
				$args	= ['span', ['dashicons', 'dashicons-dismiss', 'init']];
			}elseif($name == 'del_btn'){
				$args	= ['删除', 'a', ['button', 'del-item']];
			}elseif(in_array($name, ['del_icon', 'del_img'])){
				$args	= ['a', ['dashicons', 'dashicons-no-alt', ($name == 'del_icon' ? 'del-item' : 'del-img')]];
			}

			return $icon->after(...$args);
		}, wpjam_tag());
	}

	public static function create($args, $key=''){
		if(empty($args['key']) && $key){
			$args['key']	= $key;
		}

		if(is_numeric($args['key'])){
			trigger_error('Field 的 key「'.$args['key'].'」'.'不能为纯数字');
			return;
		}elseif(!$args['key']){
			trigger_error('Field 的 key 不能为空');
			return;
		}

		$total	= array_pull($args, 'total');

		if($total && !isset($args['max_items'])){
			$args['max_items']	= $total;
		}

		$field	= self::parse_attr($args);

		if(!empty($field['size'])){
			$size	= $field['size'] = wpjam_parse_size($field['size']);

			if(!isset($field['description']) && !empty($size['width']) && !empty($size['height'])){
				$field['description']	= '建议尺寸:'.$size['width'].'x'.$size['height'];
			}
		}

		if(empty($fields['buttons']) && !empty($field['button'])){
			$fields['buttons']	= [$field['button']];
		}

		$field['before']	= empty($field['before']) ? '' : $field['before'].' ';
		$field['after']		= empty($field['after']) ? '' : ' '.$field['after'];
		$field['options']	= array_wrap(array_pull($field, 'options'), 'wp_parse_args');
		$field['id']		= array_pull($field, 'id') ?: $field['key'];
		$field['name']		= array_pull($field, 'name') ?: $field['key'];
		$field['type']		= $type	= array_pull($field, 'type') ?: ($field['options'] ? 'select' : 'text');

		if(in_array($type, ['image', 'mu-image'])){
			$field['item_type']	= 'image';
		}elseif($type == 'mu-text'){
			$field['item_type']		= $field['item_type'] ?? 'text';
			$field['item_class']	= $field['class'] ?? null;
		}

		if($type == 'checkbox'){
			if($field['options']){
				return new WPJAM_Options_Field($field);
			}else{
				if(!isset($field['label']) && !empty($field['description'])){
					$field['label']	= array_pull($field, 'description');
				}
			}
		}elseif(in_array($type, ['select', 'radio'])){
			return new WPJAM_Options_Field($field);
		}elseif(in_array($type, ['fieldset', 'fields'])){
			return new WPJAM_Fieldset($field);
		}elseif(in_array($type, ['img', 'image', 'file', 'uploader'], true)){
			return new WPJAM_Image_Field($field);
		}elseif(str_starts_with($type, 'mu-')){
			return new WPJAM_MU_Field($field);
		}elseif(in_array($type, ['view', 'hr', 'br'], true)){
			$field['disabled']	= 'disabled';
		}

		return new WPJAM_Field($field);
	}
}

class WPJAM_Options_Field extends WPJAM_Field{
	public function __get($key){
		$value	= parent::__get($key);

		if(is_null($value)){
			if($key == '_values'){
				return $this->_values = array_keys(wpjam_parse_options($this->options));
			}elseif($key == '_custom' && $this->custom_input){
				$input	= $this->custom_input;
				$custom	= is_array($input) ? $input : [];
				$custom	= self::create(wp_parse_args($custom, [
					'title'			=> is_string($input) ? $input : '其他',
					'placeholder'	=> '请输入其他选项',
					'id'			=> $this->id.'__custom_input',
					'key'			=> $this->key.'__custom_input',
					'type'			=> 'text',
					'class'			=> '',
					'required'		=> true,
					'data-wrap_id'	=> $this->is('select') ? '' : $this->id.'_options',
					'show_if'		=> ['key'=>$this->key, 'value'=>'__custom'],
				]));

				$custom->_title	= $this->_title.'-「'.$custom->title.'」';

				return $this->_custom = $custom;
			}
		}

		return $value;
	}

	public function __call($method, $args){
		if(str_ends_with($method, '_by_custom')){
			$field	= $this->_custom;
			$value	= $args[0];

			if(!$field){
				return $value;
			}

			$custom	= null;
			$values	= array_map('strval', $this->_values);

			if($this->is('checkbox')){
				$value	= array_diff($value, ['__custom']);
				$diff	= array_diff($value, $values);

				if($method == 'validate_by_custom' && count($diff) > 1){
					throw new WPJAM_Exception($field->_title.'只能传递一个其他选项值', 'too_many_custom_value');
				}

				if($diff){
					$custom	= current($diff);
				}
			}else{
				if($value && !in_array($value, $values)){
					$custom	= $value;
				}
			}

			if($method == 'render_by_custom'){
				$field->value	= $custom;
			}elseif($method == 'validate_by_custom'){
				if(isset($custom)){
					$field->_schema	= $this->is('checkbox') ? $this->get_arg('_schema.items') : $this->_schema;
					$field->validate($custom);
				}
			}

			return $value;
		}

		return parent::__call($method, $args);
	}

	protected function prepare_schema(){
		$schema	= ['type'=>'string'];

		if(!$this->_custom){
			$schema['enum']	= $this->_values;
		}

		return $this->is('checkbox') ? ['type'=>'array', 'items'=>$schema] : $schema;
	}

	public function prepare_value($value){
		if($this->is('checkbox')){
			return $this->prepare_by_custom($value);
		}

		return $value;
	}

	public function validate_value($value){
		if($this->is('checkbox')){
			$value	= $value ?: [];
		}

		return $this->sanitize_value($this->validate_by_custom($value));
	}

	protected function render_component(){
		if(!$this->is('select')){
			$this->update_args(['data-wrap_id'=>$this->id.'_options']);
		}

		if($this->is('checkbox')){
			$this->name	.= '[]';

			if(!is_array($this->value) && !is_blank($this->value)){
				$this->value	= [$this->value];
			}
		}else{
			$this->value	= $this->value ?? current($this->_values);
		}

		$items	= $this->options;
		$custom	= $this->_custom;

		if($custom){
			$this->value	= $this->render_by_custom($this->value);

			$items	= array_replace($items, ['__custom'=>$custom->title]);
			$custom	= $custom->update_args(['title'=>'', 'name'=>$this->name])->wrap('span');
		}

		$items	= $this->render_options($items);

		if($this->is('select')){
			if($custom){
				$this->after	.= '&emsp;'.$custom;
			}

			return $this->tag('select', [], implode('', $items));
		}else{
			if($custom){
				$items[]	= $custom;
			}

			$dir	= $this->direction ?: ($this->sep ? '' : 'row');
			$sep	= $this->sep ?? ($dir ? '' : '&emsp;');
			$class	= [($dir ? 'direction-'.$dir : ''), ($this->type == 'checkbox' ? 'mu-checkbox' : '')];

			return wpjam_wrap_tag(implode($sep, $items), 'span', [
				'id'	=> $this->id.'_options',
				'class'	=> $class,
				'data'	=> ['max_items'=>$this->max_items]
			]);
		}
	}

	protected function render_options($options){
		$field	= $this->_custom;
		$items	= [];

		foreach($options as $opt => $label){
			$attr	= $data = $class = [];

			if(is_array($label)){
				$opt_arr	= $label;
				$label		= array_pull($opt_arr, 'label', array_pull($opt_arr, 'title'));
				$bool_attr	= ['disabled', 'required', 'hidden'];

				foreach($opt_arr as $k => $v){
					if(is_numeric($k)){
						if(in_array($v, $bool_attr)){
							$attr[$v]	= $v;
						}
					}elseif(in_array($k, $bool_attr)){
						if($v){
							$attr[$k]	= $k;
						}
					}elseif($k == 'show_if'){
						$show_if	= $this->parse_show_if($v);

						if($show_if){
							$class[]	= 'show_if';
							$data		+= ['show_if'=>$show_if];
						}
					}elseif($k == 'class'){
						$class	= array_merge($class, array_wrap($v));
					}elseif($k == 'options'){
						$attr[$k]	= $v;
					}elseif(!is_array($v)){
						$data[$k]	= $v;
					}
				}
			}

			if($opt === '__custom'){
				$checked	= $field && !is_null($field->value);
			}else{
				if($this->is('checkbox')){
					$checked	= is_array($this->value) && in_array($opt, $this->value);
				}else{
					$checked	= $this->value ? ($opt == $this->value) : !$opt;
				}
			}

			if($this->is('select')){
				$attr		= $attr+['data'=>$data, 'class'=>$class];
				$sub_opts	= array_pull($attr, 'options');

				if(isset($sub_opts)){
					if($sub_opts){
						$sub_opts	= implode('', $this->render_options($sub_opts));
						$items[]	= wpjam_tag('optgroup', array_merge($attr, ['label'=>$label]), $sub_opts);
					}
				}else{
					$items[]	= wpjam_tag('option', array_merge($attr, ['value'=>$opt, 'selected'=>$checked]), $label);
				}
			}else{
				$class[]	= $checked ? 'checked' : '';
				$opt_id		= $this->id.'_'.$opt;
				$attr		= array_merge($attr, ['required'=>false, 'checked'=>$checked, 'id'=>$opt_id, 'value'=>$opt]);
				$items[]	= $this->tag('input', $attr)->after($label)->wrap('label', ['data'=>$data, 'class'=>$class, 'id'=>'label_'.$opt_id, 'for'=>$opt_id]);
			}
		}

		return $items;
	}
}

class WPJAM_Image_Field extends WPJAM_Field{
	protected function prepare_schema(){
		if($this->is('uploader')){
			return ['type'=>'string'];
		}elseif($this->is('img')){
			if($this->item_type != 'url'){
				return ['type'=>'integer'];
			}
		}

		return ['type'=>'string', 'format'=>'uri'];
	}

	public function prepare_value($value){
		return $this->is('uploader') ? $value : wpjam_get_thumbnail($value, $this->size);
	}

	protected function render_component(){
		if(!current_user_can('upload_files')){
			$this->disabled	= 'disabled';
		}

		if($this->is('uploader')){
			$class		= ['hide-if-no-js', 'plupload'];
			$mime_types	= $this->mime_types ?: ['title'=>'图片', 'extensions'=>'jpeg,jpg,gif,png'];
			$mime_types	= wp_is_numeric_array($mime_types) ? $mime_types : [$mime_types];
			$btn_id		= 'plupload_button__'.$this->key;
			$btn_text	= $this->button_text ?: __('Select Files');
			$btn_attr	= ['type'=>'button', 'class'=>'button', 'id'=>$btn_id, 'value'=>$btn_text];
			$container	= 'plupload_container__'.$this->key;
			$plupload	= [
				'browse_button'		=> $btn_id,
				'container'			=> $container,
				'file_data_name'	=> $this->key,
				'filters'			=> [
					'mime_types'	=> $mime_types,
					'max_file_size'	=> (wp_max_upload_size()?:0).'b'
				],
				'multipart_params'	=> [
					'_ajax_nonce'	=> wp_create_nonce('upload-'.$this->key),
					'action'		=> 'wpjam-upload',
					'file_name'		=> $this->key,
				]
			];

			$data	= ['key'=>$this->key, 'plupload'=>&$plupload];
			$title	= $this->value ? array_slice(explode('/', $this->value), -1)[0] : '';
			$args	= ['type'=>'hidden', 'class'=>($this->value ? 'hidden' : '')];
			$tag	= $this->tag('input', $args)->before('input', $btn_attr)->after($this->render_query($title));

			if($this->drap_drop && !wp_is_mobile()){
				$dd_id		= 'plupload_drag_drop__'.$this->key;
				$plupload	+= ['drop_element'=>$dd_id];
				$class[]	= 'drag-drop';

				$tag->wrap('p', ['drag-drop-buttons'])
				->before('p', [], _x('or', 'Uploader: Drop files here - or - Select Files'))
				->before('p', ['drag-drop-info'], __('Drop files to upload'))
				->wrap('div', ['drag-drop-inside'])
				->wrap('div', ['id'=>$dd_id, 'class'=>'plupload-drag-drop']);
			}

			$progress	= wpjam_tag('div', ['progress', 'hidden'], ['div', ['percent']])->append('div', ['bar']);

			return $tag->after($progress)->wrap('div', ['id'=>$container, 'class'=>$class, 'data'=>$data]);
		}elseif($this->is('img')){
			$size	= $this->size ?: '600x0';
			$size	= wpjam_constrain_size($size, 600, 600);
			$attr	= array_filter(['width'=>(int)($size['width']/2), 'height'=>(int)($size['height']/2)]);
			$data	= ['item_type'=>$this->item_type, 'thumb_args'=>wpjam_get_thumbnail_args($size)];
			$img	= $this->value ? wpjam_get_thumbnail($this->value, $size) : '';
			$img	= wpjam_tag('img', $attr+['src'=>$img, 'class'=>($img ? '' : 'hidden')]);
			$button	= wpjam_tag('span', ['wp-media-buttons-icon'])->after($this->button_text ?: '添加图片')->wrap('button', ['button', 'add_media'])->wrap('div', ['wp-media-buttons']);

			return $this->tag('input', ['type'=>'hidden'])->before($img.$button.self::get_icon('del_img'), 'div', ['class'=>'wpjam-img', 'data'=>$data]);
		}else{
			$title	= '选择'.($this->is('image') ? '图片' : '文件');

			return $this->tag('input', ['type'=>'url'])->after($title, 'a', ['class'=>'button', 'data'=>['item_type'=>$this->item_type]])->wrap('div', ['wpjam-file']);
		}
	}
}

class WPJAM_MU_Field extends WPJAM_Field{
	public function __get($key){
		$value	= parent::__get($key);

		if(is_null($value)){
			if($key == '_item'){
				$args	= array_except($this->get_args(), ['required', 'show_in_rest']);	// 提交时才验证

				$args['type']	= $this->item_type;
				$args['class']	= $this->item_class;

				if($this->item_type != 'select' && $this->direction == 'row' && is_null($args['class'])){
					$args['class']	= 'medium-text';
				}

				return $this->_item = self::create($args);
			}elseif($key == '_fields'){
				return $this->_fields = WPJAM_Fields::create($this->fields, $this);
			}
		}

		return $value;
	}

	protected function prepare_schema(){
		if($this->is('mu-fields')){
			$items	= $this->get_schema_by_fields();
		}elseif($this->is('mu-text')){
			$items	= $this->get_schema_by_item();
		}else{
			$items	= ['type'=>'string', 'format'=>'uri'];

			if($this->is('mu-img') && $this->item_type != 'url'){
				$items	= ['type'=>'integer'];
			}
		}

		return ['type'=>'array', 'items'=>$items];
	}

	public function prepare_value($value){
		if($this->is('mu-fields')){
			return $this->map($value, 'prepare_value_by_fields');
		}elseif($this->is('mu-text')){
			return $this->map($value, 'prepare_value_by_item');
		}else{
			if($value && is_array($value)){
				$value	= wpjam_map($value, 'wpjam_get_thumbnail', $this->size);
				$value	= array_filter($value);
			}

			return $value;
		}
	}

	public function validate_value($value){
		if($value){
			$value	= is_array($value) ? filter_deep($value, 'is_populated') : wpjam_json_decode($value);
		}

		if(!$value || is_wp_error($value)){
			return $this->required ? null : [];
		}

		$value	= array_values($value);

		if($this->is('mu-fields')){
			return $this->map($value, 'validate_value_by_fields');
		}elseif($this->is('mu-text')){
			return $this->map($value, 'validate_value_by_item');
		}

		return $value;
	}

	protected function render_component(){
		if($this->is(['mu-img', 'mu-image', 'mu-file'])){
			if(!current_user_can('upload_files')){
				$this->disabled	= 'disabled';
			}

			$data	= ['item_type'=>$this->item_type];
		}

		$value	= $this->value ?: [];

		if(!is_blank($value)){
			if(is_array($value)){
				$value	= filter_deep($value, 'is_populated');
				$value	= array_values($value);
			}else{
				$value	= (array)$value;
			}
		}

		$last		= count($value);
		$value[]	= null;

		if($this->is('mu-text')){
			if(count($value) <= 1 && $this->direction == 'row' && $this->item_type != 'select'){
				$last ++;

				$value[]	= null;
			}
		}elseif($this->is('mu-img')){
			$this->direction	= 'row';
		}

		if(!$this->is(['mu-fields', 'mu-img']) && $this->max_items && $last >= $this->max_items){
			unset($value[$last]);

			$last --;
		}

		$args	= ['id'=>'', 'name'=>$this->name.'[]'];
		$items	= [];
		$text	= $this->button_text ?: '添加'.(($this->title && mb_strwidth($this->title) <= 8) ? $this->title : '选项');

		foreach($value as $i => $item){
			$args['value']	= $item;

			if($this->is('mu-fields')){
				if($last === $i){
					$item	= $this->render_by_fields(['i'=>'{{ data.i }}']);
					$item	= wpjam_tag('script', ['type'=>'text/html', 'id'=>'tmpl-'.md5($this->name)], $item);
				}else{
					$item	= $this->render_by_fields(['i'=>$i, 'item'=>$item]);
				}
			}elseif($this->is('mu-text')){
				if($this->item_type == 'select' && $last === $i){
					$options	= $this->get_arg_by_item('options');

					if(!in_array('', array_keys($options))){
						$args['options']	= array_replace([''=>['title'=>'请选择', 'disabled', 'hidden']], $options);
					}
				}

				$item	= $this->archive_by_item()->update_args($args)->render();

				$this->restore_by_item();
			}elseif($this->is('mu-img')){
				$img	= $item ? wpjam_get_thumbnail($item) : '';
				$thumb	= wpjam_get_thumbnail($item, [200, 200]);
				$item	= $this->tag('input', array_merge($args, ['type'=>'hidden']));

				if($img){
					$item->before('a', ['href'=>$img, 'class'=>'wpjam-modal'], ['img', ['src'=>$thumb]]);
				}
			}else{
				$item	= $this->tag('input', array_merge($args, ['type'=>'url']));
			}

			$icon	= ($this->direction == 'row' ? 'del_icon' : 'del_btn').','.$this->sortable;
			$item	.= self::get_icon($icon);

			if($last === $i){
				$class	= 'button';

				if($this->is('mu-text')){
					$data	= [];
				}elseif($this->is('mu-fields')){
					$data	= ['i'=>$i, 'tmpl_id'=>md5($this->name)];
				}elseif($this->is('mu-img')){
					$data	+= ['thumb_args'=>wpjam_get_thumbnail_args([200, 200])];
					$text	= '';
					$class	= 'dashicons dashicons-plus-alt2';
				}else{
					$data	+= ['title'=>($this->item_type == 'image' ? '选择图片' : '选择文件')];
					$text	= $data['title'].'[多选]';
				}

				$item	.= wpjam_tag('a', ['class'=>'new-item '.$class, 'data'=>$data], $text);
			}

			$items[]	= wpjam_tag('div', ['mu-item', ($this->group ? 'field-group' : '')], $item);
		}

		return wpjam_wrap_tag(implode("\n", $items), 'div', [
			'id'	=> $this->id,
			'class'	=> [$this->type, $this->sortable, 'direction-'.($this->direction ?: 'column')],
			'data'	=> ['max_items'=>$this->max_items]
		]);
	}
}

class WPJAM_FieldSet extends WPJAM_Field{
	public function __get($key){
		if($key == 'fieldset_type' && $this->_data_type){
			return 'array';
		}

		$value	= parent::__get($key);

		if(is_null($value) && $key == '_fields'){
			if($this->is('fields')){
				$this->fields	= wpjam_parse_fields($this->fields, $this->fields_type);
			}

			return $this->_fields = WPJAM_Fields::create($this->fields, $this);
		}

		return $value;
	}

	protected function prepare_schema(){
		if($this->_data_type){
			return parent::prepare_schema();
		}

		return $this->get_schema_by_fields();
	}

	public function validate_value($value){
		if($this->_data_type){
			return parent::validate_value($value);
		}

		return $this->validate_value_by_fields($value);
	}

	public function render($args=[], $to_string=true){
		$fields	= $this->render_by_fields($args);
		$fields	= $this->after_render($fields);

		if($this->summary){
			$fields->before([$this->summary, 'strong'], 'summary')->wrap('details');
		}

		$class	= array_wrap($this->class);
		$data	= $this->data ?: [];

		if($this->is('fieldset', true) && $this->group){
			$class[]	= 'field-group';
		}

		if($this->fieldset_type == 'array'){
			$data['key']	= $this->key;

			if($this->_data_type){
				$data['value']	= $this->render_value_by_data_type($this->value_callback($args)) ?: new StdClass;
			}
		}

		if($class || $data || $this->style){
			$fields->wrap('div', ['data'=>$data, 'class'=>$class, 'style'=>$this->style]);
		}

		return $to_string ? (string)$fields : $fields;
	}

	public static function parse_size_fields($fields){
		$parsed	= [];

		foreach(['width', 'x', 'height'] as $key){
			if($key == 'x'){
				$parsed['x']	= ['type'=>'view',	'value'=>self::get_icon('multiply')];
			}else{
				$field	= $fields[$key] ?? [];
				$field	= wp_parse_args($field, ['type'=>'number', 'class'=>'small-text']);
				$key	= array_pull($field, 'key') ?: $key;

				$parsed[$key]	= $field;
			}
		}

		return $parsed;
	}
}

class WPJAM_Fields extends WPJAM_Attr{
	private $fields		= [];
	private $creator	= null;

	private function __construct($fields, $creator=null){
		$this->fields	= $fields ?: [];
		$this->creator	= $creator;
	}

	public function	__call($method, $args){
		$data	= wpjam_array();

		foreach($this->fields as $field){
			if(in_array($method, ['get_schema', 'get_defaults', 'get_show_if_values'])){
				if(!$field->editable){
					continue;
				}
			}elseif($method == 'prepare'){
				if(!$field->show_in_rest){
					continue;
				}
			}

			if($field->is('fieldset') && !$field->_data_type){
				$value	= wpjam_try([$field, $method.'_by_fields'], ...$args);
			}else{
				if($method == 'prepare'){
					$value	= $field->pack($field->prepare(...$args));
				}elseif($method == 'get_defaults'){
					$value	= $field->pack($field->default);
				}elseif($method == 'get_show_if_values'){ // show_if 判断基于key,并且array类型的fieldset的key是 ${key}__{$sub_key}
					$item	= wpjam_call([$field, 'validate'], $field->unpack($args[0]));
					$value	= [$field->key => is_wp_error($item) ? null : $item];
				}elseif(in_array($method, ['get_schema', 'prepare_value', 'validate_value'])){
					$name	= array_value_last($field->names);

					if($method == 'get_schema'){
						$value	= [$name => $field->_schema];
					}else{
						$item	= $args[0][$name] ?? null;
						$value	= is_null($item) ? [] : [$name => wpjam_try([$field, $method], $item)];
					}
				}else{
					$value	= wpjam_try([$field, $method], ...$args);
				}
			}

			$data->merge($value);
		}

		return $data->to_array();
	}

	public function	__invoke($args=[]){
		return $this->render($args);
	}

	public function validate($values=null){
		$values	= $values ?? wpjam_get_post_parameter();

		if(!$this->fields){
			return $values;
		}

		if($this->creator && isset($this->creator->_if_values)){
			$if_values	= $this->creator->_if_values;
			$if_show	= $this->creator->_if_show;
		}else{
			$if_values	= $this->get_show_if_values($values);
			$if_show	= true;
		}

		$data	= wpjam_array();

		foreach($this->fields as $field){
			if(!$field->editable){
				continue;
			}

			$show	= $if_show ? wpjam_show_if($if_values, $field->show_if) : false;

			if($field->is('fieldset') && $field->fieldset_type != 'array'){
				$field->_if_values	= $if_values;
				$field->_if_show	= $show;

				$value	= $field->validate_by_fields($values);
			}else{
				if($show){
					$value	= $field->unpack($values);
					$value	= $field->validate($value, true);
				}else{	// 第一次获取的值都是经过 json schema validate 的,可能存在 show_if 的字段在后面
					$value	= $if_values[$field->key] = null;
				}

				$value	= $field->pack($value);
			}

			$data->merge($value);
		}

		return $data->to_array();
	}

	public function get_schema(){
		return ['type'=>'object', 'properties'=>$this->__call('get_schema',[])];
	}

	public function render($args=null, $to_string=false){
		$fields		= [];
		$grouped	= [];
		$pre_group	= '';
		$creator	= '';

		$sep	= "\n";
		$args	= $args ?? $this->get_args();
		$args	= wpjam_array($args);

		if($this->creator){
			$creator	= $this->creator->type;

			$type	= '';
			$tag	= 'div';

			if($creator == 'fields'){
				$sep	= $this->creator->sep ?? ' ';
				$tag	= '';
			}else{
				$args->push_arg('wrap_class', 'sub-field');

				if($creator == 'mu-fields'){
					$i		= $args->pull('i');
					$item	= $args->pull('item');
				}
			}
		}else{
			$type	= $args->pull('fields_type', 'table');

			if(isset($args->wrap_tag)){
				$tag	= $args->pull('wrap_tag');
			}else{
				$map	= ['table'=>'tr', 'list'=>'li'];
				$tag	= $map[$type] ?? $type;
			}
		}

		foreach($this->fields as $field){
			if($field->show_admin_column === 'only'){
				continue;
			}

			$field->archive();

			if($creator == 'mu-fields'){
				$field->affix($this->creator, $i, $item);
			}

			$wrapped	= $field->wrap($tag, $args);

			$field->restore();

			if($creator && $creator != 'fields'){
				if($field->group != $pre_group){
					$fields[]	= $this->group($grouped, $pre_group);
					$grouped	= [];
					$pre_group	= $field->group;
				}

				$grouped[]	= $wrapped;
			}else{
				$fields[]	= $wrapped;
			}
		}

		if($grouped){
			$fields[]	= $this->group($grouped, $pre_group);
		}

		$fields	= implode($sep, array_filter($fields));

		if($type == 'table'){
			$fields	= wpjam_tag('table', ['cellspacing'=>0, 'class'=>'form-table'], [$fields, 'tbody']);
		}elseif($type == 'list'){
			$fields	= wpjam_tag('ul', [], $fields);
		}

		return $to_string ? (string)$fields : $fields;
	}

	protected function group($grouped, $pre_group){
		$grouped	= implode('', $grouped);

		return $pre_group ? wpjam_tag('div', ['field-group'], $grouped) : $grouped;
	}

	public function callback($args=[]){
		return $this->render($args);
	}

	public static function create($fields, $creator=null){
		$objects	= [];
		$prefix		= '';
		$propertied	= false;

		if($creator){
			if($creator->is('fieldset')){
				if($creator->fieldset_type == 'array'){
					$propertied	= true;
				}else{
					if($creator->prefix){
						$prefix	= $creator->prefix;
						$prefix	= $prefix === true ? $creator->key : $prefix;
					}
				}
			}elseif($creator->is('mu-fields')){
				$propertied	= true;
			}

			$sink_attrs	= wp_array_slice_assoc($creator, ['readonly', 'disabled']);
		}

		foreach((array)$fields as $key => $field){
			if(isset($field['type']) && $field['type'] == 'fields'){
				$field['prefix']	= $prefix;
			}else{
				$key	= ($prefix ? $prefix.'_' : '').$key;
			}

			$object	= WPJAM_Field::create($field, $key);

			if(!$object){
				continue;
			}

			if($propertied){
				if(count($object->names) > 1){
					trigger_error($creator->_title.'子字段不允许[]模式:'.$object->name);

					continue;
				}

				if($object->is('fieldset', true) || $object->is('mu-fields')){
					trigger_error($creator->_title.'子字段不允许'.$object->type.':'.$object->name);

					continue;
				}
			}

			$objects[$key]	= $object;

			if($creator){
				if($creator->is('fieldset')){
					if($creator->fieldset_type == 'array'){
						$object->affix($creator);
					}else{
						if(!isset($object->show_in_rest)){
							$object->show_in_rest	= $creator->show_in_rest;
						}
					}
				}

				$object->update_args($sink_attrs);
			}
		}

		return new self($objects, $creator);
	}
}