File: /www/wwwroot/www.sceytyg.com/wp-content/plugins/wpjam-basic/includes/class-wpjam-admin.php
<?php
class WPJAM_Admin extends WPJAM_Args{
public function prefix(){
return is_network_admin() ? 'network_' : (is_user_admin() ? 'user_' : '');
}
public function url($path=''){
return ($this->prefix().'admin_url')($path);
}
public function error($msg='', $type='success'){
if($msg === ''){
return wpjam_map($this->error ?: [], fn($e)=> wpjam_echo(wpjam_tag('div', ['is-dismissible', 'notice', 'notice-'.$e['type']], ['p', [], $e['msg']])));
}
if(is_wp_error($msg)){
$msg = $msg->get_error_message();
$type = 'error';
}
$msg && $type && $this->update_arg('error[]', compact('msg', 'type'));
}
public function enqueue(){
$ver = get_plugin_data(WPJAM_BASIC_PLUGIN_FILE)['Version'];
$static = wpjam_url(dirname(__DIR__), 'relative').'/static';
wp_enqueue_media(wpjam_pick($this, ['post_id'])); // 引入 jquery-ui-sortable
wp_enqueue_script('wpjam-script', $static.'/script.js', ['wp-color-picker', 'jquery-ui-autocomplete', ...(wp_is_mobile() ? ['jquery-touch-punch'] : [])], $ver);
wp_localize_script('wpjam-script', 'wpjam_page_setting', array_map('maybe_closure', $this->vars)+[
'upload_files' => current_user_can('upload_files'),
'admin_url' => $GLOBALS['current_admin_url'],
'loading' => includes_url('js/thickbox/loadingAnimation.gif'),
]+$this->pick(['query_data', 'query_url', 'block']));
$this->block && wp_enqueue_script('wpjam-blocks', $static.'/blocks.js', ['wp-plugins', 'wp-editor', 'wp-element', 'wp-components', 'wp-data'], '1.0', true);
wp_enqueue_style('wpjam-style', $static.'/style.css', ['wp-color-picker', 'editor-buttons'], $ver);
$this->style && wp_add_inline_style('wpjam-style', "\n".implode("\n\n", array_filter($this->style)));
$this->script && wp_add_inline_script('wpjam-script', "jQuery(function($){".preg_replace('/^/m', "\t", "\n".implode("\n\n", $this->script))."\n});");
}
public function load(...$args){
if(count($args) >= 2){
$map = ['builtin_page'=>'base', 'plugin_page'=>'plugin_page'];
$type = $args[0] ?? array_find_key($map, fn($v)=> isset($args[1][$v]));
return isset($map[$type]) && $this->update_arg($type.'_load[]', $args[1]);
}
if($screen = array_shift($args)){
$this->screen = $screen;
$this->vars = ($this->vars ?? [])+['screen_id'=>$screen->id]+array_filter(wpjam_pick($screen, ['post_type', 'taxonomy']));
$screen->base == 'post' && ($this->post_id = (int)($_GET['post'] ?? ($_POST['post_ID'] ?? 0)));
}
if($this->plugin_page){
$type = 'plugin_page';
$object = $this->current_tab ?: $this->plugin_page;
$args = [$object->menu_slug, $this->current_tab ? $object->tab_slug : ''];
$this->vars += ['plugin_page'=>$args[0]]+($args[1] ? ['current_tab'=>$args[1]] : []);
if(!$args[1] && $screen && str_contains($screen->id, '%')){
$screen->id = preg_replace_callback('/^(.*?)(_page_.*)/', fn($m)=> (array_search($m[1], $GLOBALS['admin_page_hooks']) ?: $m[1]).$m[2], $screen->id);
}
}else{
if($screen->base == 'customize' || !empty($GLOBALS['plugin_page'])){
return;
}
$type = 'builtin_page';
$args = [$screen];
$page = $this->$type = wpjam_get_post_parameter($type) ?: $GLOBALS['pagenow'];
$url = add_query_arg(array_intersect_key($_REQUEST, wpjam_pick($this->vars, ['taxonomy', 'post_type'])), $this->url($page));
$GLOBALS['current_admin_url'] = $url;
$this->vars += [$type=>$page];
if(in_array($screen->base, ['edit', 'upload', 'post'])){
if(!($this->type_object = wpjam_get_post_type_object($GLOBALS['typenow']))){
return;
}
}elseif(in_array($screen->base, ['term', 'edit-tags'])){
if(!($this->tax_object = wpjam_get_taxonomy($GLOBALS['taxnow']))){
return;
}
}
}
do_action('wpjam_'.$type.'_load', ...$args); // 兼容
foreach(wpjam_sort(array_filter($this->get_arg($type.'_load[]'), function($load){
if($this->plugin_page){
$page = $this->plugin_page->name;
$tab = ($tab = $this->current_tab) ? $tab->name : '';
$pv = $load['plugin_page'] ?? '';
$tv = $load['current_tab'] ?? '';
if($pv && is_callable($pv)){
return $pv($page, $tab);
}
if($pv && !wpjam_compare($page, $pv)){
return false;
}
return $tv ? ($tab && wpjam_compare($tab, $tv)) : !$tab;
}else{
if(($sv = $load['screen'] ?? '') && is_callable($sv) && !$sv($this->screen)){
return false;
}
return array_all(['base', 'post_type', 'taxonomy'], fn($k) => empty($load[$k]) || wpjam_compare($this->screen->$k, $load[$k]));
}
}), 'order', 'desc', 10) as $load){
wpjam_map((array)($load['page_file'] ?? []), fn($file)=> is_file($file) && include $file);
wpjam_call(wpjam_get($load, 'callback') ?: [($model = $load['model'] ?? ''), array_find(['load', $type.'_load'], fn($m)=> method_exists($model, $m))], ...$args);
}
wpjam_trap($this->plugin_page ? [$object, 'load'] : [$this, 'builtin'], $screen, fn($e)=> $this->error($e));
add_action('admin_enqueue_scripts', [$this, 'enqueue'], 9);
add_action('all_admin_notices', [$this, 'error'], 9);
}
public function builtin(){
$base = $this->screen->base;
$type = $GLOBALS['typenow'];
$tax = $GLOBALS['taxnow'];
if($base == 'edit'
|| ($base == 'upload' && (wpjam_get_parameter('mode') ?: get_user_option('media_library_mode', get_current_user_id())) === 'list')
){
$object = wpjam_admin('type_object');
WPJAM_Builtin_List_Table::load(wpjam_pick($object, ['title', 'model', 'hierarchical'])+[
'capability' => fn($id)=> $id ? 'edit_post' : $object->cap->edit_posts,
'primary_key' => 'ID',
'singular' => 'post',
'data_type' => 'post_type',
'meta_type' => 'post',
'post_type' => $type,
]);
}elseif($base == 'post'){
$object = wpjam_admin('type_object');
$size = $object->thumbnail_size;
$label = $object->_jam ? $object->labels->name : '';
$size && add_filter('admin_post_thumbnail_html', fn($content)=> $content.wpautop('尺寸:'.$size));
$label && add_filter('post_updated_messages', fn($ms)=> $ms+[$type=> array_map(fn($m)=> str_replace('文章', $label, $m), $ms['post'])]);
$options = wpjam_get_post_options($type, ['list_table'=>false]);
if(use_block_editor_for_post_type($type) && post_type_supports($type, 'custom-fields')){
foreach($options as $name => $option){
if(($option->modal || $option->context === 'side') && wpjam_is_assoc_array($option->fields)){
$fields = wpjam_fields($option->fields)->to_block();
$fields && $this->update_arg('block[panels][]', wpjam_pick($option, ['title', 'modal'])+compact('name', 'fields'));
unset($options[$name]);
}
}
}
if($options){
$this->meta_options = $options;
add_action('add_meta_boxes', [$this, 'meta_options'], 10, 2);
add_action('wp_after_insert_post', [$this, 'meta_options'], 999);
}
}elseif(in_array($base, ['term', 'edit-tags'])){
$object = wpjam_admin('tax_object');
$label = $object->_jam ? $object->labels->name : '';
$label && add_filter('term_updated_messages', fn($ms)=> $ms+[$tax=> array_map(fn($m)=> str_replace(['项目', 'Item'], [$label, ucfirst($label)], $m), $ms['_item'])]);
$this->meta_options = wpjam_get_term_options($tax, ['list_table'=>false]);
add_action($tax.'_'.($base == 'term' ? 'edit' : 'add').'_form_fields', [$this, 'meta_options'], 10, 2);
if($base == 'edit-tags'){
wpjam_map(['slug', 'description'], fn($sup)=> $object->supports($sup) || wpjam_admin('removed_columns[]', $sup));
wpjam_admin('removed_actions[]', 'inline hide-if-no-js');
wpjam_hook('tap', 'pre_insert_term', [$this, 'meta_options'], 10, 2);
add_action((wp_doing_ajax() ? 'created' : 'edited').'_term', [$this, 'meta_options']);
WPJAM_Builtin_List_Table::load(wpjam_pick($object, ['title','model', 'hierarchical', 'levels', 'sortable'])+[
'capability' => $object->cap->edit_terms,
'primary_key' => 'term_id',
'singular' => 'tag',
'data_type' => 'taxonomy',
'meta_type' => 'term',
'taxonomy' => $tax,
'post_type' => $type,
]);
}
}elseif($base == 'users'){
WPJAM_Builtin_List_Table::load([
'title' => '用户',
'model' => 'WPJAM_User',
'capability' => 'edit_user',
'primary_key' => 'ID',
'singular' => 'user',
'data_type' => 'user',
'meta_type' => 'user',
]);
}
}
public function meta_options(...$args){
$hook = current_filter();
$base = $this->screen->base;
$options = $this->meta_options;
if($base == 'post'){
if($hook === 'add_meta_boxes'){
$type = $args[0];
$pid = $args[1]->ID;
$id = $this->screen->action == 'add' ? false : $pid;
}else{
$action = 'callback';
$pid = $args[0];
$type = get_post_type($pid);
// 非 POST / 自动草稿 / 自动保存 / 预览情况下不处理
if($_SERVER['REQUEST_METHOD'] != 'POST' || get_post_status($pid) == 'auto-draft' || (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) || (($_POST['wp-preview'] ?? '') == 'dopreview')){
return;
}
}
}else{
if($hook == 'pre_insert_term'){
$action = 'validate';
$tax = $args[1];
$args = [];
}elseif(str_ends_with($hook, '_form_fields')){
$action = 'render';
$i = str_contains($hook, 'edit') ? 1 : 0;
$tax = $args[$i];
$args = [($i ? $args[0]->term_id : false), ['fields_type'=>($i ? 'tr' : 'div'), 'wrap_class'=>'form-field']];
}else{
$action = 'callback';
$tax = get_term_field('taxonomy', $args[0]);
}
$options = wpjam_filter($options, ['action'=>['value'=>str_contains($hook, 'edit') ? 'edit' : 'add', 'if_null'=>true]]);
}
foreach($options as $option){
if($base == 'post'){
$context = $option->context ?: 'normal';
if(!($cb = $option->meta_box_cb)){
if(!($option->fields = $option->get_fields($pid))){
continue;
}
$cb = fn()=> $option->render($id, ['fields_type'=>($context === 'side' ? 'list' : 'table')]);
}
}
if($hook === 'add_meta_boxes'){
add_meta_box($option->name, $option->title, $cb, $type, $context, $option->priority);
}else{
wpjam_trap([$option, $action], ...[...$args, 'die']);
}
}
}
public function init(){
if($GLOBALS['pagenow'] == 'admin-post.php'){
return;
}
if(wp_doing_ajax()){
wpjam_ajax('wpjam-page-action', [
'admin' => true,
'nonce_action' => fn($data)=> $data['page_action'] ?? '',
'callback' => fn($data)=> ($object = wpjam_get_page_action($data['page_action'] ?? '')) ? $object($data['action_type']) : wpjam_page_action_compat($data),
]);
wpjam_ajax('wpjam-query', [
'admin' => true,
'fields' => ['data_type'=> ['required'=>true]],
'callback' => fn($data)=> ['items'=> wpjam_try([wpjam_get_data_type($data['data_type'], $data['query_args']), isset($data['include']) ? 'query_label' : 'query_items'], $data['include'] ?? $data['query_args'])]
]);
wpjam_ajax('wpjam-upload', [
'admin' => true,
'nonce_action' => fn($data)=> 'upload-'.$data['name'].'-'.$data['accept'],
'callback' => fn($data)=> wpjam_upload($data['filename'] ?? $data['name'], $data)
]);
$screen = $_POST['screen_id'] ?? ($_POST['screen'] ?? '');
if(!$screen){
return;
}
$page = $GLOBALS['plugin_page'] = $_POST['plugin_page'] ?? '';
$part = $page ? wpjam_at($screen, '_page_'.$page, 1) : $screen;
$type = array_find(['network', 'user'], fn($v)=> str_ends_with($part, '-'.$v));
$const = $type ? 'WP_'.strtoupper($type).'_ADMIN' : '';
$const && !defined($const) && define($const, true);
$screen == 'upload' && ([$GLOBALS['hook_suffix'], $screen] = [$screen, '']);
}
do_action('wpjam_admin_init');
add_action('current_screen', [$this, 'load'], 9);
$menu = new WPJAM_Plugin_Page();
$pages = apply_filters('wpjam_pages', $this->pages ?: []);
if(wp_doing_ajax()){
$page && ($slug = array_find_key($pages, fn($v, $k)=> $page == $k || isset($v['subs'][$page]))) && $menu->parse($pages[$slug], $page, $slug);
set_current_screen($screen);
}else{
wpjam_map($pages, [$menu, 'parse']);
}
}
public static function get_instance(){
static $object;
if(!isset($object)){
$object = new self();
WPJAM_Notice::init();
if(wp_doing_ajax()){
add_action('admin_init', [$object, 'init'], 9);
}else{
add_action($object->prefix().'admin_menu', [$object, 'init'], 9);
add_filter('wpjam_html', fn($html)=> str_replace(
['dashicons-before dashicons-ri-', '/wp-admin/page='],
['ri-', '/wp-admin/admin.php?page='],
$html
));
}
}
return $object;
}
}
class WPJAM_Plugin_Page extends WPJAM_Args{
private $builtins = [];
public function __get($key){
$value = parent::__get($key);
if($key == 'name'){
return $this->tab_slug ?: $this->menu_slug;
}elseif($key == 'type'){
return ($value ??= $this->function) == 'list' ? 'list_table' : (in_array($value, ['option', 'list_table', 'form', 'dashboard', 'tab']) ? $value : '');
}
return $value;
}
public function get_sub($slug){
return $this->pull('subs['.$slug.']') ?: array_filter(['menu_title'=>$this->sub_title])+wpjam_except($this->args, ['position', 'subs', 'page_title']);
}
public function parse($args, $slug, $parent=''){
if(wp_doing_ajax()){
$this->args = $args;
[$args, $parent] = $this->subs ? [$this->get_sub($slug), $parent] : [$args, ''];
}elseif(!$this->builtins){
$map = ['appearance'=>'themes', 'settings'=>'options', 'profile'=>'users'];
$this->builtins = wpjam_reduce($GLOBALS['admin_page_hooks'], fn($c, $v, $k)=> $c+[(str_starts_with($k, 'edit.php?') && $v != 'pages') ? wpjam_get_post_type_setting($v, 'plural') : $v => $k]+(isset($map[$v]) ? [$map[$v]=>$k] : []), []);
}
$this->args = $args+['menu_slug'=>$slug];
$slug = $this->menu_slug;
$builtin = $parent ? '' : ($this->builtins[$slug] ?? '');
if(!$builtin){
$path = str_contains($parent, '.php') ? $parent : 'admin.php';
if(!$this->menu_title || !$this->is_available(['network'=>$this->pull('network', ($path == 'admin.php'))])){
return;
}
$this->page_title ??= $this->menu_title;
$this->capability ??= 'manage_options';
if(!str_contains($slug, '.php')){
$this->admin_url = add_query_arg(['page'=>$slug], $path);
if(!$this->query_data()){
return;
}
$cb = '__return_true';
}else{
str_starts_with($slug, $GLOBALS['pagenow']) && array_all(wp_parse_args(parse_url($slug, PHP_URL_QUERY)), fn($v, $k)=> $v == wpjam_get_parameter($k)) && add_filter('parent_file', fn()=> $parent ?: $slug);
}
$object = ($this->is_current() && ($parent || (!$parent && !$this->subs))) ? wpjam_admin('plugin_page', wp_clone($this)) : null;
$args = [$this->page_title, $this->menu_title, $this->capability, $slug, ($object ? [$object, 'render'] : ($cb ?? null)), $this->position];
$icon = $parent ? '' : ($this->icon ? (str_starts_with($this->icon, 'dashicons-') ? '' : 'dashicons-').$this->icon : '');
$hook = $parent ? add_submenu_page($parent, ...$args) : add_menu_page(...wpjam_add_at($args, -1, $icon));
$object && wpjam_admin('page_hook', $hook);
}
if(!$parent && ($builtin || $this->subs)){
$subs = ($builtin ? [] : [$slug=>$this->get_sub($slug)])+wpjam_sort($this->subs, fn($v)=> ($v['order'] ?? 10) - ($v['position'] ?? 10)*1000);
wpjam_map($subs, fn($sub, $s)=> $this->parse($sub, $s, $builtin ?: $slug));
}
}
private function is_available($args){
return array_all([
'network' => fn($v)=> is_network_admin() ? (bool)$v : $v !== 'only',
'capability' => fn($v)=> current_user_can($v),
'plugin_page' => fn($v)=> $v == $this->menu_slug
], fn($cb, $k)=> isset($args[$k]) ? $cb($args[$k]) : true);
}
public function is_current(){
return ($this->tab_slug ? $GLOBALS['current_tab'] : $GLOBALS['plugin_page']) == $this->name;
}
public function query_data(){
if($this->query_args){
$query_data = wpjam_get_data_parameter($this->query_args);
$null_data = array_filter($query_data, fn($v)=> is_null($v));
if($null_data){
return $this->is_current() ? wp_die('「'.implode('」,「', array_keys($null_data)).'」参数无法获取') : false;
}
wpjam_admin('query_url[]', [$this->admin_url, ($this->admin_url = add_query_arg($query_data, $this->admin_url))]);
$this->is_current() && wpjam_admin('query_data', $query_data);
}
return true;
}
private function throw($title){
wpjam_throw('error', $title);
}
private function include(){
$cb = $this->pull('load_callback');
$cb && !is_callable($cb) && $this->include(); // load_callback 优先文件加载,如不存在,尝试先加载文件
$cb && wpjam_call($cb, $this->name);
wpjam_map((array)$this->pull(($this->tab_slug ? 'tab' : 'page').'_file'), fn($f)=> include($f));
}
private function defaults($defaults=null){
($defaults ??= $this->defaults) && is_array($defaults) && wpjam_default($defaults);
}
public function load(){
$this->defaults();
$this->include();
do_action('wpjam_plugin_page', $this, ($type = $this->type));
if($type && $type != 'tab'){
$name = $this->{$type.'_name'} ?: $this->menu_slug;
$object = $this->page_object($type, $name, 'preprocess');
}
if($this->data_type){
$data_type = wpjam_admin('data_type', $this->data_type);
$dt_object = wpjam_get_data_type($data_type, $this->args);
$dt_object && $dt_object->meta_type && wpjam_admin('meta_type', $dt_object->meta_type);
in_array($data_type, ['post_type', 'taxonomy']) && $this->$data_type && !wpjam_admin('screen', $data_type) && wpjam_admin('screen', $data_type, $this->$data_type);
}
$this->actions && wpjam_map($this->actions, 'wpjam_register_page_action', 'kv');
$this->chart && wpjam_chart(is_array($this->chart) ? $this->chart : []);
$this->editor && add_action('admin_footer', 'wp_enqueue_editor');
$GLOBALS['current_admin_url'] = wpjam_admin('url', $this->admin_url);
if($type == 'tab'){
$GLOBALS['current_tab'] = wpjam_get_parameter(...(wp_doing_ajax() ? ['current_tab', [], 'POST'] : ['tab'])) ?: null;
$tabs = $this->get_arg('tabs', [], 'callback');
$tabs = wpjam_reduce(wpjam_admin('tabs[]'), fn($c, $v, $k)=> $this->is_available($v) ? $c+[$v['tab_slug']=>$v] : $c, $tabs);
$tabs = wpjam_array(wpjam_sort($tabs, 'order', 'desc', 10), fn($s, $tab)=> ($tab = new self(['tab_slug'=>$s, 'admin_url'=>$this->admin_url.'&tab='.$s]+$tab+['capability'=>$this->capability]))->query_data() ? [$s, $tab, $GLOBALS['current_tab'] ??= $s] : null);
$object = $tabs ? ($tabs[$GLOBALS['current_tab']] ?? $this->throw('无效的 Tab')) : $this->throw('Tabs 未设置');
$object->type || $object->function || $this->throw('Tab 未设置 function');
$object->type == 'tab' && $this->throw('Tab 不能嵌套 Tab');
$object->menu_slug = $this->menu_slug;
if(!wp_doing_ajax()){
$this->tabs = $tabs;
$this->render = [$object, 'render'];
}
wpjam_admin('current_tab', $object);
wpjam_admin('load');
}elseif($type){
$object ??= $this->page_object($type, $name);
if($type == 'dashboard'){
$load = fn()=> wpjam_dashboard('load');
$render = fn()=> wpjam_dashboard('render');
}else{
$load = [$object, 'page_load'];
$load = method_exists(...$load) ? $load : null;
$render = [$object, 'render'];
}
$load && (wp_doing_ajax() ? $load() : add_action('load-'.wpjam_admin('page_hook'), $load));
$this->render = $render;
$this->page_title = $object->title ?: $this->page_title;
$this->summary = $this->summary ?: $object->get_arg('summary');
$object->query_args && wpjam_admin('query_data', wpjam_get_data_parameter($object->query_args));
}else{
$function = $this->function ?: wpjam_get_filter_name($this->name, 'page');
$this->render = is_callable($function) ? fn()=> wpjam_chart('render').wpjam_ob($function) : $this->throw('页面函数'.'「'.$function.'」未定义。');
}
}
private function page_object($type, $name, $step=''){
$object = null;
$args = $this->$type;
if($step == 'preprocess'){
if($type == 'option'){
$object = WPJAM_Option_Setting::get($name);
$object = $object ? $object->get_current() : null;
}elseif($type == 'form'){
$object = wpjam_get_page_action($name);
}
if($object){
$args = $object->to_array();
}else{
$model = in_array($type, ['list_table', 'form']) ? ($args ? (is_string($args) ? $args : '') : $this->model) : '';
$cb = $model && class_exists($model) ? [$model, 'get_'.$type] : '';
$args = $cb && method_exists(...$cb) ? $cb($this) : $args;
$args = wpjam_is_assoc_array($args) ? $args+$this->pick(['model']) : $args;
$args && ($args = $this->$type = maybe_callback($args, $this));
}
if(is_array($args)){
!empty($args['meta_type']) && wpjam_admin('meta_type', $args['meta_type']);
$this->update_args(WPJAM_Data_Type::prepare($args));
}
return $object;
}
if(!$args){
array_any(['form'=>['callback'], 'option'=>['sections', 'fields'], 'dashboard'=>['widgets'], 'list_table'=>['model']][$type], fn($k)=> $this->$k) || $this->throw($type.'「'.$name.'」未定义。');
$args = $type == 'list_table' ? wpjam_except($this->to_array(), 'defaults') : $this->to_array();
}
if($type == 'option'){
return wpjam_register_option($name, $args)->get_current();
}elseif($type == 'form'){
return wpjam_register_page_action($name, $args);
}elseif($type == 'dashboard'){
return wpjam_dashboard(['name'=>$name]+$args);
}
$model = $args['model'] ?? '';
(!$model || !class_exists($model)) && $this->throw('List Table Model'.'「'.$model.'」未定义。');
$this->defaults($args['defaults'] ?? []);
wpjam_map(['admin_head', 'admin_footer'], fn($v)=> method_exists($model, $v) && add_action($v, [$model, $v]));
return new WPJAM_List_Table($args+array_filter([
'layout' => 'table',
'name' => $name,
'singular' => $name,
'plural' => $name.'s',
'capability'=> $this->capability ?: 'manage_options',
'sortable' => $this->sortable,
'data_type' => 'model',
'per_page' => 50
]));
}
public function render(){
$sum = maybe_callback($this->summary, $this->menu_slug, $this->tab_slug ?: '');
$sum = !$sum || is_array($sum) ? '' : (is_file($sum) ? wpjam_get_file_data($sum, 'summary') : $sum);
$tag = wpjam_tag('h1', ['wp-heading-inline'], ($this->page_title ?? $this->title))->after([
['hr', ['wp-header-end']],
$sum ? ['p', ['summary'], $sum] : '',
...($this->type == 'tab' ? [
wpjam_ob(wpjam_get_filter_name($this->menu_slug, 'page')) ?: '', // 所有 Tab 页面都执行的函数
wpjam_tag('nav', ['nav-tab-wrapper', 'wp-clearfix'])->append(array_map(fn($tab)=> ['a', ['class'=>['nav-tab', $tab->is_current() ? 'nav-tab-active' : ''], 'href'=>$tab->admin_url], ($tab->tab_title ?: $tab->title)], $this->tabs))
] : []),
wpjam_call($this->render, $this)
]);
return $this->tab_slug ? $tag->tag('h2') : wpjam_echo($tag->wrap('div', ['wrap']));
}
}
class WPJAM_Page_Action extends WPJAM_Args{
public function __invoke($type=''){
if($type == 'form'){
$title = wpjam_get_post_parameter('page_title');
$key = $title ? '' : array_find(['page_title', 'button_text', 'submit_text'], fn($k)=> $this->$k && !is_array($this->$k));
return [
'type' => 'form',
'form' => $this->get_form(),
'width' => (int)$this->width,
'modal_id' => $this->modal_id,
'page_title' => $key ? $this->$key : $title
];
}
$this->is_allowed($type) || wp_die('access_denied');
$args = $this->validate ? [$this->get_fields()->get_parameter('data')] : [];
$submit = $type == 'submit' ? (wpjam_get_post_parameter('submit_name') ?: $this->name) : '';
$button = $submit ? wpjam_button($this, $submit) : [];
$type = $button['response'] ?? ($this->response ?? $this->name);
$cb = $button['callback'] ?? $this->callback;
$result = ($cb ? wpjam_try($cb, ...[...$args, $submit ?: $this->name]) : null) ?? wpjam_throw('invalid_callback');
$result = is_array($result) ? wpjam_notice($result)+$result : (is_string($result) ? [($type == 'redirect' ? 'url' : 'data')=>$result] : []);
$result += ['type'=>$type]+($this->dismiss ? ['dismiss'=>true] : []);
$result += $result['type'] == 'redirect' ? ['target'=>$this->target ?: '_self'] : [];
return apply_filters('wpjam_ajax_response', $result);
}
public function is_allowed($type=''){
return wpjam_can(($this->capability ?? ($type ? 'manage_options' : '')), $this->name);
}
public function render(){
return wpjam_trap([$this, 'get_form'], 'die');
}
public function get_button($args=[]){
if($this->is_allowed()){
$text = $this->update_args(wpjam_except($args, 'data'))->button_text ?? '保存';
return wpjam_tag(($this->tag ?: 'a'), [
'title' => $this->page_title ?: $text,
'style' => $this->style,
'class' => $this->class ?? 'button-primary large',
'data' => ['action'=>$this->name, 'nonce'=>wp_create_nonce($this->name)]+$this->pick(['direct', 'confirm'])+[
'title' => $this->page_title ?: $this->button_text,
'data' => wp_parse_args($args['data'] ?? [], ($this->data ?: [])),
]
], $text)->add_class('wpjam-button');
}
}
public function get_form(){
if($this->is_allowed()){
return $this->get_fields()->wrap('form', [
'id' => $this->form_id ?: 'wpjam_form',
'button' => wpjam_button($this),
'data' => ['action'=>$this->name, 'nonce'=>wp_create_nonce($this->name)]
]);
}
}
public function get_fields(){
$fields = wpjam_try('maybe_callback', $this->fields, $this->name) ?: [];
$data = is_callable($this->data_callback) ? wpjam_try($this->data_callback, $this->name, $fields) : [];
return wpjam_fields($fields, wpjam_merge($this->args, ['data'=>$data ?: []]));
}
}
class WPJAM_Chart extends WPJAM_Args{
public function validate($data){
$fields = wpjam_fields($this->get_fields(['data'=>$data]));
return $fields->validate($data+$fields->get_defaults());
}
public function render($wrap=true){
if(!$this->show_form){
return;
}
$fields = $this->get_fields(['show_title'=>$this->show_compare]);
if($this->show_compare){
$current = wpjam_get_parameter('type', ['default'=>-1]);
$current = $current == 'all' ? '-1' : $current;
if($current !=-1 && $this->show_start_date){
$fields['compare_date'] = ['before'=>'对比:', 'sep'=>' ', 'fields'=>[
'start_date_2' => ['type'=>'date', 'value'=>$this->get_parameter('start_date_2')],
'sep_view_2' => ['type'=>'view', 'value'=>'-'],
'end_date_2' => ['type'=>'date', 'value'=>$this->get_parameter('end_date_2')],
'compare' => ['type'=>'checkbox', 'value'=>$this->get_parameter('compare')],
]];
}
}
if($fields){
$fields = apply_filters('wpjam_chart_fields', $fields);
$fields += $wrap ? ['chart_button'=>['type'=>'submit', 'value'=>'显示', 'class'=>'button button-secondary']] : [];
$fields = wpjam_fields($fields)->render(['fields_type'=>'']);
if($wrap){
$action = $GLOBALS['current_admin_url'];
$action .= ($this->show_compare && $current != -1) ? '&type='.$current : '';
$fields->wrap('form', ['method'=>'POST', 'action'=>$action, 'id'=>'chart_form', 'class'=>'chart-form']);
}
return $fields;
}
}
public function get_parameter($key, $args=[]){
if(is_array($key)){
return wpjam_fill($key, fn($k)=> $this->get_parameter($k, $args));
}
if(str_contains($key, 'timestamp')){
return wpjam_strtotime($this->get_parameter(str_replace('timestamp', 'date', $key), $args).' '.(str_starts_with($key, 'end_') ? '23:59:59' : '00:00:00'));
}
$value = ($args['data'][$key] ?? '') ?: wpjam_get_parameter($key, ['method'=>($args['method'] ?? $this->method)]);
$value && wpjam_set_cookie($key, $value, HOUR_IN_SECONDS);
if(!empty($_COOKIE[$key])){
return $_COOKIE[$key];
}
if($key == 'date_format' || $key == 'date_type'){
return '%Y-%m-%d';
}elseif($key == 'compare'){
return 0;
}
if($key == 'date'){
$ts = time() - DAY_IN_SECONDS;
}elseif($key == 'start_date'){
$ts = time() - DAY_IN_SECONDS*30;
}elseif($key == 'end_date'){
$ts = time();
}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 isset($ts) ? wpjam_date('Y-m-d', $ts) : null;
}
public function get_fields($args=[]){
$fields = $this->show_start_date ? [
'start_date'=> ['type'=>'date'],
'date_view' => ['type'=>'view', 'value'=>'-'],
'end_date' => ['type'=>'date']
] : ($this->show_date ? [
'prev_day' => ['type'=>'button', 'value'=>'‹', 'class'=>'button prev-day'],
'date' => ['type'=>'date'],
'next_day' => ['type'=>'button', 'value'=>'›', 'class'=>'button next-day']
] : []);
$fields = $fields ? ['date'=>['sep'=>' ', 'fields'=>wpjam_map($fields, fn($v, $k)=> ['value'=>($v['value'] ?? $this->get_parameter($k, $args))]+$v)]+(!empty($args['show_title']) ? ['title'=>'日期'] : [])] : [];
return $fields+($this->show_date_type ? ['date_format'=>['type'=>'select','value'=>$this->get_parameter('date_format', $args), 'options'=>[
'%Y-%m' => '按月',
'%Y-%m-%d' => '按天',
// '%Y%U' => '按周',
'%Y-%m-%d %H:00' => '按小时',
'%Y-%m-%d %H:%i' => '按分钟',
]]] : []);
}
public static function line($data, $args=[], $type='Line'){
foreach(($args['labels'] ?? []) as $k => $v){
if(is_array($v)){
$columns[$k] = $v['label'];
if($v['show_in_chart'] ?? true){
$labels[$k] = $v['label'];
}
if(!empty($v['callback'])){
$cbs[$k] = $v['callback'];
}
}else{
$columns[$k] = $labels[$k] = $v;
}
}
$xkey = $args['day_key'] ?? 'day';
$ykeys = array_keys($labels);
$data += ($args['show_table'] ?? true) && $data ? ['total' => array_fill_keys($ykeys, 0)] : [];
foreach($data as $day => &$item){
$item = (array)$item;
$item = array_merge($item, isset($cbs) ? array_map(fn($cb)=> $cb($item), $cbs) : []);
if($day !== 'total'){
$item[$xkey] ??= $day;
if(isset($data['total'])){
$data['total'] = wpjam_map($data['total'], fn($v, $k)=> $v+(is_numeric($item[$k] ?? null) ? $item[$k] : 0));
$rows[$item[$xkey]] = $item;
}
$item = wpjam_pull($item, [...$ykeys, $xkey]);
}
}
$total = wpjam_pull($data, 'total');
$tag = wpjam_tag();
if($args['show_chart'] ?? true){
$tag->append(wpjam_tag('div', [
'id' => $args['chart_id'] ?? 'daily-chart',
'data' => ['type'=>$type, 'chart'=>true, 'options'=>['data'=>$data, 'xkey'=>$xkey, 'ykeys'=>$ykeys, 'labels'=>array_values($labels)]]
]));
}
if($total && $data){
$labels = ($args['day_labels'] ?? [])+['sum'=>'累加', 'avg'=>'平均'];
$columns = [$xkey => $args['day_label'] ?? '时间']+$columns;
$th = wpjam_tag('tr')->append(wpjam_map($columns, fn($col, $id)=> wpjam_tag('th', ['scope'=>'col', 'id'=>$id], $col)));
$rows += ($args['show_sum'] ?? true) ? ['sum'=>$total] : [];
$rows += ($args['show_avg'] ?? true) ? ['avg'=>array_map(fn($v)=> is_numeric($v) ? round($v/count($data)) : '', $total)] : [];
$rows = wpjam_map($rows, fn($row, $k)=> wpjam_tag('tr')->append(wpjam_map($columns, fn($col, $id)=> wpjam_tag('td', [
'data' => ['colname'=>$col],
'class' => ['column-'.$id, ($id == $xkey ? 'column-primary' : '')]
])->append($id == $xkey ? [($labels[$k] ?? $k), wpjam_tag('button', ['class'=>'toggle-row'])] : ($row[$id] ?? '')))));
$tag->append(wpjam_tag('table', ['class'=>'wp-list-table widefat striped'])->append([
wpjam_tag('thead')->append($th),
wpjam_tag('tbody')->append($rows),
wpjam_tag('tfoot')->append($th)
]));
}
return $tag;
}
public static function bar($data, $args=[]){
return self::line($data, $args, 'Bar');
}
public static function donut($data, $args=[]){
$tag = wpjam_tag('div', ['class'=>'donut-chart-wrap']);
$data = wpjam_array($data, fn($k, $v)=> [null, [
'label' => !empty($v['label']) ? ($args['labels'][$v['label']] ?? $v['label']) : '/',
'value' => $v['count']
]]);
if($args['show_chart'] ?? true){
$tag->append('div', [
'id' => $args['chart_id'] ?? 'chart_'.wp_generate_password(6, false, false),
'data' => ['type'=>'Donut', 'chart'=>true, 'options'=>['data'=>$data]]
]);
}
if(($args['show_table'] ?? true) && $data){
$columns = (!empty($args['show_line_num']) ? ['no'=>'排名'] : [])+['label'=>$args['title'] ?? '名称', 'value'=>'数量'];
if($total = $args['total'] ?? false){
$total = $total === true ? array_sum(array_column($data, 'value')) : $total;
$data += ['total'=>['label'=>'所有', 'value'=>$total]];
$columns += ['rate'=>'比例'];
}
$tag->append(wpjam_tag('table', ['wp-list-table', 'widefat', 'striped'])->append([
wpjam_tag('thead')->append(wpjam_tag('tr')->append(wpjam_map($columns, fn($col, $id)=> wpjam_tag('th', ['scope'=>'col', 'id'=>$id], $col)))),
wpjam_tag('tbody')->append(wpjam_map($data, fn($item, $k)=> wpjam_tag('tr')->append(wpjam_map($columns, fn($col, $id)=> wpjam_tag('td', ['data'=>['colname'=>$col]], $item[$id] ?? ($id == 'no' ? ($k === 'total' ? '' : $k+1) : round($item['value']/$total*100, 2).'%'))))))
]));
}
return $tag;
}
public static function create($args=[]){
$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($args+[
'method' => 'POST',
'show_form' => true,
'show_start_date' => true,
'show_date' => true,
'show_date_type' => false,
'show_compare' => false
]);
}
}