HEX
Server: nginx/1.18.0
System: Linux iZj6c1ieg2jrpk1z5tzi19Z 6.3.9-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jun 21 22:18:40 EDT 2023 x86_64
User: www (1001)
PHP: 8.2.4
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.cytocare.cn/wp-content/plugins/wpjam-basic/includes/class-wpjam-model.php
<?php
trait WPJAM_Instance_Trait{
	use WPJAM_Call_Trait;

	public static function instance_exists($name){
		return wpjam_get_instance(self::get_called(), $name) ?: false;
	}

	public static function add_instance($name, $object){
		return wpjam_add_instance(self::get_called(), $name, $object);
	}

	protected static function create_instance(...$args){
		return new static(...$args);
	}

	public static function instance(...$args){
		if(count($args) == 2 && is_callable($args[1])){
			return wpjam_get_instance(self::get_called(), ...$args);
		}

		$args	= wpjam_filter($args, fn($v)=> !is_null($v));
		$name	= $args ? implode(':', $args) : 'singleton';

		return self::instance_exists($name) ?: self::add_instance($name, static::create_instance(...$args));
	}

	protected static function validate_data($data, $id=0){
		return true;
	}

	protected static function sanitize_data($data, $id=0){
		return $data;
	}

	public static function prepare_data($data, $id=0){
		$result	= static::validate_data($data, $id);
		$result	= wpjam_throw_if_error($result);

		return static::sanitize_data($data, $id);
	}

	public static function before_delete($id){
		if(method_exists(get_called_class(), 'is_deletable')){
			$object	= static::get_instance($id);
			$result	= $object ? $object->is_deletable() : true;
			$result	= wpjam_throw_if_error($result);

			if(!$result){
				wpjam_throw('indelible', '不可删除');
			}
		}
	}
}

abstract class WPJAM_Model implements ArrayAccess, IteratorAggregate{
	use WPJAM_Instance_Trait;

	protected $_id;
	protected $_data	= [];

	public function __construct($data=[], $id=null){
		if($id){
			$this->_id		= $id;
			$this->_data	= $data ? array_diff_assoc($data, static::get($id)) : [];
		}else{
			$key	= static::get_primary_key();
			$exist	= isset($data[$key]) ? static::get($data[$key]) : null;

			if($exist){
				$this->_id		= $data[$key];
				$this->_data	= array_diff_assoc($data, $exist);
			}else{
				$this->_data	= $data;
			}
		}
	}

	public function __get($key){
		return wpjam_exists($this->get_data(), $key) ? $this->get_data()[$key] : $this->meta_get($key);
	}

	public function __isset($key){
		return wpjam_exists($this->get_data(), $key) || $this->meta_exists($key);
	}

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

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

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

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

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

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

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

	public function get_primary_id(){
		return $this->get_data(static::get_primary_key());
	}

	public function get_data($key=''){
		$data	= is_null($this->_id) ? [] : static::get($this->_id);
		$data	= array_merge($data, $this->_data);

		return $key ? ($data[$key] ?? null) : $data;
	}

	public function set_data($key, $value){
		if(!is_null($this->_id) && static::get_primary_key() == $key){
			trigger_error('不能修改主键的值');
		}else{
			$this->_data[$key]	= $value;
		}

		return $this;
	}

	public function unset_data($key){
		$this->_data[$key]	= null;
	}

	public function reset_data($key=''){
		if($key){
			unset($this->_data[$key]);
		}else{
			$this->_data	= [];
		}
	}

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

	public function save($data=[]){
		$meta_input	= self::get_meta_type() ? wpjam_pull($data, 'meta_input') : null;
		$data		= array_merge($this->_data, $data);

		if($this->_id){
			$data	= wpjam_except($data, static::get_primary_key());
			$result	= $data ? static::update($this->_id, $data) : false;
		}else{
			$result	= static::insert($data);
		}

		if(!is_wp_error($result)){
			if(!$this->_id){
				$this->_id	= $result;
			}

			if($this->_id && $meta_input){
				$this->meta_input($meta_input);
			}

			$this->reset_data();
		}

		return $result;
	}

	public function meta_get($key){
		return wpjam_get_metadata(self::get_meta_type(), $this->_id, $key);
	}

	public function meta_exists($key){
		return metadata_exists(self::get_meta_type(), $this->_id, $key);
	}

	public function meta_input(...$args){
		if($args){
			return wpjam_update_metadata(self::get_meta_type(), $this->_id, ...$args);
		}
	}

	public static function find($id){
		return static::get_instance($id);
	}

	public static function get_instance($id){
		if($id){
			return static::instance($id, fn($id)=> static::get($id) ? new static([], $id) : null);
		}
	}

	public static function get_handler(){
		$handler	= wpjam_get_handler(self::get_called());

		if(!$handler && property_exists(get_called_class(), 'handler')){
			return static::$handler;
		}

		return $handler;
	}

	public static function set_handler($handler){
		return wpjam_register_handler(self::get_called(), $handler);
	}

	public static function insert($data){
		return wpjam_catch([get_called_class(), 'insert_by_handler'], static::prepare_data($data));
	}

	public static function update($id, $data){
		return wpjam_catch([get_called_class(), 'update_by_handler'], $id, static::prepare_data($data, $id));
	}

	public static function delete($id){
		return wpjam_catch([get_called_class(), 'before_delete'], $id) ?: static::delete_by_handler($id);
	}

	public static function delete_multi($ids){
		try{
			array_walk($ids, fn($id)=> static::before_delete($id));

			return static::delete_multi_by_handler($ids);
		}catch(Exception $e){
			return wpjam_catch($e);
		}
	}

	public static function insert_multi($data){
		return wpjam_catch([get_called_class(), 'insert_multi_by_handler'], array_map(fn($v)=> static::prepare_data($v), $data));
	}

	public static function validate_by_field($value, $field){
		$result	= static::get($value);

		if(!$result || is_wp_error($result)){
			return $result ?: new WP_Error('invalid_id', [$field->_title]);
		}

		return $value;
	}

	public static function get_actions(){
		return [
			'add'		=> ['title'=>'新建',	'dismiss'=>true],
			'edit'		=> ['title'=>'编辑'],
			'delete'	=> ['title'=>'删除',	'direct'=>true, 'confirm'=>true,	'bulk'=>true,	'order'=>1],
		];
	}

	public static function __callStatic($method, $args){
		if(in_array($method, ['item_callback', 'render_item'])){
			return $args[0];
		}

		try_remove_suffix($method, '_by_handler');

		return wpjam_call_handler(static::get_handler(), $method, ...$args);
	}
}

class WPJAM_Handler{
	public static function call($name, $method, ...$args){
		$method	= strtolower($method);
		$object	= is_object($name) ? $name : self::get($name);

		if(!$object){
			return new WP_Error('undefined_handler');
		}

		if($object instanceof WPJAM_DB){
			if($method == 'query'){
				if(!$args){
					return $object;
				}
			}elseif($method == 'query_items'){
				if(is_array($args[0])){
					$method		= 'query';
					$args[1]	??= 'array';
				}
			}elseif(str_starts_with($method, 'cache_')){
				$method	.= '_force';
			}
		}

		if(in_array($method, [
			'get_primary_key',
			'get_meta_type',
			'get_searchable_fields',
			'get_filterable_fields'
		])){
			return $object->{substr($method, 4)};
		}elseif(in_array($method, [
			'set_searchable_fields',
			'set_filterable_fields'
		])){
			return $object->{substr($method, 4)}	= $args[0];
		}

		try{
			if(str_ends_with($method, '_multi') && !method_exists($object, $method)){
				$method	= substr($method, 0, -6);

				array_walk($args[0], fn($item)=> wpjam_try([$object, $method], $item));

				return true;
			}

			$method	= ['get_ids'=>'get_by_ids', 'get_all'=>'get_results'][$method] ?? $method;
			$cb		= [$object, $method];

			return is_callable($cb) ? $cb(...$args) : new WP_Error('undefined_method', [$method]);
		}catch(Exception $e){
			return wpjam_catch($e);
		}
	}

	public static function get($name, $args=null){
		if($name){
			if(is_array($name)){
				$args	= $name;
				$name	= wpjam_pull($args, 'name') ?: md5(serialize($args));
			}

			return wpjam_get_item('handler', $name) ?: ($args ? self::create($name, $args) : null);
		}
	}

	public static function create($name, $args=[]){
		if(is_array($name)){
			$args	= $name;
			$name	= wpjam_pull($args, 'name');
		}

		if(is_object($args)){
			return $name ? wpjam_set_item('handler', $name, $args) : null;
		}

		if(!empty($args['table_name'])){
			$name	= $name ?: $args['table_name'];

			return self::create($name, new WPJAM_DB($args));
		}

		if(!empty($args['option_name'])){
			$name	= $name ?: $args['option_name'];

			if(!empty($args['setting_name']) || !empty($args['items_field'])){
				$args['setting_name']	??= wpjam_pull($args, 'items_field');	// 兼容

				$args['items_type']		= 'setting';
			}else{
				$args['items_type']		= 'option';
			}
		}

		if(!empty($args['items_type']) || array_all(['get_items', 'update_items'], fn($method)=> !empty($args[$method]))){	// 推荐
			if(!empty($args['items_type'])){
				$args['type']	= wpjam_pull($args, 'items_type');
			}

			return self::create($name, new WPJAM_Items($args));
		}

		if(!empty($args['items_model'])){	// 不建议使用了
			$args	= wp_parse_args($args, wpjam_fill(['get_items', 'update_items'], fn($k)=> [$args['items_model'], $k]));

			return self::create($name, new WPJAM_Items($args));
		}

		if(wpjam_pull($args, 'type') == 'option_items'){	// 不建议使用
			return self::create($name, new WPJAM_Items(wp_parse_args($args, ['type'=>'option', 'option_name'=>$name])));
		}
	}
}

class WPJAM_DB extends WPJAM_Args{
	protected $meta_query	= null;
	protected $query_vars	= [];
	protected $where		= [];

	public function __construct($table, $args=[]){
		if(is_array($table)){
			$args	= $table;
			$table	= $args['table_name'];
		}

		$this->args	= wp_parse_args($args, [
			'table'			=> $table,
			'primary_key'	=> 'id',
			'cache'			=> true,
			'cache_group'	=> $table,
			'cache_time'	=> DAY_IN_SECONDS,
			'field_types'	=> [],
		]);

		foreach(['group_cache_key', 'lazyload_key', 'filterable_fields', 'searchable_fields'] as $key){
			$this->$key	= wpjam_array($this->$key);
		}

		if($this->cache_key	== $this->primary_key){
			$this->cache_key	= '';
		}elseif($this->cache_key){
			$this->group_cache_key	= [...$this->group_cache_key, $this->cache_key];
		}

		$this->clear();
	}

	public function __get($key){
		if(in_array($key, array_keys($this->query_vars))){
			return $this->query_vars[$key];
		}elseif(in_array($key, ['last_error', 'last_query', 'insert_id'])){
			return $GLOBALS['wpdb']->$key;
		}

		return parent::__get($key);
	}

	public function __call($method, $args){
		if(try_remove_prefix($method, 'where_')){
			if($method == 'fragment'){
				$this->where(null, $args[0]);
			}elseif(in_array($method, ['any', 'all'])){
				$value	= '';

				if($args[0] && is_array($args[0])){
					$where	= wpjam_map($args[0], fn($v, $k)=> $this->where($k, $v, 'value'));
					$value	= $this->parse_where($where, ($method == 'any' ? 'OR' : 'AND'));
				}

				return (!isset($args[1]) || $args[1] == 'object') ? $this->where_fragment($value) : $value;
			}elseif(isset($args[1])){
				$compare	= $this->get_operator()[$method] ?? '';	

				if($compare){
					$this->where($args[0], ['value'=>$args[1], 'compare'=>$compare]);
				}
			}

			return $this;
		}elseif(in_array($method, array_keys($this->query_vars)) || in_array($method, ['search', 'order_by', 'group_by'])){
			$key	= $method;
			$value	= $args ? $args[0] : ($key == 'found_rows' ? true : null);

			if(!is_null($value)){
				if($key == 'order'){
					$value	= strcasecmp($value, 'ASC') ? 'DESC' : 'ASC';
				}elseif(in_array($key, ['limit', 'offset'])){
					$value	= (int)$value;
				}else{
					$key	= ['search'=>'search_term', 'order_by'=>'orderby', 'group_by'=>'groupby'][$key] ?? $key;
				}

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

			return $this;
		}elseif(in_array($method, ['get_col', 'get_var', 'get_row'])){
			if($method != 'get_col'){
				$this->limit(1);
			}

			$field	= $args[0] ?? '';
			$args	= [$this->get_sql($field), ...($method == 'get_row' ? [ARRAY_A] : [])];

			return $GLOBALS['wpdb']->$method(...$args);
		}elseif(try_remove_suffix($method, '_by_db')){
			return $GLOBALS['wpdb']->$method(...$args);
		}elseif(str_contains($method, '_meta')){
			$object	= wpjam_get_meta_type_object($this->meta_type);

			if($object){
				return $object->$method(...$args);
			}
		}elseif(str_starts_with($method, 'cache_')){
			if(!try_remove_suffix($method, '_force') && !$this->cache){
				return false;
			}

			return (WPJAM_Cache::create($this))->$method(...$args);
		}elseif(try_remove_suffix($method, '_last_changed')){
			$key	= 'last_changed';

			if($this->group_cache_key){
				$vars	= array_shift($args);

				if($vars && is_array($vars)){
					$vars	= wpjam_slice($vars, $this->group_cache_key);

					if($vars && count($vars) == 1 && !is_array(reset($vars))){
						$key	.= ':'.array_key_first($vars).':'.reset($vars);
					}
				}
			}

			if($method == 'get'){
				$value	= $this->cache_get($key);

				if(!$value){
					$value	= microtime();

					$this->cache_set($key, $value);
				}

				return $value;
			}elseif($method == 'delete'){
				$this->cache_delete($key);
			}
		}

		return new WP_Error('undefined_method', [$method]);
	}

	protected function get_operator(){
		return [
			'not'		=> '!=',
			'lt'		=> '<',
			'lte'		=> '<=',
			'gt'		=> '>',
			'gte'		=> '>=',
			'in'		=> 'IN',
			'not_in'	=> 'NOT IN',
			'like'		=> 'LIKE',
			'not_like'	=> 'NOT LIKE',
		];
	}

	public function clear(){
		$this->where		= [];
		$this->meta_query	= null;
		$this->query_vars	= [
			'found_rows'	=> false,
			'limit'			=> 0,
			'offset'		=> 0,
			'orderby'		=> null,
			'order'			=> null,
			'groupby'		=> null,
			'having'		=> null,
			'search_term'	=> null,
		];
	}

	public function find_by($field, $value, $order='ASC', $method='get_results'){
		$value	= is_array($value) ? array_map(fn($v)=> $this->format($v, $field), $value) : $this->format($value, $field);
		$value	= is_array($value) ? 'IN ('.implode(',', $value).')' : '= '.$value;
		$sql	= "SELECT * FROM `{$this->table}` WHERE `{$field}` {$value}".($order ? " ORDER BY `{$this->primary_key}` {$order}" : '');

		return $GLOBALS['wpdb']->$method($sql, ARRAY_A);
	}

	public function find_one_by($field, $value, $order=''){
		return $this->find_by($field, $value, $order, 'get_row');
	}

	public function find_one($id){
		return $this->find_one_by($this->primary_key, $id);
	}

	public function get($id){
		$this->load_pending();

		if(!$id){
			return [];
		}

		$result	= $this->cache_get($id);

		if($result === false){
			$result	= $this->find_one($id);
			$time	= $result ? $this->cache_time : 60;

			$this->cache_set($id, $result, $time);
		}

		return $result;
	}

	public function get_one_by($field, $value, $order='ASC'){
		$items	= $this->get_by($field, $value, $order);

		return $items ? reset($items) : [];
	}

	public function get_by($field, $value, $order='ASC'){
		if($field == $this->primary_key){
			return $this->get($value);
		}

		if($this->group_cache_key && in_array($field, $this->group_cache_key)){
			$this->load_pending($field, $order);

			return $this->query([$field=>$value, 'order'=>$order], 'items');
		}

		return $this->find_by($field, $value, $order);
	}

	public function get_by_values($field, $values, $order='ASC'){
		$values	= array_filter(array_unique($values));

		if(!$values){
			return [];
		}

		if($field == $this->primary_key){
			return $this->get_by_ids($values);
		}

		if(!$this->group_cache_key || !in_array($field, $this->group_cache_key)){
			return $this->find_by($field, $values, $order);
		}

		$data	= $ids = $uncache = [];

		foreach($values as $v){
			$result	= $this->query([$field=>$v, 'order'=>$order], 'cache');

			if($result[0] === false || !isset($result[0]['items'])){
				$uncache[$v]	= $result;
			}else{
				$data[$v]	= $result[0]['items'];
				$ids		= array_merge($ids, $data[$v]);
			}
		}

		$ids		= array_merge($ids, ($uncache ? $this->query([$field.'__in'=>array_keys($uncache), 'order'=>$order], 'ids') : []));
		$results	= array_values($this->get_by_ids($ids));
		$data		= array_map(fn($ids)=> $ids ? array_values($this->get_by_ids($ids)) : [], $data);

		foreach($uncache as $v => $result){
			$data[$v]	= wp_list_filter($results, [$field => $v]) ?: [];

			$cache[$result[1]]	= [
				'data'			=>['items'=>array_column($data[$v], $this->primary_key)],
				'last_changed'	=>$result[2]
			];
		}

		if(!empty($cache)){
			$this->cache_set_multiple($cache);
		}

		return $data;
	}

	public function cache_delete_by($field, $value, $order='ASC'){
		trigger_error('123');
		if($this->group_cache_key && in_array($field, $this->group_cache_key)){
			foreach((array)$value as $v){
				$result	= $this->query([$field=>$v, 'order'=>$order], 'cache');
				trigger_error(var_export('cache_delete_by::'.$result[1], true));
				$this->cache_delete_force($result[1]);
			}
		}
	}

	public function update_caches($keys, $primary=false){
		if($primary || !$this->cache_key){
			return $this->get_by_ids($keys);
		}else{
			return $this->get_by_values($this->cache_key, $keys);
		}
	}

	protected function load_pending($field='', $order=''){
		if($this->pending_queue){
			$queue	= $this->pending_queue;
			$key	= $this->primary_key;
			$queue	= is_array($queue) ? $queue : [$key=>$queue];
			$field	= $field ?: $key;

			if(isset($queue[$field])){
				wpjam_load_pending($queue[$field], fn($pending)=> $this->get_by_values($field, $pending, $order));
			}
		}
	}

	public function get_ids($ids){
		return $this->get_by_ids($ids);
	}

	public function get_by_ids($ids){
		if(!$ids){
			return [];
		}

		$ids	= array_filter(array_unique($ids));
		$data	= $this->cache_get_multiple($ids) ?: [];
		$data	= array_filter($data, 'is_array');
		$ids	= array_diff($ids, array_keys($data));

		if($ids){
			$results	= $this->find_by($this->primary_key, $ids);

			if($results){
				$results	= wpjam_array($results, fn($k, $v)=> $v[$this->primary_key]);

				$this->cache_set_multiple($results);
			}

			foreach($ids as $id){
				if(isset($results[$id])){
					$data[$id]	= $results[$id];
				}else{
					$this->cache_set($id, [], 5);
				}
			}
		}

		if($data){
			$this->lazyload_meta(array_keys($data));

			if($this->lazyload_key){
				wpjam_lazyload($this->lazyload_key, $data);
			}
		}

		return $data;
	}

	public function get_clauses($fields=[]){
		$distinct	= '';
		$where		= '';
		$join		= '';
		$groupby	= $this->groupby ?: '';

		if($this->meta_query){
			$sql		= $this->meta_query->get_sql($this->meta_type, $this->table, $this->primary_key, $this);
			$where		= $sql['where'];
			$join		= $sql['join'];
			$groupby	= $groupby ?: $this->table.'.'.$this->primary_key;
			$fields		= $fields ?: $this->table.'.*';
		}

		$fields		= $fields ? (is_array($fields) ? '`'.implode('`, `', esc_sql($fields)).'`' : $fields) : '*';
		$groupby	= $groupby ? ' GROUP BY '.(array_any([',', '(', '.'], fn($v)=> str_contains($groupby, $v)) ? $groupby : '`'.$groupby.'`') : '';
		$having		= $this->having ? ' HAVING '.$this->having : '';
		$orderby	= $this->orderby;
		$orderby	= (is_null($orderby) && !$groupby && !$having) ? ($this->get_arg('orderby') ?: $this->primary_key) : $orderby;

		if($orderby){
			if(is_array($orderby)){
				$parsed		= array_filter(wpjam_map($orderby, fn($v, $k)=> $this->parse_orderby($k, $v)));
				$orderby	= $parsed ? implode(', ', $parsed) : '';
			}elseif(str_contains($orderby, ',') || (str_contains($orderby, '(') && str_contains($orderby, ')'))){
				$orderby	= $orderby;
			}else{
				$order		= $this->order ?: $this->get_arg('order');
				$orderby	= $this->parse_orderby($orderby, $order);
			}

			$orderby	= $orderby ? ' ORDER BY '.$orderby : '';
		}else{
			$orderby	= '';
		}

		$limits		= $this->limit ? ' LIMIT '.$this->limit : '';
		$limits		.= $this->offset ? ' OFFSET '.$this->offset : '';
		$found_rows	= ($limits && $this->found_rows) ? 'SQL_CALC_FOUND_ROWS' : '';
		$conditions	= $this->get_conditions();
		$conditions	= (!$conditions && $where) ? '1=1' : $conditions;
		$where		= $conditions ? ' WHERE '.$conditions.$where : '';

		return compact('found_rows', 'distinct', 'fields', 'join', 'where', 'groupby', 'having', 'orderby', 'limits');
	}

	public function get_request($clauses=null){
		$clauses	= $clauses ?: $this->get_clauses();

		return sprintf("SELECT %s %s %s FROM `{$this->table}` %s %s %s %s %s %s", ...array_values($clauses));
	}

	public function get_sql($fields=[]){
		return $this->get_request($this->get_clauses($fields));
	}

	public function get_results($fields=[], $found_rows=null){
		$clauses	= $this->get_clauses($fields);
		$query_ids	= in_array($clauses['fields'], ['*', $this->table.'.*']);

		if($query_ids){
			$ids	= $this->query_ids($clauses);
		}else{
			$items	= $this->get_results_by_db($this->get_request($clauses), ARRAY_A);
		}

		if($found_rows){
			$total	= $this->find_total();
		}

		if($query_ids){
			$items	= array_values($this->get_by_ids($ids));
		}

		return $found_rows ? compact('items', 'total') : $items;
	}

	public function find($fields=[]){
		return $this->get_results($fields);
	}

	protected function query_ids($clauses){
		$clauses['fields']	= $this->table.'.'.$this->primary_key;

		return $this->get_col_by_db($this->get_request($clauses));
	}

	public function find_total(){
		return $this->get_var_by_db("SELECT FOUND_ROWS();");
	}

	protected function parse_orderby($orderby, $order){
		if($orderby == 'rand'){
			return 'RAND()';
		}elseif(preg_match('/RAND\(([0-9]+)\)/i', $orderby, $matches)){
			return sprintf('RAND(%s)', (int)$matches[1]);
		}elseif(str_ends_with($orderby, '__in')){
			return '';
			// $field	= str_replace('__in', '', $orderby);
		}

		$order	= (is_string($order) && 'ASC' === strtoupper($order)) ? 'ASC' : 'DESC';

		if($this->meta_query){
			$meta_clauses		= $this->meta_query->get_clauses();
			$primary_meta_query	= reset($meta_clauses);
			$primary_meta_key	= $primary_meta_query['key'] ?? '';

			if($orderby == $primary_meta_key || $orderby == 'meta_value'){
				if(!empty($primary_meta_query['type'])){
					return "CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']}) ".$order;
				}else{
					return "{$primary_meta_query['alias']}.meta_value ".$order;
				}
			}elseif($orderby == 'meta_value_num'){
				return "{$primary_meta_query['alias']}.meta_value+0 ".$order;
			}elseif(wpjam_exists($meta_clauses, $orderby)){
				$meta_clause	= $meta_clauses[$orderby];

				return "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']}) ".$order;
			}
		}

		if($orderby == 'meta_value_num' || $orderby == 'meta_value'){
			return '';
		}

		return '`'.$orderby.'` '.$order;
	}

	public function insert_multi($datas){	// 使用该方法,自增的情况可能无法无法删除缓存,请注意
		if(!$datas){
			return 0;
		}

		$datas	= array_filter(array_values($datas));

		$this->cache_delete_by_conditions([], $datas);

		$data		= reset($datas);
		$fields		= '`'.implode('`, `', array_keys($data)).'`';
		$updates	= implode(', ', array_map(fn($k)=> "`$k` = VALUES(`$k`)", array_keys($data)));
		$values		= implode(', ', array_map([$this, 'format'], $datas));
		$sql		= "INSERT INTO `$this->table` ({$fields}) VALUES {$values} ON DUPLICATE KEY UPDATE {$updates}";
		$result		= $this->query_by_db($sql);

		return (false === $result) ? new WP_Error('insert_error', $this->last_error) : $result;
	}

	public function insert($data){
		$this->cache_delete_by_conditions([], $data);

		$id	= $data[$this->primary_key] ?? null;

		if($id){
			$GLOBALS['wpdb']->check_current_query = false;

			$data		= array_filter($data, fn($v)=> !is_null($v));
			$fields		= implode(', ', array_keys($data));
			$updates	= implode(', ', array_map(fn($k)=> "`$k` = VALUES(`$k`)", array_keys($data)));
			$values		= $this->format($data);
			$sql		= "INSERT INTO `$this->table` ({$fields}) VALUES {$values} ON DUPLICATE KEY UPDATE {$updates}";
			$result		= $this->query_by_db($sql);
		}else{
			$result		= $this->insert_by_db($this->table, $data, $this->get_format($data));
		}

		if($result === false){
			return new WP_Error('insert_error', $this->last_error);
		}

		$id	= $id ?: $this->insert_id;

		$this->cache_delete($id);

		return $id;
	}

	/*
	update($id, $data);
	update($data, $where);
	update($data); // $where各种 参数通过 where() 方法事先传递
	*/
	public function update(...$args){
		if(count($args) == 2){
			if(is_array($args[0])){
				$data	= $args[0];
				$where	= $args[1];

				$conditions	= $this->where_all($where, 'fragment');
			}else{
				$id		= $args[0];
				$data	= $args[1];
				$where	= $conditions = [$this->primary_key => $id];

				$this->cache_delete($id);
			}

			$this->cache_delete_by_conditions($conditions, $data);

			$result	= $this->update_by_db($this->table, $data, $where, $this->get_format($data), $this->get_format($where));

			return $result === false ? new WP_Error('update_error', $this->last_error) : $result;
		}elseif(count($args) == 1){	// 如果为空,则需要事先通过各种 where 方法传递进去
			$data	= $args[0];
			$where	= $this->get_conditions();

			if($data && $where){
				$this->cache_delete_by_conditions($where, $data);

				$fields	= implode(', ', wpjam_map($data, fn($v, $k)=> "`$k` = ".(is_null($v) ? 'NULL' : $this->format($v, $k))));

				return $this->query_by_db("UPDATE `{$this->table}` SET {$fields} WHERE {$where}");
			}

			return 0;
		}
	}

	/*
	delete($where);
	delete($id);
	delete(); // $where 参数通过各种 where() 方法事先传递
	*/
	public function delete($where = ''){
		$id	= null;

		if($where){	// 如果传递进来字符串或者数字,认为根据主键删除,否则传递进来数组,使用 wpdb 默认方式
			if(is_array($where)){
				$this->cache_delete_by_conditions($this->where_all($where, 'fragment'));
			}else{
				$id		= $where;
				$where	= [$this->primary_key => $id];

				$this->cache_delete($id);
				$this->cache_delete_by_conditions($where);
			}

			$result	= $this->delete_by_db($this->table, $where, $this->get_format($where));
		}else{	// 如果为空,则 $where 参数通过各种 where() 方法事先传递
			$where	= $this->get_conditions();

			if(!$where){
				return 0;
			}

			$this->cache_delete_by_conditions($where);

			$result = $this->query_by_db("DELETE FROM `{$this->table}` WHERE {$where}");
		}

		if(false === $result){
			return new WP_Error('delete_error', $this->last_error);
		}

		if($id){
			$this->delete_meta_by_id($id);
		}else{
			$this->delete_orphan_meta($this->table, $this->primary_key);
		}

		return $result;
	}

	public function delete_by($field, $value){
		return $this->delete([$field => $value]);
	}

	public function delete_multi($ids){
		if(!$ids){
			return 0;
		}

		$this->cache_delete_by_conditions([$this->primary_key => $ids]);

		array_walk($ids, [$this, 'cache_delete']);

		$values	= array_map(fn($id)=> $this->format($id, $this->primary_key), $ids);
		$where	= 'WHERE `'.$this->primary_key.'` IN ('.implode(',', $values).') ';
		$sql	= "DELETE FROM `{$this->table}` {$where}";
		$result = $this->query_by_db($sql);

		if(false === $result ){
			return new WP_Error('delete_error', $this->last_error);
		}

		return $result ;
	}

	protected function cache_delete_by_conditions($conditions, $data=[]){
		$this->delete_last_changed();

		if($this->cache || $this->group_cache_key){
			if($data){
				$conditions	= $conditions ? (array)$conditions : [];
				$datas		= wp_is_numeric_array($data) ? $data : [$data];

				foreach($datas as $data){
					$key	= $this->primary_key;

					if(!empty($data[$key])){
						$this->cache_delete($data[$key]);

						$conditions[$key]	= isset($conditions[$key]) ? (array)$conditions[$key] : [];
						$conditions[$key][]	= $data[$key];
					}

					foreach($this->group_cache_key as $key){
						if(isset($data[$key])){
							$this->delete_last_changed([$key => $data[$key]]);
						}
					}
				}
			}

			if(is_array($conditions)){
				if(!$this->group_cache_key && count($conditions) == 1 && isset($conditions[$this->primary_key])){
					$conditions	= [];
				}

				$conditions	= $conditions ? $this->where_any($conditions, 'fragment') : null;
			}

			if($conditions){
				$fields		= implode(', ', [$this->primary_key, ...$this->group_cache_key]);
				$results	= $this->get_results_by_db("SELECT {$fields} FROM `{$this->table}` WHERE {$conditions}", ARRAY_A);

				if($results){
					$this->cache_delete_multiple(array_column($results, $this->primary_key));

					foreach($this->group_cache_key as $group_cache_key){
						$values	= array_unique(array_column($results, $group_cache_key));

						foreach($values as $value){
							$this->delete_last_changed([$group_cache_key => $value]);
						}
					}
				}
			}
		}
	}

	protected function get_conditions(){
		$where	= $this->parse_where($this->where, 'AND');
		$fields	= $this->searchable_fields;
		$term	= $this->search_term;

		if($fields && $term){
			$search	= array_map(fn($k)=> "`{$k}` LIKE '%".$this->esc_like_by_db($term)."%'", $fields);
			$where	.= ($where ? ' AND ' : '').'('.implode(' OR ', $search).')';
		}

		$this->clear();

		return $where;
	}

	public function get_wheres(){	// 以后放弃,目前统计在用
		return $this->get_conditions();
	}

	protected function format($value, $column=''){
		if(is_array($value)){
			$format	= '('.implode(', ', $this->get_format($value)).')';
			$value	= array_values($value);
		}else{
			$format	= str_contains($column, '%') ? $column : $this->get_format($column);
		}

		return $this->prepare_by_db($format, $value);
	}

	protected function get_format($column){
		if(is_array($column)){
			return array_map([$this, 'get_format'], array_keys($column));
		}else{
			return $this->field_types[$column] ?? '%s';
		}
	}

	protected function parse_where($qs=null, $type=''){
		$where	= [];
		$qs		??= $this->where;

		foreach($qs as $q){
			if(!$q || empty($q['compare'])){
				continue;
			}

			$compare	= strtoupper($q['compare']);

			if($compare == strtoupper('fragment')){
				$where[]	= $q['fragment'];

				continue;
			}

			$value	= $q['value'];
			$column	= $q['column'];

			if(!$column || is_null($value)){
				continue;
			}

			if(in_array($compare, ['IN', 'NOT IN'])){
				$value	= is_array($value) ? $value : explode(',', $value);
				$value	= array_values(array_unique($value));
				$value	= array_map(fn($v)=> $this->format($v, $column), $value);

				if(count($value) > 1){
					$value		= '('.implode(',', $value).')';
				}else{
					$compare	= $compare == 'IN' ? '=' : '!=';
					$value		= $value ? reset($value) : '\'\'';
				}
			}elseif(in_array($compare, ['LIKE', 'NOT LIKE'])){
				$left	= try_remove_prefix($value, '%');
				$right	= try_remove_suffix($value, '%');
				$value	= ($left ? '%' : '').$this->esc_like_by_db($value).($right ? '%' : '');
				$value	= $this->format($value, '%s');
			}else{
				$value	= $this->format($value, $column);
			}

			if(!str_contains($column, '(')){
				$column	= '`'.$column.'`';
			}

			$where[]	= $column.' '.$compare.' '.$value;
		}

		return $type ? implode(' '.$type.' ', $where) : $where;
	}

	public function where($column, $value, $output='object'){
		if(is_null($value)){
			$value	= [];
		}elseif(is_array($value)){
			if(wp_is_numeric_array($value)){
				$value	= ['value'=>$value];
			}

			if(!isset($value['value'])){
				$value	= [];
			}else{
				$value['column']	= $column;
				$value['compare']	??= is_array($value['value']) ? 'IN' : '=';
			}
		}else{
			if(is_numeric($column) || !$column){
				$value	= $value ? ['compare'=>'fragment', 'fragment'=>'( '.$value.' )'] : [];
			}else{
				$value	= ['compare'=>'=', 'column'=>$column, 'value'=>$value];
			}
		}

		if($output != 'object'){
			return $value;
		}

		$this->where[]	= $value;

		return $this;
	}

	public function query_items($limit, $offset){
		$this->limit($limit)->offset($offset)->found_rows();

		foreach(['orderby', 'order'] as $key){
			if(is_null($this->$key)){
				$this->$key(wpjam_get_data_parameter($key));
			}
		}

		if(is_null($this->search_term)){
			$this->search(wpjam_get_data_parameter('s'));
		}

		foreach($this->filterable_fields as $key){
			$this->where($key, wpjam_get_data_parameter($key));
		}

		return $this->get_results([], true);
	}

	public function query($query_vars, $output='object'){
		if(in_array($output, ['cache', 'items', 'ids'])){
			$query_vars	+= ['no_found_rows'=>true, 'suppress_filters'=>true];
		}else{
			$query_vars	= apply_filters('wpjam_query_vars', $query_vars, $this);

			if(isset($query_vars['groupby'])){
				$query_vars	= wpjam_except($query_vars, ['first', 'cursor']);

				$query_vars['no_found_rows']	= true;
			}else{
				if(!isset($query_vars['number']) && empty($query_vars['no_found_rows'])){
					$query_vars['number']	= 50;
				}
			}
		}

		$qv					= $query_vars;
		$orderby			= $qv['orderby'] ?? $this->primary_key;
		$fields				= wpjam_pull($qv, 'fields');
		$found_rows			= !wpjam_pull($qv, 'no_found_rows');
		$cache_results		= wpjam_pull($qv, 'cache_results', $output != 'ids');
		$suppress_filters	= wpjam_pull($qv, 'suppress_filters');

		if($this->meta_type){
			$meta_query	= wpjam_pull($qv, [
				'meta_key',
				'meta_value',
				'meta_compare',
				'meta_compare_key',
				'meta_type',
				'meta_type_key',
				'meta_query'
			]);

			if($meta_query){
				$this->meta_query	= new WP_Meta_Query();
				$this->meta_query->parse_query_vars($meta_query);
			}
		}

		foreach($qv as $key => $value){
			if(is_null($value)){
				continue;
			}

			if($key == 'number'){
				if($value == -1){
					$found_rows	= false;
				}else{
					$this->limit($value);
				}
			}elseif($key == 'offset'){
				$this->offset($value);
			}elseif($key == 'orderby'){
				$value	= is_array($value) ? wpjam_array($value, 'esc_sql') : esc_sql($value);

				$this->orderby($value);
			}elseif($key == 'order'){
				$this->order($value);
			}elseif($key == 'groupby'){
				$this->groupby(esc_sql($value));
			}elseif($key == 'cursor'){
				if($value > 0){
					$this->where_lt($orderby, $value);
				}
			}elseif($key == 'search' || $key == 's'){
				$this->search($value);
			}else{
				if(str_contains($key, '__')){
					foreach($this->get_operator() as $k => $v){
						if(try_remove_suffix($key, '__'.$k)){
							$value	= ['value'=>$value, 'compare'=>$v];
							break;
						}
					}
				}

				$this->where($key, $value);
			}
		}

		if($found_rows){
			$this->found_rows(true);
		}

		$clauses	= $this->get_clauses($fields);
		$clauses	= $suppress_filters ? $clauses : apply_filters_ref_array('wpjam_clauses', [$clauses, &$this]);
		$request	= $this->get_request($clauses);
		$request	= $suppress_filters ? $request : apply_filters_ref_array('wpjam_request', [$this->get_request($clauses), &$this]);

		if($cache_results){
			$cache_results	= !str_contains(strtoupper($orderby), ' RAND(') && in_array($clauses['fields'], ['*', $this->table.'.*']);
		}

		$result	= $cache_key = false;

		if($cache_results){
			$query_vars		= map_deep($query_vars, 'strval');
			$last_changed	= $this->get_last_changed($query_vars);
			$cache_key		= md5(serialize($query_vars).$request);
			
			$result	= $this->cache_get_force($cache_key);
			$result	= ($result && is_array($result) && array_get($result, 'last_changed') == $last_changed) ? $result['data'] : false;
		}

		if($output == 'cache'){
			return [$result, $cache_key, $last_changed];
		}

		if($result === false || !isset($result['items'])){
			$items	= ($cache_results || $output == 'ids') ? $this->query_ids($clauses) : $this->get_results_by_db($request, ARRAY_A);
			$result	= ['items'=>$items]+($found_rows ? ['total'=>$this->find_total()] : []);

			if($cache_results){
				$this->cache_set_force($cache_key, ['data'=>$result, 'last_changed'=>$last_changed], DAY_IN_SECONDS);
			}
		}

		if($output == 'ids'){
			return $result['items'];
		}

		if($cache_results){
			$result['items']	= array_values($this->get_by_ids($result['items']));
		}

		if($output == 'items'){
			return $result['items'];
		}

		if($found_rows){
			$result['next_cursor']	= 0;

			if(!empty($qv['number']) && $qv['number'] != -1){
				$result['max_num_pages']	= ceil($result['total'] / $qv['number']);

				if($result['items'] && $result['max_num_pages'] > 1){
					$result['next_cursor']	= (int)(end($result['items'])[$orderby]);
				}
			}
		}else{
			$result['total']	= count($result['items']);
		}

		$result['datas'] 		= &$result['items'];	// 兼容
		$result['found_rows']	= &$result['total'];	// 兼容
		$result['request']		= $request;

		return $output == 'object' ? (object)$result : $result;
	}
}

class WPJAM_Items extends WPJAM_Args{
	public function __construct($args=[]){
		$this->args	= wp_parse_args($args, $this->parse_by_type($args) ?: []);
		$this->args = wp_parse_args($this->args, ['item_type'=>'array', 'primary_title'=>'ID']);

		if($this->item_type == 'array'){
			$this->lazyload_key	= wpjam_array($this->lazyload_key);
			$this->primary_key	??= 'id';
		}else{
			$this->primary_key	= null;
		}
	}

	public function __call($method, $args){
		if(str_ends_with($method, '_items')){
			if($this->$method){
				return $this->call_property($method, ...$args);
			}

			if($method == 'delete_items'){
				return $this->update_items([]);
			}

			return $method == 'get_items' ? [] : true;
		}elseif(str_contains($method, '_setting')){
			if($this->option_name){
				$cb	= 'wpjam_'.$method;

				return $cb($this->option_name, ...$args);
			}
		}elseif(in_array($method, [
			'insert',
			'add',
			'update',
			'replace',
			'set',
			'delete',
			'remove',
			'empty',
			'move',
			'increment',
			'decrement'
		])){
			$retry	= $this->retry_times ?: 1;

			try{
				do{
					$retry	-= 1;
					$result	= $this->retry($method, ...$args);
				}while($result === false && $retry > 0);

				return $result;
			}catch(Exception $e){
				return wpjam_catch($e);
			}
		}
	}

	protected function retry($method, ...$args){
		$type	= $this->item_type;
		$items	= $this->get_items();

		if($type == 'array'){
			$items	= $items ?: [];
		}

		if($method == 'move'){
			$ids	= wpjam_try('wpjam_move', array_keys($items), ...$args);
			$items	= wp_array_slice_assoc($items, $ids);

			return $this->update_items($items);
		}

		$id		= ($method == 'insert' || ($method == 'add' && count($args) <= 1)) ? null : array_shift($args);
		$item	= array_shift($args);

		if(in_array($method, ['increment', 'decrement'])){
			if($type == 'array'){
				return;
			}

			$item	= $method == 'decrement' ? 0 - ($item ?: 1) : ($item ?: 1);
			$method	= 'increment';
		}elseif($method == 'replace'){
			$method	= 'update';
		}elseif($method == 'remove'){
			$method	= 'delete';
		}

		if(isset($id)){
			if(isset($items[$id])){
				if($method == 'add'){
					wpjam_throw('duplicate_'.$this->primary_key, $this->primary_title.'-「'.$id.'」已存在');
				}
			}else{
				if(in_array($method, ['update', 'delete'])){
					wpjam_throw('invalid_'.$this->primary_key, $this->primary_title.'-「'.$id.'」不存在');
				}elseif($method == 'set'){
					$method == 'add';	// set => add
				}
			}
		}

		if(in_array($method, ['add', 'insert']) && $this->max_items && count($items) >= $this->max_items){
			wpjam_throw('over_max_items', '最大允许数量:'.$this->max_items);
		}

		if($type == 'array' && isset($item)){
			if(in_array($this->primary_key, ['option_key', 'id'])){
				if($this->unique_key){
					$title	= $this->unique_title ?: $this->unique_key;
					$value	= $item[$this->unique_key] ?? null;

					if(is_null($id) || isset($value)){
						if(!$value && !is_numeric($value)){
							wpjam_throw('empty_'.$this->unique_key, $title.'不能为空');
						}

						foreach($items as $_id => $_item){
							if(isset($id) && $id == $_id){
								continue;
							}

							if($_item[$this->unique_key] == $value){
								wpjam_throw('duplicate_'.$this->unique_key, $title.'不能重复');
							}
						}
					}
				}

				if($method == 'insert' || ($method == 'add' && is_null($id))){
					if($items){
						$ids	= array_map(fn($id)=> (int)str_replace('option_key_', '', $id), array_keys($items));
						$id		= max($ids)+1;
					}else{
						$id		= 1;
					}

					$id	= $this->primary_key == 'option_key' ? 'option_key_'.$id : $id;
				}

				if(isset($id)){
					$item[$this->primary_key] = $id;
				}
			}else{
				if(is_null($id)){
					$id	= $item[$this->primary_key] ?? null;

					if(!$id){
						wpjam_throw('empty_'.$this->primary_key, $this->primary_title.'不能为空');
					}

					if(isset($items[$id])){
						wpjam_throw('duplicate_'.$this->primary_key, $this->primary_title.'不能重复');
					}
				}
			}

			$item	= wpjam_filter($item, fn($v)=> !is_null($v));
		}

		if($method == 'insert'){
			if($type == 'array'){
				$items	= $this->last ? array_replace($items, [$id=>$item]) : ([$id=>$item]+$items);
			}else{
				$cb	= 'array_'.($this->last ? 'push' : 'unshift');

				$cb($items, $item);
			}
		}elseif($method == 'add'){
			if(isset($id)){
				$items[$id]	= $item;
			}else{
				$items[]	= $item;
			}
		}elseif($method == 'update'){
			if($type == 'array'){
				$item	= wp_parse_args($item, $items[$id]);
			}

			$items[$id]	= $item;
		}elseif($method == 'set'){
			$items[$id]	= $item;
		}elseif($method == 'empty'){
			if(!$items){
				return [];
			}

			$prev	= $items;
			$items	= [];
		}elseif($method == 'delete'){
			$items	= wpjam_except($items, $id);
		}elseif($method == 'increment'){
			if(isset($items[$id])){
				$item	= (int)$items[$id] + $item;
			}

			$items[$id] = $item;
		}

		if($type == 'array' && $items && is_array($items) && in_array($this->primary_key, ['option_key','id'])){
			foreach($items as &$item){
				$item	= wpjam_except($item, $this->primary_key);

				if($this->parent_key){
					$item	= wpjam_except($item, $this->parent_key);
				}
			}
		}

		$result	= $this->update_items($items);

		if($result){
			if($method == 'insert'){
				if($type == 'array'){
					return ['id'=>$id,	'last'=>(bool)$this->last];
				}
			}elseif($method == 'empty'){
				return $prev;
			}elseif($method == 'increment'){
				return $item;
			}
		}

		return $result;
	}

	public function query_items($args){
		$items	= $this->parse_items();

		return ['items'=>$items, 'total'=>count($items)];
	}

	public function parse_items($items=null){
		$items	??= $this->get_items();

		if($items && is_array($items)){
			foreach($items as $id => &$item){
				$item	= $this->parse_item($item, $id);
			}

			if($this->item_type == 'array' && $this->lazyload_key){
				wpjam_lazyload($this->lazyload_key, $items);
			}

			return $items;
		}

		return [];
	}

	public function parse_item($item, $id){
		return $this->item_type == 'array' ? array_merge((is_array($item) ? $item : []), [$this->primary_key => $id]) : $item;
	}

	public function get_results(){
		return $this->parse_items();
	}

	public function reset(){
		return $this->delete_items();
	}

	public function exists($value, $type='unique'){
		$items	= $this->get_items();

		if(!$items){
			return false;
		}

		if($this->item_type == 'array'){
			if($type == 'unique' && $this->unique_key){
				return in_array($value, array_column($items, $this->unique_key));
			}else{
				return isset($items[$value]);
			}
		}else{
			return in_array($value, $items);
		}
	}

	public function get($id){
		$item	= $this->get_items()[$id] ?? false;

		return $item ? $this->parse_item($item, $id) : false;
	}

	protected static function parse_by_type($args){
		$type	= wpjam_pull($args, 'type');

		if($type == 'option'){
			if(!empty($args['option_name'])){
				return [
					'primary_key'	=> 'option_key',
					'get_items'		=> fn()=> get_option($this->option_name) ?: [],
					'update_items'	=> fn($items)=> update_option($this->option_name, $items),
				];
			}
		}elseif($type == 'setting'){
			if(array_all(['option_name', 'setting_name'], fn($k)=> !empty($args[$k]))){
				return [
					'get_items'		=> fn()=> wpjam_get_setting($this->option_name, $this->setting_name) ?: [],
					'update_items'	=> fn($items)=> wpjam_update_setting($this->option_name, $this->setting_name, $items),
				];
			}
		}elseif($type == 'meta'){
			if(array_all(['meta_type', 'meta_key', 'object_id'], fn($k)=> !empty($args[$k]))){
				return [
					'parent_key'	=> $args['meta_type'].'_id',
					'get_items'		=> fn()=> get_metadata($this->meta_type, $this->object_id, $this->meta_key, true) ?: [],
					'delete_items'	=> fn()=> delete_metadata($this->meta_type, $this->object_id, $this->meta_key),
					'update_items'	=> fn($items)=> update_metadata($this->meta_type, $this->object_id, $this->meta_key, $items, $this->get_items()),
				];
			}
		}elseif($type == 'cache'){
			if(!empty($args['cache_key'])){
				return [
					'item_type'		=> '',
					'retry_times'	=> 10,
					'object'		=> wpjam_cache(wp_parse_args($args, ['group'=>'list_cache'])),
					'get_items'		=> fn()=> $this->object->get_with_cas($this->cache_key, $this, []) ?: [],
					'update_items'	=> fn($items)=> $this->object->cas($this->cas_token, $this->cache_key, $items),
				];
			}
		}elseif($type == 'transient'){
			if(!empty($args['transient'])){
				return [
					'item_type'		=> '',
					'get_items'		=> fn()=> get_transient($this->transient) ?: [],
					'update_items'	=> fn($items)=> set_transient($this->transient, $items, DAY_IN_SECONDS),
				];
			}
		}elseif($type == 'post_content'){
			if(!empty($args['post_id'])){
				return [
					'parent_key'	=> 'post_id',
					'object'		=> wpjam_get_post_object($args['post_id']),
					'get_items'		=> fn()=> $this->object->get_unserialized(),
					'update_items'	=> fn($items)=> $this->object->save(['content'=>$items ?: ''])
				];
			}
		}elseif($type){
			$parser	= wpjam_pull($args, 'parser');

			if($parser){
				return $parser($args, $type);
			}
		}
	}
}

class WPJAM_Lazyloader{
	public static function queue($name, $ids){
		if(is_array($name)){
			if(wp_is_numeric_array($name)){
				array_walk($name, fn($n)=> self::queue($n, $ids));
			}else{
				array_walk($name, fn($n, $k)=> self::queue($n, array_column($ids, $k)));
			}

			return;
		}

		$ids	= array_unique($ids);
		$ids	= array_filter($ids);

		if(!$ids){
			return;
		}

		if(in_array($name, ['blog', 'site'])){
			_prime_site_caches($ids);
		}elseif($name == 'post'){
			_prime_post_caches($ids, false, false);

			self::queue('post_meta', $ids);
		}elseif($name == 'term'){
			_prime_term_caches($ids);
		}elseif($name == 'comment'){
			_prime_comment_caches($ids);
		}elseif(in_array($name, ['term_meta', 'comment_meta', 'blog_meta'])){
			wp_metadata_lazyloader()->queue_objects(substr($name, 0, -5), $ids);
		}else{
			self::call_pending('add', $name, $ids);
		}
	}

	public static function add($name, $args){
		wpjam_add_item('lazyloader', $name, $args);
	}

	public static function call_pending($action, $name, ...$args){
		$items	= array_unique(wpjam_get_items('lazyloader', $name));

		if($action == 'load'){
			if($items){
				if($args){
					$args[0]($items, $name);
				}else{
					self::remove_filter($name, $items);
				}
			}

			$items	= [];
		}elseif($action == 'add'){
			if(!$items){
				self::add_filter($name);
			}

			$items	= array_merge($items, $args[0]);
		}

		wpjam_update_items('lazyloader', $items, $name);
	}

	public static function __callStatic($method, $args){
		if(str_ends_with($method, 'filter')){
			$name		= $args[0];
			$setting	= wpjam_get_item('lazyloader', $name);
			$filter		= $setting ? $setting['filter'] : (str_ends_with($name, '_meta') ? 'get_'.$name.'data' : '');

			if($filter){
				if($method == 'remove_filter'){
					if($filter == 'get_'.$name.'data'){
						update_meta_cache(substr($name, 0, -5), $args[1]);
					}else{
						$setting['callback']($args[1]);
					}
				}

				$method($filter, [self::class, 'callback_'.$name]);
			}
		}elseif(try_remove_prefix($method, 'callback_')){
			self::call_pending('load', $method);

			return array_shift($args);
		}
	}
}