403Webshell
Server IP : 43.141.49.92  /  Your IP : 113.219.202.173
Web Server : Apache
System : Linux VM-8-5-opencloudos 6.6.34-9.oc9.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jun 19 19:35:45 CST 2024 x86_64
User : www ( 1000)
PHP Version : 8.1.27
Disable Function : 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
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /www/wwwroot/wordpress/wordpress/wp-content/plugins/wpjam-basic/includes/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /www/wwwroot/wordpress/wordpress/wp-content/plugins/wpjam-basic/includes/class-wpjam-admin.php
<?php
class WPJAM_Admin{
	public static function get_prefix(){
		return is_network_admin() ? 'network_' : (is_user_admin() ? 'user_' : '');
	}

	public static function url($path=''){
		return call_user_func(self::get_prefix().'admin_url', $path);
	}

	public static function enqueue_scripts($setting){
		$ver		= get_plugin_data(WPJAM_BASIC_PLUGIN_FILE)['Version'];
		$static		= wpjam_url(dirname(__DIR__), 'relative').'/static';
		$setting	+= wpjam_map(wpjam_get_items('page_setting'), fn($v)=> is_closure($v) ? $v() : $v);

		wp_enqueue_media($setting['screen_base'] == 'post' ? ['post'=>wpjam_get_admin_post_id()] : []);
		wp_enqueue_style('wpjam-style', $static.'/style.css', ['thickbox', 'remixicon', 'wp-color-picker', 'editor-buttons'], $ver);
		wp_enqueue_script('wpjam-script', $static.'/script.js', ['jquery', 'thickbox', 'wp-backbone', 'jquery-ui-sortable', 'jquery-ui-tooltip', 'jquery-ui-tabs', 'jquery-ui-draggable', 'jquery-ui-slider', 'jquery-ui-autocomplete', 'wp-color-picker'], $ver);
		wp_enqueue_script('wpjam-form', $static.'/form.js', ['wpjam-script', 'mce-view'], $ver);

		wp_localize_script('wpjam-script', 'wpjam_page_setting', $setting);
	}

	public static function on_current_screen($screen){
		$page	= $GLOBALS['plugin_page'];
		$object = WPJAM_Plugin_Page::get_current();

		if($object){
			$object->load($screen);

			$GLOBALS['current_admin_url']	= $url = self::url($object->admin_url);
		}else{
			if(!empty($_POST['builtin_page'])){
				$url	= self::url($_POST['builtin_page']);
			}else{
				$url	= set_url_scheme('http://'.$_SERVER['HTTP_HOST'].parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));

				if($page){
					$url	= add_query_arg('page', $page, $url);
				}else{
					foreach(['taxonomy', 'post_type'] as $key){
						if($screen->$key && isset($_REQUEST[$key])){
							$url	= add_query_arg($key, $_REQUEST[$key], $url);
						}
					}
				}
			}	

			$GLOBALS['current_admin_url']	= $url;

			WPJAM_Builtin_Page::init($screen);
		}

		if(!wp_doing_ajax()){
			if($screen->base == 'customize'){
				return;
			}

			$setting	= [
				'screen_base'	=> $screen->base,
				'screen_id'		=> $screen->id,
				'post_type'		=> $screen->post_type,
				'taxonomy'		=> $screen->taxonomy,
				'admin_url'		=> $url
			];

			$params	= wpjam_except($_REQUEST, ['page', 'tab', '_wp_http_referer', '_wpnonce', ...wp_removable_query_args()]);

			if($page){
				$setting['plugin_page']	= $page;

				if($object && $object->query_data){
					$query_data	= wpjam_map($object->query_data, fn($v)=> is_null($v) ? $v : sanitize_textarea_field($v));
					$params		= array_diff_key($params, $query_data);

					$setting['query_data']	= $query_data;
				}
			}else{
				$setting['builtin_page']	= str_replace(self::url(), '', $url);

				$params	= wpjam_except($params, array_filter(['taxonomy', 'post_type'], fn($k)=> $screen->$k));
			}

			if($params && isset($params['data']) && !is_array($params['data'])){
				$params['data']	= urldecode($params['data']);
			}

			$setting['params']	= $params ? map_deep($params, 'sanitize_textarea_field') : new stdClass();

			add_action('admin_enqueue_scripts',	fn()=> self::enqueue_scripts($setting), 9);

			add_filter('wpjam_html', fn($html)=> str_replace('dashicons-before dashicons-ri-', 'wp-menu-ri ri-', $html));
		}

		add_filter('admin_url', fn($url)=> ($pos = strpos($url, 'admin/page=')) ? substr_replace($url, 'admin.php?', $pos+6, 0) : $url);
	}

	public static function on_plugins_loaded(){
		if($GLOBALS['pagenow'] == 'admin-post.php'){
			return;
		}

		if(wp_doing_ajax()){
			wpjam_add_admin_ajax('wpjam-page-action', [
				'callback'	=> ['WPJAM_Page_Action', 'ajax_response'],
				'fields'	=> ['page_action'=>[], 'action_type'=>[]]
			]);

			wpjam_add_admin_ajax('wpjam-upload', [
				'callback'	=> ['WPJAM_Uploader', 'ajax_response'],
				'fields'	=> ['file_name'=> ['required'=>true]]
			]);

			wpjam_add_admin_ajax('wpjam-query', [
				'callback'	=> ['WPJAM_Data_Type', 'ajax_response'],
				'fields'	=> ['data_type'=> ['required'=>true]]
			]);

			add_action('admin_init', function(){
				$screen_id	= $_POST['screen_id'] ?? ($_POST['screen'] ?? null);

				if(is_null($screen_id)){
					$action	= $_REQUEST['action'] ?? '';

					if($action == 'fetch-list'){
						$screen_id	= $_GET['list_args']['screen']['id'];
					}elseif($action == 'inline-save-tax'){
						$screen_id	= 'edit-'.sanitize_key($_POST['taxonomy']);
					}else{
						$screen_id	= apply_filters('wpjam_ajax_screen_id', $screen_id, $action);
					}
				}

				if($screen_id){
					$const	= wpjam_find(['network'=>'WP_NETWORK_ADMIN', 'user'=>'WP_USER_ADMIN'], fn($v, $k)=> str_ends_with($screen_id, '-'.$k));

					if($const && !defined($const)){
						define($const, true);
					}

					if($screen_id == 'upload'){
						[$GLOBALS['hook_suffix'], $screen_id]	= [$screen_id, ''];
					}

					$GLOBALS['plugin_page']	= $_POST['plugin_page'] ?? null;

					WPJAM_Menu_Page::init(false);

					set_current_screen($screen_id);
				}
			}, 9);
		}else{
			add_action(self::get_prefix().'admin_menu',	fn()=> WPJAM_Menu_Page::init(true), 9);
		}

		add_action('current_screen', [self::class, 'on_current_screen'], 9);
	}
}

class WPJAM_Admin_Action extends WPJAM_Register{
	public function parse_submit_button($button, $name=null, $render=null){
		$render	??= is_null($name);
		$button	= $button ? (is_array($button) ? $button : [$this->name => $button]) : [];

		foreach($button as $key => &$item){
			$item	= is_array($item) ? $item : ['text'=>$item];
			$item	= wp_parse_args($item, ['response'=>($this->response ?? $this->name), 'class'=>'primary']);
			$item	= $render ? get_submit_button($item['text'], $item['class'], $key, false) : $item;
		}

		if($name){
			return $button[$name] ?? wp_die('无效的提交按钮');
		}

		return $render ? implode('', $button) : $button;
	}

	private function parse_nonce_action($args=[]){
		$name	= $this->name;
		$name	= !empty($args['bulk']) ? 'bulk_'.$name : $name.(!empty($args['id']) ? '-'.$args['id'] : '');

		return ($GLOBALS['plugin_page'] ?? $GLOBALS['current_screen']->id).'-'.$name;
	}

	public function create_nonce($args=[]){
		return wp_create_nonce($this->parse_nonce_action($args));
	}

	public function verify_nonce($args=[]){
		return check_ajax_referer($this->parse_nonce_action($args), false, false);
	}
}

class WPJAM_Page_Action extends WPJAM_Admin_Action{
	public function is_allowed($type=''){
		$capability	= $this->capability ?? ($type ? 'manage_options' : '');

		return $capability ? current_user_can($capability, $this->name) : true;
	}

	public function callback($type=''){
		if($type == 'form'){
			$page_title	= wpjam_get_post_parameter('page_title');

			if(!$page_title){
				$key	= wpjam_find(['page_title', 'button_text', 'submit_text'], fn($k)=> $this->$k && !is_array($this->$k));

				if($key){
					$page_title	= $this->$key;
				}
			}

			return [
				'form'			=> $this->get_form(),
				'width'			=> (int)$this->width,
				'modal_id'		=> $this->modal_id ?: 'tb_modal',
				'page_title'	=> $page_title
			];
		}

		if(!$this->verify_nonce()){
			wp_die('invalid_nonce');
		}

		if(!$this->is_allowed($type)){
			wp_die('access_denied');
		}

		if($type == 'submit'){
			$submit		= wpjam_get_post_parameter('submit_name', ['default'=>$this->name]);
			$button		= $this->get_submit_button($submit);
			$callback	= $button['callback'] ?? '';
			$response	= $button['response'];
		}else{
			$callback	= $submit = '';
			$response	= $this->response ?? $this->name;
		}

		$response	= ['type'=>$response];
		$callback	= $callback ?: $this->callback;

		if(!$callback || !is_callable($callback)){
			wp_die('无效的回调函数');
		}

		if($this->validate){
			$data	= wpjam_get_fields_parameter($this->get_fields(), 'data');
			$result	= wpjam_try($callback, $data, $this->name, $submit);
		}else{
			$result	= wpjam_try($callback, $this->name, $submit);
		}

		if(is_array($result)){
			$response	= array_merge($response, $result);
		}elseif($result === false || is_null($result)){
			$response	= new WP_Error('invalid_callback', ['返回错误']);
		}elseif($result !== true){
			$key		= $this->response == 'redirect' ? 'url' : 'data';
			$response	= array_merge($response, [$key=>$result]);
		}

		return apply_filters('wpjam_ajax_response', $response);
	}

	public function render(){
		try{
			return $this->get_form();
		}catch(Exception $e){
			wp_die(wpjam_catch($e));
		}
	}

	public function get_submit_button($name=null, $render=null){
		if(!is_null($this->submit_text)){
			$button	= $this->submit_text;
			$button	= is_callable($button) ? wpjam_try($button, $this->name) : $button;
		}else{
			$button = wp_strip_all_tags($this->page_title);
		}

		return $this->parse_submit_button($button, $name, $render);
	}

	public function get_data(){
		$data	= is_callable($this->data_callback) ? wpjam_try($this->data_callback, $this->name, $this->get_fields()) : [];

		return array_merge(($this->data ?: []), $data);
	}

	public function get_button($args=[]){
		if(!$this->is_allowed()){
			return '';
		}

		$this->update_args(wpjam_except($args, 'data'));

		$text	= $this->button_text ?? '保存';
		$attr	= ['title'=>$this->page_title ?: $text, 'style'=>$this->style, 'class'=>$this->class ?? 'button-primary large'];
		$data	= $this->generate_data_attr(['data'=>wpjam_pull($args, 'data') ?: []]);

		return wpjam_tag(($this->tag ?: 'a'), $attr, $text)->add_class('wpjam-button')->data($data);
	}

	public function get_form(){
		if(!$this->is_allowed()){
			return '';
		}

		$args	= array_merge($this->args, ['data'=>$this->get_data()]);
		$button	= $this->get_submit_button();
		$form	= wpjam_fields($this->get_fields())->render($args, false)->wrap('form', [
			'method'	=> 'post',
			'action'	=> '#',
			'id'		=> $this->form_id ?: 'wpjam_form',
			'data'		=> $this->generate_data_attr([], 'form')
		]);

		return $button ? $form->append('p', ['submit'], $button) : $form;
	}

	protected function get_fields(){
		$fields	= $this->fields;
		$fields	= ($fields && is_callable($fields)) ? wpjam_try($fields, $this->name) : $fields;

		return $fields ?: [];
	}

	public function generate_data_attr($args=[], $type='button'){
		return [
			'action'	=> $this->name,
			'nonce'		=> $this->create_nonce()
		] + ($type == 'button' ? [
			'title'		=> $this->page_title ?: $this->button_text,
			'data'		=> wp_parse_args(($args['data'] ?? []), ($this->data ?: [])),
			'direct'	=> $this->direct,
			'confirm'	=> $this->confirm
		] : []);
	}

	public static function ajax_response($data){
		$object	= self::get($data['page_action']);

		if($object){
			return $object->callback($data['action_type']);
		}

		do_action_deprecated('wpjam_page_action', [$data['page_action'], $data['action_type']], 'WPJAM Basic 4.6');

		$callback	= wpjam_get_filter_name($GLOBALS['plugin_page'], 'ajax_response');

		if(is_callable($callback)){
			$result	= $callback($data['page_action']);
			$result	= (is_wp_error($result) || is_array($result)) ? $result : [];

			wpjam_send_json($result);
		}else{
			wp_die('invalid_callback');
		}
	}
}

class WPJAM_Dashboard extends WPJAM_Args{
	public function page_load(){
		if($this->name != 'dashboard'){
			require_once ABSPATH . 'wp-admin/includes/dashboard.php';
			// wp_dashboard_setup();

			wp_enqueue_script('dashboard');

			if(wp_is_mobile()){
				wp_enqueue_script('jquery-touch-punch');
			}
		}

		$widgets	= $this->widgets ?: [];
		$widgets	= is_callable($widgets) ? $widgets($this->name) : $widgets;
		$widgets	= array_merge($widgets, array_filter(wpjam_get_items('dashboard_widget'), fn($widget)=> isset($widget['dashboard']) ? ($widget['dashboard'] == $this->name) : ($this->name == 'dashboard')));

		foreach($widgets as $id => $widget){
			$id	= $widget['id'] ?? $id;

			add_meta_box(
				$id,
				$widget['title'],
				$widget['callback'] ?? wpjam_get_filter_name($id, 'dashboard_widget_callback'),
				get_current_screen(),			// 传递 screen_id 才能在中文的父菜单下,保证一致性。
				$widget['context'] ?? 'normal',	// 位置,normal 左侧, side 右侧
				$widget['priority'] ?? 'core',
				$widget['args'] ?? []
			);
		}
	}

	public function render(){
		$tag	= wpjam_tag('div', ['id'=>'dashboard-widgets-wrap'], wpjam_ob_get_contents('wp_dashboard'));
		$panel	= $this->welcome_panel;

		if($panel && is_callable($panel)){
			$tag->before('div', ['id'=>'welcome-panel', 'class'=>'welcome-panel wpjam-welcome-panel'], wpjam_ob_get_contents($panel, $this->name));
		}

		return $tag;
	}

	public static function add_widget($name, $args){
		wpjam_add_item('dashboard_widget', $name, $args);
	}
}

class WPJAM_Menu_Page extends WPJAM_Args{
	private function parse($args, $render=false){
		$this->args	= $args;
		$slug		= $this->menu_slug;
		$parent		= $this->parent;
		$page		= ($parent && strpos($parent, '.php')) ? $parent : 'admin.php';
		$network	= $this->pull('network', ($page == 'admin.php'));

		if(is_network_admin() ? !$network : $network === 'only'){
			return;
		}

		$this->page_title	??= $this->menu_title;
		$this->capability	??= 'manage_options';

		if(!str_contains($slug, '.php')){
			$this->admin_url = add_query_arg(['page'=>$slug], $page);

			if(!$this->query_data($GLOBALS['plugin_page'] == $slug)){
				return;
			}
		}

		$object	= WPJAM_Plugin_Page::set_current($this);

		if($render){
			if(str_contains($slug, '.php')){
				if($GLOBALS['pagenow'] == explode('?', $slug)[0]){
					$query_vars	= wp_parse_args(parse_url($slug, PHP_URL_QUERY));

					if(!$query_vars || wpjam_every($query_vars, fn($v, $k)=> $v == wpjam_get_parameter($k))){
						add_filter('parent_file', fn()=> $parent ?: $slug);
					}
				}
			}else{
				$callback	= $object ? [$object, 'render'] : '__return_true';
			}

			$args	= [$this->page_title, $this->menu_title, $this->capability, $slug, ($callback ?? null)];
			$hook	= $parent ? add_submenu_page(...[$parent, ...$args, $this->position]) : add_menu_page(...[...$args, ($this->icon ? wpjam_fix('add', 'prev', $this->icon, 'dashicons-') : null), $this->position]);

			if($object){
				$object->page_hook	= $hook;
			}
		}

		return true;
	}

	protected function query_data($current=false){
		if($this->query_args){
			$query_data	= wpjam_get_data_parameter($this->query_args);
			$null_data	= array_filter($query_data, fn($v)=> is_null($v));
			$admin_url	= $this->admin_url;

			if($null_data){
				return $current ? wp_die('「'.implode('」,「', array_keys($null_data)).'」参数无法获取') : false;
			}

			$this->admin_url	= $query_url = add_query_arg($query_data, $admin_url);
			$this->query_data	= $query_data;

			add_filter('wpjam_html', fn($html)=> str_replace("href='".esc_url($admin_url)."'", "href='".$query_url."'", $html));
		}

		return true;
	}

	public static function init($render=true){
		do_action('wpjam_admin_init');

		if($render){
			$builtins	= array_filter(array_flip($GLOBALS['admin_page_hooks']), fn($v)=> str_contains($v, '.php'));
			$builtins	= wpjam_array($builtins, fn($k, $v)=> str_starts_with($v, 'edit.php?') && $k != 'pages' ? wpjam_get_post_type_setting($k, 'plural') : $k);
			$builtins	+= ['themes'=>'themes.php', 'options'=>$builtins['settings']];
			$builtins	+= isset($builtins['profile']) ? ['users'=>'profile.php'] : [];
		}else{
			$page	= $GLOBALS['plugin_page'] ?? '';

			if(!$page){
				return;
			}
		}

		$menu	= new self();

		foreach(apply_filters('wpjam_pages', wpjam_get_items('menu_page')) as $slug => $args){
			$slug	= $args['menu_slug'] ??= $slug;
			$subs	= $args['subs'] ??= [];
			$parent	= $render ? ($builtins[$slug] ?? '') : '';

			if(!$parent){
				$parent	= $slug;

				if($render){
					if(!$menu->parse($args, $render)){
						continue;
					}
				}else{
					if(!$subs && $page == $slug){
						return $menu->parse($args);
					}
				}
			}

			if(!$subs){
				continue;
			}

			$subs	= wpjam_sort($subs, fn($v)=> array_get($v, 'order', 10) - 1000 * array_get($v, 'position', 10));

			if($parent == $slug){
				$sub	= $subs[$slug] ?? wpjam_except($args, ['position', 'subs', 'page_title']);
				$sub	= array_merge($sub, !empty($sub['sub_title']) ? ['menu_title'=>$sub['sub_title']] : []);
				$subs	= array_merge([$slug=>$sub], $subs);
			}

			foreach($subs as $s => $sub){
				$sub	= wp_parse_args($sub, ['menu_slug'=>$s, 'parent'=>$parent]);

				if($render){
					$menu->parse($sub, $render);
				}else{
					if($page == $s){
						return $menu->parse($sub);
					}
				}
			}
		}
	}

	public static function get_tabs($page, $strict=true){
		return wpjam_filter(wpjam_get_items('tab_page'), fn($args)=> empty($args['plugin_page']) ? !$strict : $args['plugin_page'] == $page);
	}

	public static function add($args=[]){
		if(!empty($args['tab_slug'])){
			if(!is_numeric($args['tab_slug']) && !empty($args['title'])){
				$tab	= array_merge($args, ['name'=>$args['tab_slug'], 'tab_page'=>true]);
				$slug	= wpjam_join(':', [($args['plugin_page'] ?? ''), $args['tab_slug']]);
				$score	= wpjam_get($tab, 'order', 10);
				$items	= wpjam_add_item('tab_page', $slug, $tab, fn($v)=> $score > wpjam_get($v, 'order', 10));
			}
		}elseif(!empty($args['menu_slug'])){
			if(!is_numeric($args['menu_slug']) && !empty($args['menu_title'])){
				$slug	= wpjam_pull($args, 'menu_slug');
				$parent	= wpjam_pull($args, 'parent');
				$args	= $parent ? ['subs'=>[$slug=>$args]] : wp_parse_args($args, ['subs'=>[]]);
				$slug	= $parent ?: $slug;
				$item	= wpjam_get_item('menu_page', $slug);
				$subs	= $item ? array_merge($item['subs'], $args['subs']) : [];
				$args	= $item ? array_merge($item, $args, ['subs'=>$subs]) : $args;

				wpjam_set_item('menu_page', $slug, $args);
			}
		}
	}
}

class WPJAM_Plugin_Page extends WPJAM_Menu_Page{
	public function __get($key){
		if($key == 'is_tab'){
			return $this->function == 'tab';
		}elseif($key == 'cb_args'){
			return [$GLOBALS['plugin_page'], ($this->tab_page ? $this->name : '')];
		}

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

		if($key == 'function'){
			return $value == 'list' ? 'list_table' : ($value ?: wpjam_get_filter_name($this->name, 'page'));
		}

		return $value;
	}

	private function throw($title){
		wpjam_throw('error', $title);
	}

	private function include_file(){
		$key	= ($this->tab_page ? 'tab' : 'page').'_file';
		$file	= (array)$this->$key ?: [];

		array_walk($file, fn($f)=> include $f);
	}

	public function load($screen=null, $page_hook=null){
		$this->set_defaults();

		if($screen && str_contains($screen->id, '%')){
			$parts	= explode('_', $screen->id);
			$hooks	= array_flip($GLOBALS['admin_page_hooks']);

			if(isset($hooks[$parts[0]])){
				$parts[0]	= $hooks[$parts[0]];
				$screen->id	= implode('_', $parts);
			}
		}

		do_action('wpjam_plugin_page_load', ...$this->cb_args);	// 兼容

		wpjam_admin_load('plugin_page', function($load, $page, $tab){
			if(!empty($load['plugin_page'])){
				if(is_callable($load['plugin_page'])){
					return $load['plugin_page']($page, $tab);
				}

				if(!wpjam_compare($page, $load['plugin_page'])){
					return false;
				}
			}

			if(!empty($load['current_tab'])){
				return $tab && wpjam_compare($tab, $load['current_tab']);
			}

			return !$tab;
		}, ...$this->cb_args);

		// 一般 load_callback 优先于 load_file 执行
		// 如果 load_callback 不存在,尝试优先加载 load_file

		$included	= false;
		$callback	= $this->load_callback;

		if($callback){
			if(!is_callable($callback)){
				$this->include_file();

				$included	= true;
			}

			if(is_callable($callback)){
				$callback($this->name);
			}
		}

		if(!$included){
			$this->include_file();
		}

		if(!$this->is_tab){
			$function	= $this->function;

			if(is_string($function) && in_array($function, ['option', 'list_table', 'form', 'dashboard'])){
				$name	= $this->{$function.'_name'} ?: $GLOBALS['plugin_page'];

				$this->preprocess($name, $screen);
			}
		}

		if($this->chart && !is_object($this->chart)){
			$this->chart	= WPJAM_Chart::get_instance($this->chart);
		}

		if($this->editor){
			add_action('admin_footer', 'wp_enqueue_editor');
		}

		try{
			$this->query_data	??= [];

			if($this->is_tab){
				$object	= $this->get_tab();

				$object->chart	??= $this->chart;

				$object->load($screen, $this->page_hook);

				$this->render		= [$object, 'render'];
				$this->admin_url	= $object->admin_url;
				$this->query_data	+= $object->query_data ?: [];

				wpjam_add_item('page_setting', 'current_tab', $object->name);
			}else{
				if(!empty($name)){
					$object	= $this->page_object($name);

					if(method_exists($object, 'page_load')){
						if(wp_doing_ajax()){
							$object->page_load();
						}else{
							add_action('load-'.($page_hook ?: $this->page_hook), [$object, 'page_load']);
						}
					}

					$this->render				= [$object, 'render'];
					$this->page_title			= $object->title ?: $this->page_title;
					$this->page_title_action	= $object->page_title_action;
					$this->summary				= $this->summary ?: $object->summary;
					$this->query_data			+= wpjam_get_data_parameter($object->query_args);
				}else{
					if(!is_callable($function)){
						$this->throw('页面函数'.'「'.$function.'」未定义。');
					}

					$this->render	= fn()=> ($this->chart ? $this->chart->render() : '').wpjam_ob_get_contents($function);
				}
			}
		}catch(Exception $e){
			wpjam_add_admin_error(wpjam_catch($e));
		}
	}

	private function preprocess($name, $screen){
		do_action('wpjam_preprocess_plugin_page', $this, $name);	// 兼容

		$function	= $this->function;

		if($function == 'form'){
			$object	= WPJAM_Page_Action::get($name);
		}elseif($function == 'option'){
			$object	= WPJAM_Option_Setting::get($name);
		}

		if(isset($object)){
			$args	= $object->to_array();
		}else{
			$args	= $this->$function;

			if($args){
				if($function == 'list_table' && is_string($args) && class_exists($args) && method_exists($args, 'get_list_table')){
					$args	= [$args, 'get_list_table'];
				}

				if(is_callable($args)){
					$args	= $args($this);
				}

				$this->$function	= $args;
			}
		}

		$args	= ($args && is_array($args)) ? wpjam_parse_data_type($args) : [];

		if($args){
			$this->update_args($args);
		
			$data_type	= $this->data_type;

			if($data_type){
				$screen->add_option('data_type', $data_type);

				$object	= wpjam_get_data_type_object($data_type, $args);

				if($object && $object->meta_type){
					$screen->add_option('meta_type', $object->meta_type);
				}

				if(in_array($data_type, ['post_type', 'taxonomy']) && !$screen->$data_type && $this->$data_type){
					$screen->$data_type	= $this->$data_type;
				}
			}
		}
	}

	private function page_object($name){
		$function	= $this->function;

		if($function == 'form'){
			$object	= WPJAM_Page_Action::get($name);

			if(!$object){
				$args	= $this->form ?: ($this->callback ? $this->to_array() : []);
				$object	= $args ? WPJAM_Page_Action::register($name, $args) : $this->throw('Page Action'.'「'.$name.'」未定义。');
			}

			return $object;
		}elseif($function == 'option'){
			$object	= WPJAM_Option_Setting::get($name);

			if(!$object){
				if($this->model && method_exists($this->model, 'register_option')){	// 舍弃 ing
					$object	= call_user_func([$this->model, 'register_option'], $this->delete_arg('model')->to_array());
				}else{
					$args	= $this->option ?: (($this->sections || $this->fields) ? $this->to_array() : []);

					if(!$args){
						$args	= apply_filters(wpjam_get_filter_name($name, 'setting'), []); // 舍弃 ing
						$args	= $args ?: $this->throw('Option'.'「'.$name.'」未定义。');
					}

					$object	= WPJAM_Option_Setting::create($name, $args);
				}
			}

			return $object->get_current();
		}elseif($function == 'list_table'){
			$args	= $this->list_table;

			if($args){
				if(isset($args['defaults'])){
					$this->set_defaults($args['defaults']);
				}
			}else{
				$args	= $this->model ? wpjam_except($this->to_array(), 'defaults') : apply_filters(wpjam_get_filter_name($name, 'list_table'), []);
				$args	= $args ?: $this->throw('List Table'.'「'.$name.'」未定义。');
			}

			if(empty($args['model']) || (!is_object($args['model']) && !class_exists($args['model']))){
				$this->throw('List Table Model'.'「'.$args['model'].'」未定义。');
			}

			foreach(['admin_head', 'admin_footer'] as $admin_hook){
				if(method_exists($args['model'], $admin_hook)){
					add_action($admin_hook,	[$args['model'], $admin_hook]);
				}
			}

			if($this->chart){
				$args['chart']	= $this->chart;	
			}

			return new WPJAM_List_Table(wp_parse_args($args, [
				'name'		=> $name,
				'singular'	=> $name,
				'plural'	=> $name.'s',
				'capability'=> $this->capability ?: 'manage_options',
				'data_type'	=> 'model',
				'per_page'	=> 50,
			]));
		}elseif($function == 'dashboard'){
			$args	= $this->dashboard ?: ($this->widgets ? $this->to_array() : []);
			$args	= $args ?: $this->throw('Dashboard'.'「'.$name.'」未定义。');

			return new WPJAM_Dashboard(array_merge($args, ['name'=>$name]));
		}
	}

	public function render(){
		$tag	= wpjam_tag(($this->tab_page ? 'h2' : 'h1'), ['wp-heading-inline'], ($this->page_title ?? $this->title));
		$tag	= $this->page_title_action ? $tag->after('span', ['page-title-action-wrap'], $this->page_title_action) : $tag;
		$tag	= $tag->after('hr', ['wp-header-end']);

		$summary	= $this->summary;

		if($summary){
			if(is_callable($summary)){
				$summary	= $summary(...$this->cb_args);
			}elseif(is_array($summary)){
				$summary	= $summary[0].(!empty($summary[1]) ? ',详细介绍请点击:'.wpjam_tag('a', ['href'=>$summary[1], 'target'=>'_blank'], $this->menu_title) : '');
			}elseif(is_file($summary)){
				$summary	= wpjam_get_file_summary($summary);
			}

			$tag->after($summary, 'p');
		}

		if($this->is_tab){
			$callback	= wpjam_get_filter_name($this->name, 'page');

			if(is_callable($callback)){
				$tag->after(wpjam_ob_get_contents($callback));	// 所有 Tab 页面都执行的函数
			}

			if(count($this->tabs) > 1){
				$nav	= wpjam_tag('nav', ['nav-tab-wrapper', 'wp-clearfix']);
				$nav	= array_reduce($this->tabs, fn($nav, $tab)=> $nav->append(($tab->tab_title ?: $tab->title), 'a', ['class'=>['nav-tab', $GLOBALS['current_tab'] == $tab->name ? 'nav-tab-active' : ''], 'href'=>$tab->admin_url]), $nav);

				$tag->after($nav);
			}
		}

		if($this->render){
			$tag->after(call_user_func($this->render, $this));
		}

		if($this->tab_page){
			return $tag;
		}

		echo $tag->wrap('div', ['wrap']);
	}

	private function set_defaults($defaults=[]){
		if($defaults){
			$this->defaults	= array_merge(($this->defaults ?: []), $defaults);
		}

		if($this->defaults){
			wpjam_var('defaults', $this->defaults);
		}
	}

	private function get_tab(){
		$tabs	= $this->tabs ?: [];
		$tab	= $GLOBALS['current_tab'] ?? '';

		if($tab){
			return $tabs[$tab] ?? null;
		}

		$tabs	= is_callable($tabs) ? $tabs($this->name) : $tabs;
		$tabs	= apply_filters(wpjam_get_filter_name($this->name, 'tabs'), $tabs);
		$result	= wpjam_map($tabs, fn($args, $name)=> self::add(array_merge($args, ['tab_slug'=>$name])));
		$tab	= sanitize_key(wpjam_get_parameter(...(wp_doing_ajax() ? ['current_tab', [], 'POST'] : ['tab'])));
		$tabs	= [];

		foreach(self::get_tabs($this->name, false) as $args){
			if((empty($args['network']) && is_network_admin()) || (!empty($args['capability']) && !current_user_can($args['capability']))){
				continue;
			}

			$object	= new self($args);
			$slug	= $object->tab_slug;
			$tab	= $tab ?: $slug;

			$object->admin_url	= $this->admin_url.'&tab='.$slug;

			if($object->query_data($tab == $slug)){
				$tabs[$slug]	= $object;
			}
		}

		$GLOBALS['current_tab']	= $tab;

		$this->tabs	= $tabs ?? [];

		if(empty($tabs)){
			$this->throw('Tabs 未设置');
		}

		$object	= $tabs[$tab] ?? null;

		if(!$object){
			$this->throw('无效的 Tab');
		}elseif(!$object->function){
			$this->throw('Tab 未设置 function');
		}elseif(!$object->function == 'tab'){
			$this->throw('Tab 不能嵌套 Tab');
		}

		return $object;
	}

	public function get_setting($key='', $tab=false){
		if(str_ends_with($key, '_name')){
			$tab		= $this->is_tab;
			$default	= $GLOBALS['plugin_page'];
		}else{
			$tab		= $tab ? $this->is_tab : false;
			$default	= null;
		}

		if($tab){
			try{
				$object	= $this->get_tab();
			}catch(Exception $e){
				return null;
			}
		}else{
			$object	= $this;
		}

		return $key ? ($object->$key ?: $default) : $object->to_array();
	}

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

	public static function set_current($menu){
		if($GLOBALS['plugin_page'] == $menu->menu_slug && ($menu->parent || (!$menu->parent && !$menu->subs))){
			return wpjam_var('plugin_page', new static(array_merge($menu->get_args(), ['name'=>$menu->menu_slug])));
		}
	}
}

class WPJAM_Builtin_Page{
	protected function __construct(){}

	public function __get($key){
		$screen	= get_current_screen();
		$object	= $screen->get_option('object');

		return $screen->$key ?? ($object ? $object->$key : null);
	}

	public function __call($method, $args){
		$object	= get_screen_option('object');

		if($object){
			return call_user_func([$object, $method], ...$args);
		}
	}

	public static function on_edit_form($post){	// 下面代码 copy 自 do_meta_boxes
		$meta_boxes	= $GLOBALS['wp_meta_boxes'][$post->post_type]['wpjam'] ?? [];
		$title		= wpjam_tag('ul');
		$content	= wpjam_tag('div', ['inside']);
		$count		= 0;

		foreach(wp_array_slice_assoc($meta_boxes, ['high', 'core', 'default', 'low']) as $_meta_boxes){
			foreach((array)$_meta_boxes as $meta_box){
				if(empty($meta_box['id']) || empty($meta_box['title'])){
					continue;
				}

				$count++;

				$title->append('li', [], wpjam_tag('a', ['class'=>'nav-tab', 'href'=>'#tab_'.$meta_box['id']], $meta_box['title']));
				$content->append('div', ['id'=>'tab_'.$meta_box['id']], wpjam_ob_get_contents($meta_box['callback'], $post, $meta_box));
			}
		}

		if(!$count){
			return;
		}

		if($count == 1){
			$title	= wpjam_tag('h2', ['hndle'], strip_tags($title))->wrap('div', ['postbox-header']);
		}else{
			$title->wrap('h2', ['nav-tab-wrapper']);
		}

		echo $title->after($content)->wrap('div', ['id'=>'wpjam', 'class'=>['postbox','tabs']])->wrap('div', ['id'=>'wpjam-sortables']);
	}

	public static function call_post_options($method, ...$args){
		$post_type	= get_screen_option('post_type');
		$options	= wpjam_get_post_options($post_type, ['list_table'=>false]);

		if($method == 'callback'){	// 只有 POST 方法提交才处理,自动草稿、自动保存和预览情况下不处理
			if($_SERVER['REQUEST_METHOD'] != 'POST'
				|| get_post_status($args[0]) == 'auto-draft'
				|| get_post_type($args[0]) != $post_type
				|| (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
				|| (!empty($_POST['wp-preview']) && $_POST['wp-preview'] == 'dopreview')
			){
				return;
			}

			wpjam_map($options, fn($object)=> wpjam_die_if_error($object->callback($args[0])));
		}else{
			if($args[0] != $post_type){
				return;
			}

			$context	= use_block_editor_for_post_type($post_type) ? 'normal' : 'wpjam';

			wpjam_map($options, fn($object)=> add_meta_box($object->name, $object->title, [$object, 'render'], $post_type, ($object->context ?: $context), $object->priority));
		}
	}

	public static function call_term_options($method, ...$args){
		$taxonomy	= get_screen_option('taxonomy');

		if($method == 'render'){
			$term	= array_shift($args);
			$action	= $term ? 'edit' : 'add';
			$args	= [($term ? $term->term_id : false), ['fields_type'=>($term ? 'tr' : 'div'), 'wrap_class'=>'form-field']];
		}elseif($method == 'validate'){
			$action	= 'add';
			$term	= array_shift($args);

			if(array_shift($args) != $taxonomy){
				return;
			}
		}elseif($method == 'callback'){
			$action	= $_POST['action'] == 'add-tag' ? 'add' : 'edit';
			
			if(get_term_taxonomy($args[0]) != $taxonomy){
				return;
			}
		}

		wpjam_map(wpjam_get_term_options($taxonomy, ['action'=>$action, 'list_table'=>false]), fn($object)=> wpjam_die_if_error(wpjam_catch([$object, $method], ...$args)));

		if($method == 'validate'){
			return $term;
		}
	}

	public static function init($screen){
		$base	= $screen->base;

		if(in_array($base, ['edit', 'upload', 'post', 'term', 'edit-tags'])){
			if(in_array($base, ['edit', 'upload', 'post'])){
				$typenow	= $GLOBALS['typenow'];
				$object		= wpjam_get_post_type_object($typenow);
			}elseif(in_array($base, ['term', 'edit-tags'])){
				$taxnow		= $GLOBALS['taxnow'];
				$object		= wpjam_get_taxonomy_object($taxnow);
			}

			if(!$object){
				return;
			}

			$screen->add_option('object', $object);
		}

		wpjam_admin_load('builtin_page', function($load, $screen){
			if(!empty($load['screen']) && is_callable($load['screen']) && !$load['screen']($screen)){
				return false;
			}

			if(wpjam_some(['base', 'post_type', 'taxonomy'], fn($k)=> !empty($load[$k]) && !wpjam_compare($screen->$k, $load[$k]))){
				return false;
			}

			return true;
		}, $screen);

		if(in_array($base, ['edit', 'upload'])){
			if($base == 'upload'){
				$mode	= get_user_option('media_library_mode', get_current_user_id()) ?: 'grid';

				if(isset($_GET['mode']) && in_array($_GET['mode'], ['grid', 'list'], true)){
					$mode	= $_GET['mode'];
				}

				if($mode == 'grid'){
					return;
				}
			}

			WPJAM_Posts_List_Table::load([
				'title'			=> $object->title,
				'model'			=> $object->model,
				'primary_key'	=> 'ID',
				'singular'		=> 'post',
				'capability'	=> fn($id)=> $id ? 'edit_post' : $object->cap->edit_posts,
				'data_type'		=> 'post_type',
				'meta_type'		=> 'post',
				'post_type'		=> $typenow,
				'builtin_class'	=> $typenow == 'attachment' ? 'WP_Media_List_Table' : 'WP_Posts_List_Table',
				'hook_part'		=> $typenow == 'attachment' ? ['media', 'media'] : ($object->hierarchical ? ['pages', 'page', 'posts'] : ['posts', 'post', 'posts'])
			]);
		}elseif($base == 'post'){
			$fragment	= parse_url(wp_get_referer(), PHP_URL_FRAGMENT);
			$label		= $object->labels->name;

			if(!in_array($typenow, ['post', 'page', 'attachment'])){
				add_filter('post_updated_messages',	fn($ms)=> $ms+[$typenow=> wpjam_map($ms['post'], fn($m)=> str_replace('文章', $label, $m))]);
			}

			if($fragment){
				add_filter('redirect_post_location', fn($location)=> $location.(parse_url($location, PHP_URL_FRAGMENT) ? '' : '#'.$fragment));
			}

			if($object->thumbnail_size){
				add_filter('admin_post_thumbnail_html', fn($content)=> $content.wpautop('尺寸:'.$object->thumbnail_size));
			}

			add_action(($typenow == 'page' ? 'edit_page_form' : 'edit_form_advanced'),	[self::class, 'on_edit_form'], 99);

			add_action('add_meta_boxes',		fn($post_type)=> self::call_post_options('render', $post_type));
			add_action('wp_after_insert_post',	fn($post_id)=> self::call_post_options('callback', $post_id), 999, 2);
		}elseif(in_array($base, ['term', 'edit-tags'])){
			$label	= $object->labels->name;

			if(!in_array($taxnow, ['post_tag', 'category'])){
				add_filter('term_updated_messages',	fn($ms)=> $ms+[$taxnow=> array_map(fn($m)=> str_replace(['项目', 'Item'], [$label, ucfirst($label)], $m), $ms['_item'])]);
			}

			if($base == 'edit-tags'){
				wpjam_map(['slug', 'description'], fn($k)=> $object->supports($k) ? null : wpjam_unregister_list_table_column($k));

				wpjam_unregister_list_table_action('inline hide-if-no-js');

				if(wp_doing_ajax()){
					if($_POST['action'] == 'add-tag'){
						add_filter('pre_insert_term',	fn($term, $taxonomy)=> self::call_term_options('validate', $term, $taxonomy), 10, 2);
						add_action('created_term',		fn($term_id)=> self::call_term_options('callback', $term_id));
					}
				}elseif(isset($_POST['action'])){
					if($_POST['action'] == 'editedtag'){
						add_action('edited_term',		fn($term_id)=> self::call_term_options('callback', $term_id));
					}
				}else{
					add_action($taxnow.'_add_form_fields',	fn()=> self::call_term_options('render'));
				}

				WPJAM_Terms_List_Table::load([
					'title'			=> $object->title,
					'capability'	=> $object->cap->edit_terms,
					'levels'		=> $object->levels,
					'hierarchical'	=> $object->hierarchical,
					'model'			=> $object->model,
					'sortable'		=> $object->sortable,
					'primary_key'	=> 'term_id',
					'singular'		=> 'tag',
					'data_type'		=> 'taxonomy',
					'meta_type'		=> 'term',
					'taxonomy'		=> $taxnow,
					'post_type'		=> $taxnow,
					'hook_part'		=> [$taxnow, $taxnow],
					'builtin_class'	=> 'WP_Terms_List_Table'
				]);
			}else{
				add_action($taxnow.'_edit_form_fields',	fn($term)=> self::call_term_options('render', $term));
			}
		}elseif($base == 'users'){
			WPJAM_Users_List_Table::load([
				'title'			=> '用户',
				'singular'		=> 'user',
				'capability'	=> 'edit_user',
				'data_type'		=> 'user',
				'meta_type'		=> 'user',
				'model'			=> 'WPJAM_User',
				'primary_key'	=> 'ID',
				'hook_part'		=> ['users', 'user', 'users'],
				'builtin_class'	=> 'WP_Users_List_Table'
			]);
		}
	}

	public static function load($screen){
		return new static($screen);
	}
}

class WPJAM_Chart extends WPJAM_Args{
	public function get_parameter($key){
		if(str_contains($key, 'timestamp')){
			return wpjam_strtotime($this->get_parameter(str_replace('timestamp', 'date', $key)).' '.(str_starts_with($key, 'end_') ? '23:59:59' : '00:00:00'));
		}

		$value	= wpjam_get_parameter($key, ['method'=>$this->method]);

		if($value){
			wpjam_set_cookie($key, $value, HOUR_IN_SECONDS);
		}else{
			$value	= $_COOKIE[$key] ?? null;
			$value	= $value ?: $this->get_default($key);
		}

		return $value;
	}

	protected function get_default($key){
		if($key == 'date_format' || $key == 'date_type'){
			return '%Y-%m-%d';
		}elseif($key == 'compare'){
			return 0;
		}elseif(str_contains($key, 'date')){
			if($key == 'start_date'){
				$ts	= time() - DAY_IN_SECONDS*30;
			}elseif($key == 'end_date'){
				$ts	= time();
			}elseif($key == 'date'){
				$ts	= time() - DAY_IN_SECONDS;
			}elseif($key == 'start_date_2'){
				$ts	= $this->get_parameter('end_timestamp_2') - ($this->get_parameter('end_timestamp') - $this->get_parameter('start_timestamp'));
			}elseif($key == 'end_date_2'){
				$ts	= $this->get_parameter('start_timestamp') - DAY_IN_SECONDS;
			}

			return wpjam_date('Y-m-d', $ts);
		}
	}

	public function render($wrap=true){
		if(!$this->show_form){
			return;
		}

		if($this->show_start_date){
			$fields['date_view']	= ['type'=>'view',	'value'=>'<span>日期:</span>'];
			$fields['start_date']	= ['type'=>'date',	'value'=>$this->get_parameter('start_date')];
			$fields['sep_view']		= ['type'=>'view',	'value'=>'<span>-</span>'];
			$fields['end_date']		= ['type'=>'date',	'value'=>$this->get_parameter('end_date')];
		}elseif($this->show_date){
			$fields['date']			= ['type'=>'date',	'value'=>$this->get_parameter('date')];
		}

		if($this->show_date_type){
			$fields['date_format']	= ['type'=>'select','value'=>$this->get_parameter('date_format'), 'options'=>[
				'%Y-%m'				=> '按月',
				'%Y-%m-%d'			=> '按天',
				// '%Y%U'			=> '按周',
				'%Y-%m-%d %H:00'	=> '按小时',
				'%Y-%m-%d %H:%i'	=> '按分钟',
			]];
		}elseif($this->show_date){
			$fields['date_format']	= ['type'=>'hidden','value'=>'%Y-%m-%d'];
		}

		$current	= wpjam_get_parameter('type', ['default'=>-1]);
		$current	= $current == 'all' ? '-1' : $current;

		if($this->show_compare){
			if($current !=-1 && $this->show_start_date){
				$fields['date_view_2']	= ['type'=>'view',		'value'=>'对比: '];
				$fields['start_date_2']	= ['type'=>'date',		'value'=>$this->get_parameter('start_date_2')];
				$fields['sep_view_2']	= ['type'=>'view',		'value'=>'-'];
				$fields['end_date_2']	= ['type'=>'date',		'value'=>$this->get_parameter('end_date_2')];
				$fields['compare']		= ['type'=>'checkbox',	'value'=>$this->get_parameter('compare')];
			}
		}

		if(!empty($fields)){
			if($wrap){
				$fields['chart_button']	= ['type'=>'submit', 'value'=>'显示', 'class'=>'button button-secondary'];
			}
		
			$fields	= apply_filters('wpjam_chart_fields', $fields);
			$fields	= wpjam_fields($fields)->render(['fields_type'=>'']);

			if($wrap){
				$action	= $GLOBALS['current_admin_url'];
				$action	= $current == -1 ? $action : $action.'&type='.$current;

				$fields->wrap('form', ['method'=>'POST', 'action'=>$action, 'id'=>'chart_form'])->after('div', ['clear']);
			}

			return $fields;
		}
	}

	public static function line($args=[], $type='Line'){
		$args	= wp_parse_args($args, [
			'data'			=> [],
			'labels'		=> [],
			'day_labels'	=> [],
			'day_key'		=> 'day',
			'chart_id'		=> 'daily-chart',
			'show_table'	=> true,
			'show_chart'	=> true,
			'show_sum'		=> true,
			'show_avg'		=> true,
		]);

		$labels	= array_filter($args['labels'], fn($v)=> !is_array($v) || array_get($v, 'show_in_chart') !== false);
		$labels	= wpjam_map($labels, fn($v)=> is_array($v) ? $v['label'] : $v);
		$cbs	= wpjam_map($args['labels'], fn($label)=> is_array($label) && !empty($label['callback']) ? $label['callback'] : '');
		$cbs	= array_filter($cbs);
		$parser	= fn($counts)=> $cbs ? array_merge($counts, wpjam_map($cbs, fn($cb)=> $cb($counts))) : $counts;
		$data	= $total = [];

		if($args['show_table']){
			$args['day_labels']	= wp_parse_args($args['day_labels'], ['sum'=>'累加', 'avg'=>'平均']);
			$args['columns']	= wpjam_map($args['labels'], fn($v)=> is_array($v) ? $v['label'] : $v);

			$thead	= wpjam_tag('thead')->append(self::row('head', [], $args));
			$tbody	= wpjam_tag('tbody');
		}

		foreach($args['data'] as $day => $counts){
			$counts	= $parser((array)$counts);
			$day	= $counts[$args['day_key']] ?? $day;
			$item 	= [$args['day_key']=> $args['day_labels'][$day] ?? $day];
			$item	= array_merge($item, wp_array_slice_assoc($counts, array_keys($labels)));

			foreach($args['labels'] as $key => $label){
				if(isset($counts[$key]) && is_numeric($counts[$key])){
					$total[$key]	??= 0;
					$total[$key]	+= $counts[$key];
				}	
			}

			if(strpos($day, '%') === false && strpos($day, '#') === false){
				$data[]	= $item;
			}

			if($args['show_table']){
				$tbody->append(self::row($day, (array)$counts, $args));
			}
		}

		$tag	= wpjam_tag();

		if($args['show_chart']){
			$options	= ['data'=>$data, 'xkey'=>$args['day_key'], 'ykeys'=>array_keys($labels), 'labels'=>array_values($labels)];

			$tag->append('div', ['class'=>'chart', 'id'=>$args['chart_id'], 'data'=>['type'=>$type, 'options'=>$options]]);
		}

		if($args['show_table'] && $args['data']){
			$total	= $parser($total);

			if($args['show_sum']){
				$tbody->append(self::row('sum', $total, $args));
			}

			if($args['show_avg']){
				$num	= count($args['data']);
				$avg	= $parser(array_map(fn($v)=> is_numeric($v) ? round($v/$num) : '', $total));

				$tbody->append(self::row('avg', $avg, $args));
			}

			$thead->after($tbody)->wrap('table', ['class'=>'wp-list-table widefat striped'])->append_to($tag);
		}

		return $tag;
	}

	public static function donut($args=[]){
		$args	= wp_parse_args($args, [
			'data'			=> [],
			'total'			=> 0,
			'title'			=> '名称',
			'key'			=> 'type',
			'chart_id'		=> '',
			'total_link'	=> $GLOBALS['current_admin_url'],
			'show_table'	=> true,
			'show_chart'	=> true,
			'show_line_num'	=> false,
			'show_link'		=> false,
			'labels'		=> []
		]);

		if($args['show_table']){
			$thead	= wpjam_tag('thead')->append(self::row('head', '', $args));
			$tbody	= wpjam_tag('tbody');
		}

		foreach(array_values($args['data']) as $i => $item){
			$label 	= $item['label'] ?? '/';
			$link	= $args['show_link'] ? $args['total_link'].'&'.$args['key'].'='.($item[$args['key']] ?? $label) : '';
			$label 	= $args['labels'][$label] ?? $label;
			$value	= $item['count'];
			$data[]	= ['label'=>$label, 'value'=>$value];

			if($args['show_table']){
				$tbody->append(self::row($i+1, $value, array_merge($args, ['label'=>$label, 'link'=>$link])));
			}
		}

		$tag	= wpjam_tag();

		if($args['show_chart']){
			$chart_id	= $args['chart_id'] ?: 'chart_'.wp_generate_password(6, false, false);

			$tag->append('div', ['class'=>'chart', 'id'=>$chart_id, 'data'=>['options'=>['data'=>$data], 'type'=>'Donut']]);
		}

		if($args['show_table']){
			if($args['total']){
				$link	= $args['show_link'] ? $args['total_link'] : '';

				$tbody->append(self::row('total', $args['total'], array_merge($args, ['label'=>'所有', 'link'=>$link])));
			}

			$thead->after($tbody)->wrap('table', ['wp-list-table', 'widefat', 'striped'])->append_to($tag);
		}

		return $tag->wrap('div', ['class'=>'donut-chart-wrap']);
	}

	protected static function row($key, $data=[], $args=[]){
		$row	= wpjam_tag('tr');

		if(is_array($data)){
			$day_label	= $args['day_label'] ?? '时间';
			$day_key	= $args['day_key'];

			if($key == 'head'){
				$row->append($day_label, 'th', ['scope'=>'col', 'id'=>$day_key, 'class'=>['column-'.$day_key, 'column-primary']]);
			}else{
				$toggle	= wpjam_tag('button', ['type'=>'button', 'class'=>'toggle-row'], ['显示详情', 'span', ['screen-reader-text']]);

				$row->append(($args['day_labels'][$key] ?? $key).$toggle, 'td', ['class'=>['column-'.$day_key, 'column-primary'],  'data-colname'=>$day_label]);
			}

			foreach($args['columns'] as $col => $column){
				if($key == 'head'){
					$row->append($column, 'th', ['scope'=>'col', 'id'=>$col, 'class'=>['column-'.$col]]);
				}else{
					$row->append(($data[$col] ?? ''), 'td', ['class'=>['column-'.$col], 'data-colname'=>$column]);
				}
			}
		}else{
			if($key === 'head'){
				if($args['show_line_num']){
					$row->append('排名', 'th', ['style'=>'width:40px;']);
				}

				$row->append($args['title'], 'th')->append('数量', 'th');

				if($args['total']){
					$row->append('比例', 'th');
				}
			}else{
				if($args['show_line_num']){
					$row->append($key == 'total' ? '' : $key, 'td');
				}

				$row->append(($args['link'] ? '<a href="'.$args['link'].'">'.$args['label'].'</a>' : $args['label']), 'td')->append($data, 'td');

				if($args['total']){
					$row->append(round($data/$args['total']*100, 2).'%', 'td');
				}
			}
		}

		return $row;
	}

	public static function create_instance(){
		$offset	= (int)get_option('gmt_offset');
		$offset	= $offset >= 0 ? '+'.$offset.':00' : $offset.':00';

		$GLOBALS['wpdb']->query("SET time_zone = '{$offset}';");

		wpjam_style('morris',	wpjam_get_static_cdn().'/morris.js/0.5.1/morris.css');
		wpjam_script('raphael',	wpjam_get_static_cdn().'/raphael/2.3.0/raphael.min.js');
		wpjam_script('morris',	wpjam_get_static_cdn().'/morris.js/0.5.1/morris.min.js');

		return new self([
			'method'			=> 'POST',
			'show_form'			=> true,
			'show_start_date'	=> true,
			'show_date'			=> true,
			'show_date_type'	=> false,
			'show_compare'		=> false
		]);
	}

	public static function get_instance($args=[]){
		$object = wpjam_get_instance('chart_form', 'object', fn()=> self::create_instance());
		$args	= is_array($args) ? $args : [];

		return $args ? $object->update_args($args) : $object;
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit