| 1 |
<?php |
| 2 |
if(!class_exists('WP_List_Table')){ |
| 3 |
include ABSPATH.'wp-admin/includes/class-wp-list-table.php'; |
| 4 |
} |
| 5 |
|
| 6 |
class WPJAM_List_Table extends WP_List_Table{ |
| 7 |
use WPJAM_Call_Trait; |
| 8 |
|
| 9 |
public function __construct($args=[]){ |
| 10 |
add_screen_option('list_table', ($GLOBALS['wpjam_list_table'] = wpjam_admin('list_table', $this))); |
| 11 |
|
| 12 |
$this->screen = $screen = get_current_screen(); |
| 13 |
$this->_args = $args+['screen'=>$screen, 'shortcodes'=>['filter', 'row_action']]; |
| 14 |
|
| 15 |
array_map([$this, 'component'], ['action', 'view', 'column']); |
| 16 |
array_map(fn($k)=> add_filter('manage_'.$screen->id.'_'.$k, [$this, 'filter_'.$k]), ['columns', 'sortable_columns']); |
| 17 |
array_map(fn($k, $s)=> add_filter($k.$s.$screen->id, [$this, 'filter_'.$k]), ['views', 'bulk_actions'], ['_', '-']); |
| 18 |
array_map(fn($v)=> add_shortcode($v, [$this, $v.'_shortcode']), ['filter', 'row_action']); |
| 19 |
|
| 20 |
wpjam_admin([ |
| 21 |
'style' => $this->style, |
| 22 |
'vars[list_table]' => fn()=> $this->get_setting(), |
| 23 |
'vars[page_title_action]' => fn()=> $this->get_action('add', ['class'=>'page-title-action']) ?: '' |
| 24 |
]); |
| 25 |
|
| 26 |
if($this->builtin){ |
| 27 |
$this->page_load(); |
| 28 |
}else{ |
| 29 |
add_filter('list_table_primary_column', fn()=> $this->primary_column ??= array_key_first(wpjam_except($this->columns, ['no', 'cb']))); |
| 30 |
|
| 31 |
parent::__construct($this->_args); |
| 32 |
} |
| 33 |
} |
| 34 |
|
| 35 |
public function __get($name){ |
| 36 |
if(in_array($name, $this->compat_fields, true)){ |
| 37 |
return $this->$name; |
| 38 |
} |
| 39 |
|
| 40 |
if(isset($this->_args[$name])){ |
| 41 |
return $this->_args[$name]; |
| 42 |
} |
| 43 |
|
| 44 |
if(in_array($name, ['actions', 'views', 'fields'])){ |
| 45 |
$value = [$this, 'get_'.$name.'_by_model'](); |
| 46 |
|
| 47 |
if($name == 'fields'){ |
| 48 |
return $this->$name = wpjam_fields($value ?: [], true); |
| 49 |
} |
| 50 |
|
| 51 |
return $value ?? ($name == 'actions' ? ($this->builtin ? [] : WPJAM_Model::get_actions()) : ($this->views_by_model() ?: [])); |
| 52 |
}elseif(in_array($name, ['primary_key', 'filterable_fields', 'searchable_fields'])){ |
| 53 |
$value = wpjam_trap($this->model.'::get_'.$name, []) ?: ($name == 'primary_key' ? 'id' : []); |
| 54 |
|
| 55 |
if($name == 'filterable_fields'){ |
| 56 |
$fields = wpjam_filter($this->fields, ['filterable'=>true]); |
| 57 |
$views = array_keys(wpjam_filter($fields, ['type'=>'view'])); |
| 58 |
$fields = array_map(fn($v)=> wpjam_except($v, ['title', 'before', 'after', 'required', 'show_admin_column'])+[($v['type'] === 'select' ? 'show_option_all' : 'placeholder') => $v['title'] ?? ''], wpjam_except($fields, $views)); |
| 59 |
|
| 60 |
$value = $fields+($fields && !$this->builtin && $this->sortable_columns ? [ |
| 61 |
'orderby' => ['options'=>[''=>'排序']+array_map('wp_strip_all_tags', array_intersect_key($this->columns, $this->sortable_columns))], |
| 62 |
'order' => ['options'=>['desc'=>'降序','asc'=>'升序']] |
| 63 |
] : [])+array_fill_keys(array_merge($value, $views), []); |
| 64 |
} |
| 65 |
|
| 66 |
return $this->$name = $value; |
| 67 |
}elseif(in_array($name, ['form_data', 'params'])){ |
| 68 |
return $this->validate_by_fields($name); |
| 69 |
}elseif($name == 'offset'){ |
| 70 |
return ((int)$this->per_page ?: 50)*($this->get_pagenum()-1); |
| 71 |
} |
| 72 |
} |
| 73 |
|
| 74 |
public function __set($name, $value){ |
| 75 |
return in_array($name, $this->compat_fields, true) ? ($this->$name = $value) : ($this->_args[$name] = $value); |
| 76 |
} |
| 77 |
|
| 78 |
public function __isset($name){ |
| 79 |
return $this->$name !== null; |
| 80 |
} |
| 81 |
|
| 82 |
public function __call($method, $args){ |
| 83 |
if($method == 'get_arg'){ |
| 84 |
return wpjam_get($this->_args, ...$args); |
| 85 |
}elseif($method == 'get_date'){ |
| 86 |
$type = $args[0] ?? ''; |
| 87 |
$offset = $type == 'prev' ? -1 : (int)($type == 'next'); |
| 88 |
|
| 89 |
[$year, $month] = array_map(fn($k, $f, $r)=> clamp(($type == 'current' ? 0 : (int)$this->get_param($k)) ?: wpjam_date($f), ...$r), ['year', 'month'], ['Y', 'm'], [[1970, 2200], [1, 12]]); |
| 90 |
|
| 91 |
$month += $offset; |
| 92 |
|
| 93 |
if(in_array($month, [0, 13])){ |
| 94 |
$year += $offset; |
| 95 |
$month = abs($month-12); |
| 96 |
} |
| 97 |
|
| 98 |
return in_array('locale', $args) |
| 99 |
? sprintf(__('%1$s %2$d', 'wpjam'), $GLOBALS['wp_locale']->get_month($month), $year) |
| 100 |
: compact('year', 'month'); |
| 101 |
}elseif($method == 'add'){ |
| 102 |
$this->_args = wpjam_set($this->_args, array_shift($args).'['.(count($args) >= 2 ? array_shift($args) : '').']', $args[0]); |
| 103 |
|
| 104 |
return $args[0]; |
| 105 |
}elseif($this->mod($method, '_by_fields')){ |
| 106 |
$name = $args[0] ?? ''; |
| 107 |
$fields = array_filter($this->{($name === 'left' ? 'left' : 'filterable').'_fields'}); |
| 108 |
$parts = array_filter([$name === 'left' ? '' : wpjam_chart(), $fields ? wpjam_fields($fields) : '']); |
| 109 |
$name = ($name === 'left' || !$name) ? 'params' : $name; |
| 110 |
$data = wp_doing_ajax() ? wp_parse_args($_POST[$name] ?? []) : $_GET; |
| 111 |
$data = array_filter(array_merge(...array_map(fn($part)=> $method == 'validate' && wp_doing_ajax() ? $part->validate($data) : wpjam_trap([$part, 'validate'], $data, []), $parts)), fn($v)=> isset($v) && $v !== []); |
| 112 |
|
| 113 |
return $parts && $method == 'render' ? wpjam_tag('div', ['actions'])->append(array_map(fn($part)=> $part->render(['fields_type'=>'', 'data'=>$data]), $parts)) : $data; |
| 114 |
}elseif($this->mod($method, '_by_model')){ |
| 115 |
return ($cb = wpjam_callback([$this->model, $method])) ? wpjam_catch($cb, ...$args) : null; |
| 116 |
}elseif($this->mod($method, '_shortcode')){ |
| 117 |
return $method === 'filter' |
| 118 |
? $this->get_filter_link($args[0], $args[1], wpjam_pull($args[0], 'class')) |
| 119 |
: $this->get_row_action($this->row_id, (is_blank($args[1]) ? [] : ['title'=>$args[1]])+$args[0])."\n"; |
| 120 |
}elseif($this->mod($method, '_row_actions')){ |
| 121 |
[$value, $id] = $method == 'get' ? [[], $args[0]] : [$args[0], $this->parse_id($args[1])]; |
| 122 |
|
| 123 |
$names = array_diff($this->row_actions ?: [], $this->next_actions ?: []); |
| 124 |
$value += $this->get_action($names, ['id'=>$id]+($this->layout == 'calendar' ? ['wrap'=>'<span class="%s"></span>'] : [])); |
| 125 |
$value += $this->builtin ? wpjam_pull($value, ['view', 'delete', 'trash', 'spam', 'remove']) : []; |
| 126 |
|
| 127 |
return wpjam_except($value+($this->builtin || $this->primary_key == 'id' ? ['id'=>'ID: '.$id] : []), wpjam_admin('removed_actions[]')); |
| 128 |
}elseif($this->mod($method, 'ob_get_')){ |
| 129 |
$result = wpjam_ob([$this, ($this->builtin && $method != 'single_row' ? 'builtin_' : '').$method], ...$args); |
| 130 |
|
| 131 |
return $this->builtin && in_array($method, ['single_row', 'display']) ? $this->filter_table($result) : $result; |
| 132 |
}elseif($this->mod($method, 'builtin_')){ |
| 133 |
return [$GLOBALS['wp_list_table'] ??= _get_list_table($this->builtin, ['screen'=>$this->screen]), $method](...$args); |
| 134 |
}elseif($this->mod($method, 'filter_')){ |
| 135 |
if($method == 'table'){ |
| 136 |
return wpjam_preg_replace('#<tr id=".+?-(\d+)"[^>]*>.+?</tr>#is', fn($m)=> $this->filter_single_row(...$m), $args[0]); |
| 137 |
}elseif($method == 'single_row'){ |
| 138 |
$this->row_id = $args[1]; |
| 139 |
|
| 140 |
return wpjam_do_shortcode(apply_filters('wpjam_single_row', ...$args), $this->shortcodes); |
| 141 |
}elseif($method == 'columns'){ |
| 142 |
return $this->columns = wpjam_except(wpjam_add_at($args[0], $args[0] ? -1 : 0, $this->columns ?: []), wpjam_admin('removed_columns[]')); |
| 143 |
} |
| 144 |
|
| 145 |
return array_merge($args[0], $this->$method ?: []); |
| 146 |
} |
| 147 |
|
| 148 |
return parent::__call($method, $args); |
| 149 |
} |
| 150 |
|
| 151 |
public function __invoke($data){ |
| 152 |
$type = $data['action_type']; |
| 153 |
$action = $data['list_action'] ?? ''; |
| 154 |
$parts = parse_url((wp_get_original_referer() ?: wp_get_referer()) ?: wp_die('非法请求')); |
| 155 |
|
| 156 |
if($parts['host'] == $_SERVER['HTTP_HOST']){ |
| 157 |
$_SERVER['REQUEST_URI'] = $parts['path']; |
| 158 |
} |
| 159 |
|
| 160 |
if($type == 'query_items'){ |
| 161 |
$data = wpjam_params('data'); |
| 162 |
|
| 163 |
if(count($data) == 1 && isset($data['id'])){ |
| 164 |
return $this->response(['type'=>'add']+$data); |
| 165 |
} |
| 166 |
|
| 167 |
$_REQUEST = $data+$_REQUEST; |
| 168 |
|
| 169 |
$result = ['type'=>'list']; |
| 170 |
}else{ |
| 171 |
$result = ($this->get_action($action) ?: wp_die('无效的操作'))($type); |
| 172 |
} |
| 173 |
|
| 174 |
if(!in_array($result['type'], ['form', 'append', 'redirect', 'move', 'up', 'down', ''])){ |
| 175 |
$this->prepare_items(); |
| 176 |
|
| 177 |
$result = $this->response($result)+['params'=>$this->params, 'setting'=>$this->get_setting(), 'views'=>$this->ob_get_views()]; |
| 178 |
$result += $result['type'] == 'list' ? ['table'=>$this->ob_get_display()] : ['tablenav'=>wpjam_fill(['top', 'bottom'], [$this, 'ob_get_display_tablenav'])]; |
| 179 |
} |
| 180 |
|
| 181 |
return $result; |
| 182 |
} |
| 183 |
|
| 184 |
protected function response($data){ |
| 185 |
if($data['type'] == 'list'){ |
| 186 |
if($this->layout == 'left' && !isset(wpjam_params('data')[$this->left_key])){ |
| 187 |
$data['left'] = $this->ob_get_col_left(); |
| 188 |
} |
| 189 |
}elseif($data['type'] == 'items'){ |
| 190 |
if(isset($data['items'])){ |
| 191 |
$data['items'] = wpjam_map($data['items'], fn($item, $id)=> $this->response(['id'=>$id]+$item)); |
| 192 |
} |
| 193 |
}elseif($data['type'] != 'delete'){ |
| 194 |
if($this->layout == 'calendar'){ |
| 195 |
if(!empty($data['data'])){ |
| 196 |
$data['data'] = wpjam_map($data['data'], [$this, 'ob_get_single_date']); |
| 197 |
} |
| 198 |
}elseif(!empty($data['bulk'])){ |
| 199 |
$this->get_by_ids_by_model($ids = array_filter($data['ids'])); |
| 200 |
|
| 201 |
$data['data'] = array_map(fn($id)=> ['id'=>$id, 'data'=>$this->ob_get_single_row($id)], $ids); |
| 202 |
}elseif(!empty($data['id'])){ |
| 203 |
$data['data'] = $this->ob_get_single_row($data['id']); |
| 204 |
} |
| 205 |
} |
| 206 |
|
| 207 |
return $data; |
| 208 |
} |
| 209 |
|
| 210 |
protected function component($type, ...$args){ |
| 211 |
if($args){ |
| 212 |
return $this->objects[$type][$args[0]] ?? array_find($this->objects[$type], fn($v)=> $v->name == $args[0]); |
| 213 |
} |
| 214 |
|
| 215 |
$args = wpjam_admin('data_type', $this); |
| 216 |
|
| 217 |
if($type == 'action'){ |
| 218 |
if($this->sortable){ |
| 219 |
$sortable = is_array($this->sortable) ? $this->sortable : []; |
| 220 |
$action = (wpjam_pull($sortable, 'action') ?: [])+wpjam_except($sortable, ['items']); |
| 221 |
|
| 222 |
$this->sortable = ['items'=> $sortable['items'] ?? ' >tr']; |
| 223 |
$this->actions += wpjam_map([ |
| 224 |
'move' => ['page_title'=>'拖动', 'dashicon'=>'move'], |
| 225 |
'up' => ['page_title'=>'向上移动', 'dashicon'=>'arrow-up-alt'], |
| 226 |
'down' => ['page_title'=>'向下移动', 'dashicon'=>'arrow-down-alt'], |
| 227 |
], fn($v)=> $action+$v+['direct'=>true]); |
| 228 |
} |
| 229 |
|
| 230 |
$name = 'update_setting'; |
| 231 |
$this->actions += $this->$name ? [$name => (is_array($this->$name) ? $this->$name : [])+['page_title'=>'� |
| 232 |
�局设置', 'title'=>'设置', 'class'=>'button-primary', $name=>true]] : []; |
| 233 |
|
| 234 |
foreach(wpjam_get_meta_options(wpjam_admin('meta_type'), ['list_table'=>true]+wpjam_except($args, 'data_type')) as $v){ |
| 235 |
wpjam_register_list_table_action(($v->action_name ?: 'set_'.$v->name), $v->get_args()+[ |
| 236 |
'meta_type' => $v->name, |
| 237 |
'page_title' => '设置'.$v->title, |
| 238 |
'submit_text' => '设置' |
| 239 |
]); |
| 240 |
} |
| 241 |
}elseif($type == 'column'){ |
| 242 |
if($this->layout == 'calendar'){ |
| 243 |
array_map(fn($i)=> $this->add('columns', 'day'.($i%7), $GLOBALS['wp_locale']->get_weekday_abbrev($GLOBALS['wp_locale']->get_weekday($i%7))), array_slice(range(0, get_option('start_of_week')+6), -7)); |
| 244 |
|
| 245 |
return array_map(fn($v)=> $this->add('query_args', $v), ['year', 'month']); |
| 246 |
} |
| 247 |
|
| 248 |
$this->bulk_actions && !$this->builtin && $this->add('columns', 'cb', true); |
| 249 |
|
| 250 |
($no = $this->numberable) && $this->add('columns', 'no', $no === true ? 'No.' : $no); |
| 251 |
} |
| 252 |
|
| 253 |
$class = 'WPJAM_List_Table_'.$type; |
| 254 |
[$class, 'registers']($type == 'column' ? $this->fields : $this->{$type.'s'}); |
| 255 |
|
| 256 |
$args = array_map(fn($v)=> ['value'=>$v, 'if_null'=>true, 'callable'=>true], $args); |
| 257 |
$args += $type == 'action' ? ['calendar'=> $this->layout == 'calendar' ? true : ['compare'=>'!==', 'value'=>'only']] : []; |
| 258 |
|
| 259 |
foreach($this->add('objects', $type, [$class, 'get_registereds']($args)) as $object){ |
| 260 |
$key = $object->name; |
| 261 |
|
| 262 |
if($type == 'action'){ |
| 263 |
if($object->overall){ |
| 264 |
$this->add('overall_actions', $key); |
| 265 |
}else{ |
| 266 |
$object->bulk && $object->is_allowed() && $this->add('bulk_actions', $key, $object); |
| 267 |
$object->row_action && $this->add('row_actions', $key); |
| 268 |
} |
| 269 |
|
| 270 |
$object->next && $this->add('next_actions', $key, $object->next); |
| 271 |
}elseif($type == 'view'){ |
| 272 |
$view = $object(); |
| 273 |
$view && $this->add('views', $key, is_array($view) ? $this->get_filter_link(...$view) : $view); |
| 274 |
}else{ |
| 275 |
$data = array_filter($object->pick(['description', 'sticky', 'format', 'precision', 'conditional_styles', ...($this->nowrap ? [] : ['nowrap'])])); |
| 276 |
|
| 277 |
$this->add('columns', $key, $object->title.($data ? wpjam_tag('i', ['data'=>$data]) : '')); |
| 278 |
|
| 279 |
$object->sortable && $this->add('sortable_columns', $key, [$key, true]); |
| 280 |
|
| 281 |
wpjam_style($object->style); |
| 282 |
} |
| 283 |
} |
| 284 |
} |
| 285 |
|
| 286 |
protected function get_setting(){ |
| 287 |
$fields = $this->searchable_fields; |
| 288 |
$s = $this->get_param('s'); |
| 289 |
|
| 290 |
return wpjam_pick($this, ['sortable', 'layout', 'left_key'])+[ |
| 291 |
'search' => $this->builtin || ($this->search ?? $fields) ? [ |
| 292 |
'columns' => wpjam_is_assoc_array($fields) ? wpjam_field(['key'=>'search_columns', 'show_option_all'=>'默认', 'value'=>$this->get_param('search_columns'), 'options'=>$fields]) : '' |
| 293 |
]+($this->builtin ? ['term'=>$s] : ['box'=>$this->ob_get_search_box('搜索', 'wpjam')]) : false, |
| 294 |
|
| 295 |
'subtitle' => $this->get_subtitle_by_model().($s ? sprintf(__('Search results for: %s', 'wpjam'), '<strong>'.esc_html($s).'</strong>') : ''), |
| 296 |
'summary' => $this->get_summary_by_model(), |
| 297 |
|
| 298 |
'bulk_actions' => array_map(fn($v)=> array_filter($v->get_attr(['bulk'=>true])), $this->bulk_actions ?: []), |
| 299 |
'overall_actions' => array_values($this->get_action(array_diff($this->overall_actions ?: [], $this->next_actions ?: []), ['class'=>'button overall-action'])) |
| 300 |
]; |
| 301 |
} |
| 302 |
|
| 303 |
public function get_action($name, ...$args){ |
| 304 |
if(is_array($name)){ |
| 305 |
return wpjam_fill($name ?: [], fn($n)=> $this->get_action($n, ...$args)); |
| 306 |
} |
| 307 |
|
| 308 |
return ($object = $this->component('action', $name)) && $args ? $object->render($args[0]) : $object; |
| 309 |
} |
| 310 |
|
| 311 |
public function get_row_action($id, $args){ |
| 312 |
return $this->get_action(wpjam_pull($args, 'name'), $args+['id'=>$id]); |
| 313 |
} |
| 314 |
|
| 315 |
public function get_filter_link($filter, $label, $attr=[]){ |
| 316 |
return wpjam_tag('a', $attr, $label)->add_class('list-table-filter')->data('filter', $filter+$this->get_param($this->query_args ?: []) ?: new stdClass()); |
| 317 |
} |
| 318 |
|
| 319 |
public function single_row($item){ |
| 320 |
if($this->layout == 'calendar'){ |
| 321 |
return wpjam_echo(wpjam_tag('tr')->append(wpjam_map($item, fn($date, $day)=> ['td', ['id'=>'date_'.$date, 'class'=>'column-day'.$day], $this->ob_get_single_date($this->calendar[$date] ?? [], $date)]))); |
| 322 |
} |
| 323 |
|
| 324 |
$item = ($item instanceof WPJAM_Register) ? $item->to_array() : (is_array($item) ? $item : wpjam_trap($this->model.'::get', $item, wp_doing_ajax() ? 'throw' : [])); |
| 325 |
|
| 326 |
if(!$item){ |
| 327 |
return; |
| 328 |
} |
| 329 |
|
| 330 |
$raw = (array)$item; |
| 331 |
$id = $this->parse_id($item); |
| 332 |
$attr = $id ? ['id'=>$this->singular.'-'.str_replace('.', '-', $id), 'data'=>['id'=>$id]] : []; |
| 333 |
|
| 334 |
$item['row_actions'] = $id ? $this->get_row_actions($id) : ($this->row_actions ? ['error'=>'Primary Key「'.$this->primary_key.'」不存在'] : []); |
| 335 |
|
| 336 |
$this->before_single_row_by_model($raw); |
| 337 |
|
| 338 |
$method = array_find(['render_row', 'render_item', 'item_callback'], fn($v)=> method_exists($this->model, $v)); |
| 339 |
$item = $method ? [$this->model, $method]($item, $attr) : $item; |
| 340 |
|
| 341 |
$this->numberable && ($this->no ??= $this->offset); |
| 342 |
|
| 343 |
echo $item ? $this->filter_single_row(wpjam_tag('tr', $attr, $this->ob_get_single_row_columns($item+($this->numberable ? ['no'=>($this->no += 1)] : [])))->add_class($this->multi_rows ? 'tr-'.$id : ''), $id)."\n" : ''; |
| 344 |
|
| 345 |
$this->after_single_row_by_model($item, $raw); |
| 346 |
} |
| 347 |
|
| 348 |
public function single_date($item, $date){ |
| 349 |
$parts = explode('-', $date); |
| 350 |
$append = ($item || $parts[1] == $this->get_date()['month']) ? wpjam_tag('div', ['row-actions', 'alignright'])->append($this->get_row_actions($date)) : ''; |
| 351 |
|
| 352 |
echo wpjam_tag('div', ['date-meta'])->append('span', ['day', $date == wpjam_date('Y-m-d') ? 'today' : ''], (int)$parts[2])->append($append)->after('div', ['date-content'], $this->render_date_by_model($item, $date) ?? (is_string($item) ? $item : '')); |
| 353 |
} |
| 354 |
|
| 355 |
protected function parse_id($item){ |
| 356 |
return wpjam_get($item, $this->primary_key); |
| 357 |
} |
| 358 |
|
| 359 |
protected function parse_cell($cell, $id){ |
| 360 |
$wrap = wpjam_pull($cell, 'wrap'); |
| 361 |
|
| 362 |
if(isset($cell['row_action'])){ |
| 363 |
$cell = $this->get_row_action($id, ['name'=>wpjam_pull($cell, 'row_action')]+$cell); |
| 364 |
}elseif(isset($cell['filter'])){ |
| 365 |
$cell = $this->get_filter_link(wpjam_pull($cell, 'filter'), wpjam_pull($cell, 'label'), $cell); |
| 366 |
}elseif(isset($cell['items'])){ |
| 367 |
$items = $cell['items']; |
| 368 |
$args = $cell['args'] ?? []; |
| 369 |
$type = $args['item_type'] ?? 'image'; |
| 370 |
$key = $args[$type.'_key'] ?? $type; |
| 371 |
$max = $args['max_items'] ?? null; |
| 372 |
$field = $args['field'] ?? ''; |
| 373 |
$cell = wpjam_tag('div', ['items', $type.'-list'])->data(['field'=>$field, 'max_items'=>$max])->style($args['style'] ?? ''); |
| 374 |
|
| 375 |
if($type == 'image'){ |
| 376 |
$size = wpjam_pick($args, ['width', 'height'])+['width'=>60, 'height'=>60]; |
| 377 |
|
| 378 |
$cell->style(wpjam_array($size, fn($k, $v)=> ['--'.$k, $v.'px'])+['--per-row'=>$arg['per_row'] ?? 4]); |
| 379 |
} |
| 380 |
|
| 381 |
$names = $args['actions'] ?? ['add_item', 'edit_item', 'del_item']; |
| 382 |
$names = !empty($args['sortable']) && $cell->add_class('sortable') ? ['move_item', ...$names] : $names; |
| 383 |
$args = ['id'=>$id,'data'=>['_field'=>$field]]; |
| 384 |
$add = in_array('add_item', $names) && (!$max || count($items) <= $max); |
| 385 |
|
| 386 |
foreach($items as $i => $item){ |
| 387 |
$v = $item[$key] ?: ''; |
| 388 |
$t = wpjam_tag('span', ['truncate-text']); |
| 389 |
|
| 390 |
$type == 'image' ? $t->text($item['title'] ?? '')->before('img', ['src'=>wpjam_get_thumbnail($v, array_map(fn($s)=> $s*2, $size))]+$size) : $t->text($v); |
| 391 |
|
| 392 |
$args['i'] = $args['data']['i'] = $i; |
| 393 |
|
| 394 |
$cell->append(wpjam_tag('div', ['id'=>'item_'.$i, 'data'=>['i'=>$i], 'class'=>'item'])->append([ |
| 395 |
$this->get_action('move_item', $args+['title'=>$t, 'fallback'=>true])->style(wpjam_pick($item, ['color'])), |
| 396 |
wpjam_tag('span', ['row-actions'])->append($this->get_action(array_diff($names, ['add_item']), $args+['wrap'=>'<span class="%s"></span>', 'item'=>$item])) |
| 397 |
])); |
| 398 |
} |
| 399 |
|
| 400 |
unset($args['i'], $args['data']['i']); |
| 401 |
|
| 402 |
$add && $cell->append($this->get_action('add_item', $args+['class'=>'add-item item']+($type == 'image' ? ['dashicon'=>'plus-alt2'] : []))); |
| 403 |
}else{ |
| 404 |
$cell = $cell['text'] ?? ''; |
| 405 |
} |
| 406 |
|
| 407 |
return (string)wpjam_wrap($cell, $wrap); |
| 408 |
} |
| 409 |
|
| 410 |
public function column_default($item, $name, $id=null){ |
| 411 |
$id ??= $this->parse_id($item); |
| 412 |
$object = $this->component('column', $name); |
| 413 |
$args = $this->value_callback === false ? [] : array_filter(['meta_type'=>wpjam_admin('meta_type'), 'model'=>$this->model]); |
| 414 |
$value = $object && $id ? $object($args+['data'=>$item, 'id'=>$id]) : (is_array($item) ? ($item[$name] ?? null) : $item); |
| 415 |
|
| 416 |
return implode(',', array_map(fn($v)=> is_array($v) ? $this->parse_cell($v, $id) : $v, wp_is_numeric_array($value) ? $value : [$value])); |
| 417 |
} |
| 418 |
|
| 419 |
public function column_cb($item){ |
| 420 |
if(($id = $this->parse_id($item)) && wpjam_can($this->capability, $id)){ |
| 421 |
return wpjam_tag('input', ['type'=>'checkbox', 'name'=>'ids[]', 'value'=>$id, 'id'=>'cb-select-'.$id]); |
| 422 |
} |
| 423 |
} |
| 424 |
|
| 425 |
public function render(){ |
| 426 |
$form = wpjam_tag('form', ['id'=>'list_table_form'], $this->ob_get_display())->before($this->ob_get_views()); |
| 427 |
|
| 428 |
return $this->layout == 'left' ? wpjam_tag('div', ['id'=>'col-container', 'class'=>'wp-clearfix'])->append(array_map(fn($v, $k)=> $v->add_class('col-wrap')->wrap('div', ['id'=>'col-'.$k]), [wpjam_wrap($this->ob_get_col_left(), 'form'), $form], ['left', 'right'])) : $form; |
| 429 |
} |
| 430 |
|
| 431 |
public function col_left($action=''){ |
| 432 |
$paged = (int)$this->get_param('left_paged') ?: 1; |
| 433 |
|
| 434 |
if(($cb = $this->model.'::query_left') && wpjam_callback($cb)){ |
| 435 |
static $pages, $items; |
| 436 |
|
| 437 |
if($action == 'prepare'){ |
| 438 |
$number = $this->left_per_page ?: 10; |
| 439 |
$left = array_filter($this->get_param([$this->left_key])); |
| 440 |
$items = wpjam_try($cb, ['number'=>$number, 'offset'=>($paged-1)*$number]+$this->validate_by_fields('left')+$left); |
| 441 |
|
| 442 |
[$pages, $items] = $items ? [ceil($items['total']/$number), $items['items']] : [0, []]; |
| 443 |
|
| 444 |
return $items && !$left && wpjam_default($this->left_key, array_first($items)['id']); |
| 445 |
} |
| 446 |
|
| 447 |
$fields = $this->render_by_fields('left'); |
| 448 |
$head = [[$this->left_title, 'th'], 'tr']; |
| 449 |
$items = implode(wpjam_map($items ?: ['找不到'.$this->left_title], fn($item)=> wpjam_tag('td')->append(is_array($item) ? [ |
| 450 |
['p', ['row-title'], $item['title']], |
| 451 |
['span', ['time'], $item['time']], |
| 452 |
...(isset($item['count']) ? array_map(fn($v)=> ['span', ['count', 'wp-ui-highlight'], $v], (array)$item['count']) : []) |
| 453 |
] : $item)->wrap('tr', is_array($item) ? ['class'=>'left-item', 'id'=>$item['id'], 'data-id'=>$item['id']] : ['no-items']))); |
| 454 |
|
| 455 |
echo $fields ? $fields->wrap('div', ['class'=>'tablenav']) : ''; |
| 456 |
echo wpjam_tag('table', ['widefat striped'])->append([[$head, 'thead'], [$items, 'tbody'], [$head, 'tfoot']]); |
| 457 |
}else{ |
| 458 |
$result = $this->col_left_by_model(); |
| 459 |
$pages = $result && is_array($result) ? ceil($result['total_items']/$result['per_page']) : 0; |
| 460 |
} |
| 461 |
|
| 462 |
$pages > 1 && wpjam_echo(wpjam_tag('span', ['left-pagination-links'])->append([ |
| 463 |
wpjam_tag('a', ['prev-page'], '‹')->attr('title', __('Previous page', 'wpjam')), |
| 464 |
wpjam_tag('span', [], $paged.' / '.$pages), |
| 465 |
wpjam_tag('a', ['next-page'], '›')->attr('title', __('Next page', 'wpjam')), |
| 466 |
wpjam_tag('input', ['type'=>'number', 'name'=>'left_paged', 'value'=>$paged, 'min'=>1, 'max'=>$pages, 'class'=>'current-page']), |
| 467 |
wpjam_tag('input', ['type'=>'submit', 'class'=>'button', 'value'=>'➔']) |
| 468 |
])->wrap('div', ['tablenav-pages'])->wrap('div', ['tablenav', 'bottom'])); |
| 469 |
} |
| 470 |
|
| 471 |
public function page_load(){ |
| 472 |
if(wp_doing_ajax()){ |
| 473 |
return wpjam_ajax('wpjam-list-table-action', [ |
| 474 |
'admin' => true, |
| 475 |
'callback' => $this, |
| 476 |
'nonce_action' => fn($data)=> $data['action_type'] == 'form' ? '' : (($object = $this->get_action($data['list_action'] ?? '')) ? $object->parse_nonce_action($data) : '') |
| 477 |
]); |
| 478 |
} |
| 479 |
|
| 480 |
if($action = $_GET['export_action'] ?? ''){ |
| 481 |
return ($object = $this->get_action($action)) ? wpjam_trap($object, 'export', 'die') : wp_die('无效的导出操作'); |
| 482 |
} |
| 483 |
|
| 484 |
$this->builtin || wpjam_trap([$this, 'prepare_items'], fn($result)=> wpjam_admin('error', $result)); |
| 485 |
} |
| 486 |
|
| 487 |
public function prepare_items(){ |
| 488 |
$args = array_filter($this->get_param(['orderby', 'order', 's', 'search_columns']), fn($v)=> isset($v)); |
| 489 |
$_GET = array_merge($_GET, $args); |
| 490 |
$args += $this->params+wpjam_array($this->filterable_fields, fn($k, $v)=> [$k, $v ? null : $this->get_param($k)], true); |
| 491 |
|
| 492 |
if($this->layout == 'calendar'){ |
| 493 |
$date = $this->get_date(); |
| 494 |
$start = (int)get_option('start_of_week'); |
| 495 |
$ts = mktime(0, 0, 0, $date['month'], 1, $date['year']); |
| 496 |
$pad = calendar_week_mod(date('w', $ts)-$start); |
| 497 |
$days = date('t', $ts); |
| 498 |
$days = $days+6-($days%7 ?: 7); |
| 499 |
$items = wpjam_map(array_chunk(range(0, $days), 7), fn($item)=> wpjam_array($item, fn($k, $v)=> [($v+$start)%7, date('Y-m-d', $ts+($v-$pad)*DAY_IN_SECONDS)])); |
| 500 |
|
| 501 |
$this->calendar = wpjam_try($this->model.'::query_calendar', $args+$date); |
| 502 |
}else{ |
| 503 |
$this->layout == 'left' && $this->col_left('prepare'); |
| 504 |
|
| 505 |
$number = (int)$this->per_page ?: 50; |
| 506 |
$offset = $this->offset; |
| 507 |
$cb = $this->model.'::query_items'; |
| 508 |
$params = wpjam_get_reflection($cb, 'Parameters') ?: []; |
| 509 |
$items = wpjam_try($cb, ...(count($params) > 1 && $params[0]->name != 'args' ? [$number, $offset] : [compact('number', 'offset')+$args])); |
| 510 |
|
| 511 |
if(wpjam_is_assoc_array($items) && isset($items['items'])){ |
| 512 |
$total = $items['total'] ?? null; |
| 513 |
$items = $items['items']; |
| 514 |
} |
| 515 |
|
| 516 |
$this->set_pagination_args(['total_items'=>$total ?? ($number = count($items)), 'per_page'=>$number]); |
| 517 |
} |
| 518 |
|
| 519 |
$this->items = $items; |
| 520 |
} |
| 521 |
|
| 522 |
protected function get_table_classes(){ |
| 523 |
return array_merge(array_diff(parent::get_table_classes(), ($this->fixed ? [] : ['fixed'])), $this->nowrap ? ['nowrap'] : []); |
| 524 |
} |
| 525 |
|
| 526 |
protected function handle_row_actions($item, $column, $primary){ |
| 527 |
return ($primary === $column && !empty($item['row_actions'])) ? $this->row_actions($item['row_actions']) : ''; |
| 528 |
} |
| 529 |
|
| 530 |
public function get_columns(){ |
| 531 |
return []; |
| 532 |
} |
| 533 |
|
| 534 |
public function extra_tablenav($which='top'){ |
| 535 |
if($this->layout == 'calendar'){ |
| 536 |
echo wpjam_tag('h2', [], $this->get_date('locale')); |
| 537 |
echo wpjam_tag('span', ['pagination-links'])->append(array_map(fn($v, $k)=> "\n".$this->get_filter_link($this->get_date($k), $v, ['class'=>$k.'-month button', 'title'=>$this->get_date($k, 'locale')]), ['‹', '今日', '›'], ['prev', 'current', 'next']))->wrap('div', ['tablenav-pages']); |
| 538 |
} |
| 539 |
|
| 540 |
if($which == 'top' && ($fields = $this->render_by_fields())){ |
| 541 |
echo $fields->append(get_submit_button('筛选', '', 'filter_action', false)); |
| 542 |
} |
| 543 |
|
| 544 |
if(!$this->builtin){ |
| 545 |
$this->extra_tablenav_by_model($which); |
| 546 |
|
| 547 |
do_action(wpjam_get_filter_name($this->plural, 'extra_tablenav'), $which); |
| 548 |
} |
| 549 |
} |
| 550 |
|
| 551 |
public function current_action(){ |
| 552 |
return $_REQUEST['list_action'] ?? parent::current_action(); |
| 553 |
} |
| 554 |
} |
| 555 |
|
| 556 |
class WPJAM_Builtin_List_Table extends WPJAM_List_Table{ |
| 557 |
public function __construct($args){ |
| 558 |
$data_type = wpjam_admin(wpjam_pick($args, ['data_type', 'meta_type']))['data_type']; |
| 559 |
|
| 560 |
if($data_type == 'post_type'){ |
| 561 |
$echo = true; |
| 562 |
$builtin = $args['post_type'] == 'attachment' ? 'Media' : 'Posts'; |
| 563 |
$callback = 'get_post'; |
| 564 |
$parts = $builtin == 'Media' ? ['media', 'media'] : (($args['hierarchical'] ?? '') ? ['pages', 'page', 'posts'] : ['posts', 'post', 'posts']); |
| 565 |
}elseif($data_type == 'taxonomy'){ |
| 566 |
$builtin = 'Terms'; |
| 567 |
$callback = ['get_term', 'get_term_level']; |
| 568 |
$parts = [$args['taxonomy'], $args['taxonomy']]; |
| 569 |
}elseif($data_type == 'user'){ |
| 570 |
$builtin = 'Users'; |
| 571 |
$callback = 'get_userdata'; |
| 572 |
$parts = ['users', 'user', 'users']; |
| 573 |
}elseif($data_type == 'comment'){ |
| 574 |
$echo = true; |
| 575 |
$builtin = 'Comments'; |
| 576 |
$callback = 'get_comment'; |
| 577 |
$parts = ['comments', 'comment']; |
| 578 |
} |
| 579 |
|
| 580 |
wpjam_hooks([ |
| 581 |
['manage_'.$parts[0].'_custom_column', fn(...$args)=> wpjam_echo($this->column_default(...array_pad($args, -3, [])), $echo ?? false), 10, 3], |
| 582 |
[$parts[1].'_row_actions', [$this, 'filter_row_actions'], 1, 2], |
| 583 |
isset($parts[2]) ? ['manage_'.$parts[2].'_extra_tablenav', [$this, 'extra_tablenav']] : [], |
| 584 |
wp_is_json_request() ? '' : ['wpjam_html', [$this, 'filter_table']] |
| 585 |
]); |
| 586 |
|
| 587 |
in_array($data_type, ['post_type', 'taxonomy']) && wpjam_once('maybe', 'parse_term_query', function($query){ |
| 588 |
if(!array_any(debug_backtrace(), fn($v)=> ($v['class'] ?? '') == $this->builtin)){ |
| 589 |
return false; |
| 590 |
} |
| 591 |
|
| 592 |
$vars = &$query->query_vars; |
| 593 |
$by = $vars['orderby'] ?? ''; |
| 594 |
$object = $by && is_string($by) ? $this->component('column', $by) : null; |
| 595 |
$type = $object ? ($object->sortable === true ? 'meta_value' : $object->sortable) : ''; |
| 596 |
$vars = array_merge($vars, ['list_table_query'=>true], in_array($type, ['meta_value_num', 'meta_value']) ? ['orderby'=>$type, 'meta_key'=>$by] : []); |
| 597 |
}, 0); |
| 598 |
|
| 599 |
parent::__construct($args+['builtin'=>'WP_'.$builtin.'_List_Table', 'item_callback'=>$callback]); |
| 600 |
} |
| 601 |
|
| 602 |
public function prepare_items(){ |
| 603 |
if($this->screen->base == 'edit'){ |
| 604 |
$_GET['post_type'] = $this->post_type; |
| 605 |
} |
| 606 |
|
| 607 |
$_GET = array_merge($_GET, wpjam_params('data')); |
| 608 |
$_POST = array_merge($_POST, wpjam_params('data')); |
| 609 |
|
| 610 |
$this->builtin_prepare_items(); |
| 611 |
} |
| 612 |
|
| 613 |
public function single_row($item){ |
| 614 |
$cb = (array)$this->item_callback; |
| 615 |
|
| 616 |
if($item = is_numeric($item) ? (array_shift($cb))($item) : $item){ |
| 617 |
if($this->data_type == 'post_type' && $item->post_type == 'attachment'){ |
| 618 |
$GLOBALS['authordata'] = get_userdata($item->post_author); |
| 619 |
|
| 620 |
echo wpjam_tag('tr', ['id'=>'post-'.$item->ID], $this->ob_get_single_row_columns($item))->add_class(['author-'.(get_current_user_id() == $item->post_author ? 'self' : 'other'), 'status-'.$item->post_status]); |
| 621 |
}else{ |
| 622 |
$this->builtin_single_row($item, ...array_map(fn($v)=> $v($item), $cb)); |
| 623 |
} |
| 624 |
} |
| 625 |
} |
| 626 |
|
| 627 |
public static function load($args){ |
| 628 |
return new static($args); |
| 629 |
} |
| 630 |
} |
| 631 |
|
| 632 |
class WPJAM_List_Table_Component extends WPJAM_Register{ |
| 633 |
public static function registry($method, ...$args){ |
| 634 |
$registry = wpjam_registry(static::class, ['config'=>['orderby'=>'order']]); |
| 635 |
|
| 636 |
if(in_array($method, ['add_object', 'remove_object']) && !is_null($args[1])){ |
| 637 |
$part = str_replace('wpjam_list_table_', '', $registry->name); |
| 638 |
$name = $args[0]; |
| 639 |
|
| 640 |
if($data_type = wpjam_admin('data_type', $args[1])){ |
| 641 |
$args[0] .= '__'.md5(wpjam_serialize($data_type)); |
| 642 |
} |
| 643 |
|
| 644 |
if($method == 'add_object'){ |
| 645 |
if($part == 'action'){ |
| 646 |
if(!empty($args[1]['update_setting'])){ |
| 647 |
$model = wpjam_admin('list_table', 'model'); |
| 648 |
$args[1] += ['overall'=>true, 'callback'=>[$model, 'update_setting'], 'value_callback'=>[$model, 'get_setting']]; |
| 649 |
} |
| 650 |
|
| 651 |
if(!empty($args[1]['overall']) && $args[1]['overall'] !== true){ |
| 652 |
$registry->$method($name.'_all', ['overall'=>true, 'title'=>wpjam_pull($args[1], 'overall')]+$args[1]); |
| 653 |
} |
| 654 |
}elseif($part == 'column'){ |
| 655 |
$args[1]['_field'] = wpjam_field(wpjam_pick($args[1], ['name', 'options'])+['type'=>'view', 'wrap_tag'=>'', 'key'=>$name]); |
| 656 |
} |
| 657 |
|
| 658 |
$args[1] = new static($name, $args[1]); |
| 659 |
}else{ |
| 660 |
if(!static::get($args[0])){ |
| 661 |
return wpjam_admin('removed_'.$part.'s[]', $name); |
| 662 |
} |
| 663 |
} |
| 664 |
} |
| 665 |
|
| 666 |
return $registry->$method(...$args); |
| 667 |
} |
| 668 |
} |
| 669 |
|
| 670 |
class WPJAM_List_Table_Action extends WPJAM_List_Table_Component{ |
| 671 |
public function __get($key){ |
| 672 |
$value = parent::__get($key); |
| 673 |
|
| 674 |
if(!is_null($value)){ |
| 675 |
return $value; |
| 676 |
} |
| 677 |
|
| 678 |
if($key == 'page_title'){ |
| 679 |
return $this->title ? wp_strip_all_tags($this->title.wpjam_admin('list_table', 'title')) : ''; |
| 680 |
}elseif($key == 'response'){ |
| 681 |
return $this->next ? 'form' : ($this->overall && $this->name != 'add' ? 'list' : $this->name); |
| 682 |
}elseif($key == 'row_action'){ |
| 683 |
return $this->bulk !== 'only' && $this->name != 'add'; |
| 684 |
}elseif(in_array($key, ['layout', 'model', 'builtin', 'form_data', 'primary_key', 'data_type', 'capability', 'next_actions']) || ($this->data_type && $this->data_type == $key)){ |
| 685 |
return wpjam_admin('list_table', $key); |
| 686 |
} |
| 687 |
} |
| 688 |
|
| 689 |
public function __toString(){ |
| 690 |
return $this->title; |
| 691 |
} |
| 692 |
|
| 693 |
public function __call($method, $args){ |
| 694 |
if($this->mod($method, 'prev')){ |
| 695 |
$prev = self::get($this->prev ?: array_search($this->name, $this->next_actions ?: [])); |
| 696 |
|
| 697 |
return $prev ? [$prev, $action](...$args) : []; |
| 698 |
}elseif($this->mod($method, 'parse_')){ |
| 699 |
$args = $args[0]; |
| 700 |
|
| 701 |
if($method == 'nonce_action'){ |
| 702 |
return wpjam_join('-', $this->name, empty($args['bulk']) ? ($args['id'] ?? '') : ''); |
| 703 |
}elseif($method == 'arg' && !$this->overall){ |
| 704 |
return is_scalar($args) ? $args : ((int)$args['bulk'] === 2 ? (($args['id'] ?? '') ?: $args['ids']) : $args[$args['bulk'] ? 'ids' : 'id']); |
| 705 |
}elseif($method == 'id' && !$this->overall){ |
| 706 |
return $args['bulk'] ? null : $args['id']; |
| 707 |
} |
| 708 |
} |
| 709 |
} |
| 710 |
|
| 711 |
public function __invoke($type){ |
| 712 |
$params = $this->get_param(['id', 'ids', 'bulk', 'submit'], $type == 'export' ? 'get' : 'post'); |
| 713 |
$id = $params['id']; |
| 714 |
$bulk = $params['bulk']; |
| 715 |
$ids = wp_parse_args($params['ids'] ?: []); |
| 716 |
$bulk = is_numeric($bulk) ? (int)$bulk : ($bulk && $bulk !== 'false' ? 1 : 0); |
| 717 |
$args = ['bulk'=>&$bulk, 'id'=>$id, 'ids'=>$ids]; |
| 718 |
$data = wpjam_params('data'); |
| 719 |
|
| 720 |
if($type == 'submit'){ |
| 721 |
$args += ['fields'=>$this->get_fields($args, true)]; |
| 722 |
$data = $args['fields']->validate($data); |
| 723 |
}elseif(in_array($type, ['direct', 'form'])){ |
| 724 |
$data += $this->overall ? $this->form_data : []; |
| 725 |
} |
| 726 |
|
| 727 |
$args += ['data'=>$data]; |
| 728 |
$submit = in_array($type, ['submit', 'export']) ? ($params['submit'] ?: $this->name) : null; |
| 729 |
$button = $submit ? wpjam_button($this, $submit, $this->parse_arg($args)) : []; |
| 730 |
|
| 731 |
if(in_array($type, ['submit', 'direct']) && (($button['export'] ?? $this->export) || ($this->bulk === 'export' && $bulk))){ |
| 732 |
return ['type'=>'redirect', 'args'=>['submit'=>$submit]+$this->get_attr($args, 'export')]; |
| 733 |
} |
| 734 |
|
| 735 |
$resp = $this->is_allowed($args) ? $this->pick(['page_title', 'last', 'width'])+['bulk'=>&$bulk, 'id'=>&$id, 'ids'=>$ids] : wp_die('access_denied'); |
| 736 |
|
| 737 |
if($type == 'form'){ |
| 738 |
return $resp+['type'=>'form', 'form'=>$this->get_form($args, $type)]; |
| 739 |
} |
| 740 |
|
| 741 |
$resp += ['list_action'=>$this->name, 'type'=>($button['response'] ?? '') ?: $this->response]; |
| 742 |
$bulk = $bulk === 2 ? 0 : $bulk; |
| 743 |
|
| 744 |
if($resp['type'] != 'form'){ |
| 745 |
$args += wpjam_fill(['callback', 'bulk_callback'], fn($k)=> ($button[$k] ?? '') ?: $this->$k); |
| 746 |
$result = $this->callback(['data'=>$data, 'submit'=>$submit]+$args); |
| 747 |
|
| 748 |
if(is_array($result)){ |
| 749 |
$err = wpjam_pull($result, ['errcode', 'errmsg']); |
| 750 |
$resp += empty($err['errcode']) ? ['notice'=>wpjam_pull($result, 'notice') ?: ($err['errmsg'] ?? '')] : $err; |
| 751 |
$resp += wpjam_pull($result, ['args']); |
| 752 |
|
| 753 |
if($this->layout == 'calendar'){ |
| 754 |
$resp['data'] = $result; |
| 755 |
}else{ |
| 756 |
if(wpjam_pick($result, ['type', 'bulk', 'ids', 'id', 'items'])){ |
| 757 |
[$resp, $result] = [$result+$resp, null]; |
| 758 |
} |
| 759 |
} |
| 760 |
}else{ |
| 761 |
$resp['notice'] = $button ? $button['text'].'成功' : ''; |
| 762 |
|
| 763 |
if(in_array($resp['type'], ['add', 'duplicate']) && !$resp['bulk'] && $this->layout != 'calendar'){ |
| 764 |
[$id, $result] = [$result, null]; |
| 765 |
} |
| 766 |
} |
| 767 |
|
| 768 |
if($resp['type'] == 'append'){ |
| 769 |
return $resp+array_filter(['data'=>$result]); |
| 770 |
}elseif($resp['type'] == 'redirect'){ |
| 771 |
return $resp+$this->pick(['target'])+(is_string($result) ? ['url'=>$result] : []); |
| 772 |
} |
| 773 |
|
| 774 |
if($result){ |
| 775 |
$resp['result'] = $result; |
| 776 |
} |
| 777 |
} |
| 778 |
|
| 779 |
if($submit){ |
| 780 |
if(($button['dismiss'] ?? $this->dismiss) || array_find($resp['type'] == 'items' ? $resp['items'] : [$resp], fn($v)=> !empty($v['dismiss']) || $v['type'] == 'delete')){ |
| 781 |
return ['dismiss'=>true]+$resp; |
| 782 |
} |
| 783 |
|
| 784 |
if($next = self::get($this->next)){ |
| 785 |
$resp = $next->pick(['page_title'])+$this->pick(['next'])+$resp; |
| 786 |
} |
| 787 |
|
| 788 |
if($resp['type'] == 'form'){ |
| 789 |
if($next){ |
| 790 |
$resp['notice'] = ''; |
| 791 |
} |
| 792 |
}else{ |
| 793 |
$data = wpjam_params('defaults'); |
| 794 |
} |
| 795 |
|
| 796 |
$resp['form'] = ($next ?: $this)->get_form(['data'=>$data, 'bulk'=>$bulk, 'id'=>$id, 'ids'=>$ids], $type); |
| 797 |
} |
| 798 |
|
| 799 |
return $resp; |
| 800 |
} |
| 801 |
|
| 802 |
public function callback($args){ |
| 803 |
$name = $this->name; |
| 804 |
$bulk = $args['bulk']; |
| 805 |
$data = $args['data']; |
| 806 |
|
| 807 |
if(in_array($name, ['up', 'down'])){ |
| 808 |
$data = $data+[$name=>true]; |
| 809 |
$name = 'move'; |
| 810 |
} |
| 811 |
|
| 812 |
$params = [$this->parse_arg($args), $data]; |
| 813 |
$noid = $this->overall; |
| 814 |
|
| 815 |
if($cb = $args[($bulk ? 'bulk_' : '').'callback'] ?? ''){ |
| 816 |
if(!$noid && !$bulk && ($this->response == 'add' || $name == 'add') && !is_null($data)){ |
| 817 |
$result = wpjam_get_reflection($cb, 'Parameters') ?: []; |
| 818 |
$noid = count($result) <= 1 || $result[0]->name == 'data'; |
| 819 |
} |
| 820 |
|
| 821 |
$noid && array_shift($params); |
| 822 |
|
| 823 |
return wpjam_trap($cb, ...[...$params, $args['submit'] ?: $this->name, 'throw']) ?? wp_die('「'.$this->title.'」的回调函数无效或没有正确返回'); |
| 824 |
} |
| 825 |
|
| 826 |
if($bulk){ |
| 827 |
$cb = [$this->model, 'bulk_'.$name]; |
| 828 |
|
| 829 |
if(method_exists(...$cb)){ |
| 830 |
return wpjam_try($cb, ...$params) ?? true; |
| 831 |
} |
| 832 |
|
| 833 |
return array_reduce($args['ids'], fn($c, $id)=> wpjam_merge($c, is_array($r = $this->callback(['id'=>$id, 'bulk'=>false]+$args)) ? $r : []), []) ?: true; |
| 834 |
} |
| 835 |
|
| 836 |
$m = $name == 'duplicate' && !$this->direct ? 'insert' : (['add'=>'insert', 'edit'=>'update'][$name] ?? $name); |
| 837 |
$cb = [$this->model, $m]; |
| 838 |
|
| 839 |
if($noid || $m == 'insert' || $this->response == 'add'){ |
| 840 |
array_shift($params); |
| 841 |
}elseif(method_exists(...$cb)){ |
| 842 |
$this->direct && is_null($data) && array_pop($params); |
| 843 |
}elseif($this->meta_type || !method_exists($cb[0], '__callStatic')){ |
| 844 |
$cb[1] = 'update_callback'; |
| 845 |
|
| 846 |
if(!method_exists(...$cb)){ |
| 847 |
$params = [wpjam_admin('meta_type'), ...$params]; |
| 848 |
$cb = $params[0] ? 'wpjam_update_metadata' : wp_die('「'.$cb[0].'->'.$name.'」未定义'); |
| 849 |
} |
| 850 |
|
| 851 |
$params[] = $args['fields']->get_defaults(); |
| 852 |
} |
| 853 |
|
| 854 |
return wpjam_try($cb, ...$params) ?? true ; |
| 855 |
} |
| 856 |
|
| 857 |
public function is_allowed($args=[]){ |
| 858 |
return $this->capability == 'read' || array_all($args && !$this->overall ? (array)$this->parse_arg($args) : [null], fn($id)=> wpjam_can($this->capability, $id, $this->name)); |
| 859 |
} |
| 860 |
|
| 861 |
public function get_data($id, $type=''){ |
| 862 |
$cb = $type ? $this->data_callback : null; |
| 863 |
$data = $cb ? (is_callable($cb) ? wpjam_try($cb, $id, $this->name) : wp_die($this->title.'的 data_callback 无效')) : null; |
| 864 |
|
| 865 |
if($type == 'prev'){ |
| 866 |
return array_merge($this->get_prev_data($id, 'prev'), ($data ?: [])); |
| 867 |
} |
| 868 |
|
| 869 |
if($id && !$cb){ |
| 870 |
$data = wpjam_try([$this->model, 'get'], $id); |
| 871 |
$data = $data instanceof WPJAM_Register ? $data->to_array() : ($data ?: wp_die('无效的 ID「'.$id.'」')); |
| 872 |
} |
| 873 |
|
| 874 |
return $data; |
| 875 |
} |
| 876 |
|
| 877 |
public function get_form($args, $type=''){ |
| 878 |
$id = $this->parse_id($args); |
| 879 |
$data = $type == 'submit' && $this->response == 'form' ? [] : $this->get_data($id, 'callback'); |
| 880 |
$fields = $this->get_fields(wpjam_merge($args, array_filter(['data'=>$data]))); |
| 881 |
$args = wpjam_merge($args, ['data'=>($id && $type == 'form' ? $this->get_prev_data($id, 'prev') : [])]); |
| 882 |
|
| 883 |
return $fields->wrap('form', [ |
| 884 |
'id' => 'list_table_action_form', |
| 885 |
'data' => $this->get_attr($args, 'form'), |
| 886 |
'button' => wpjam_button($this, null, $this->parse_arg($args))->prepend($this->render_prev(['class'=>['button'], 'title'=>'上一步']+$args)) |
| 887 |
]); |
| 888 |
} |
| 889 |
|
| 890 |
public function get_fields($args, $prev=false, $output='object'){ |
| 891 |
$arg = $this->parse_arg($args); |
| 892 |
$fields = wpjam_try('maybe_callback', $this->fields, $arg, $this->name) ?: wpjam_try($this->model.'::get_fields', $this->name, $arg); |
| 893 |
$fields = array_merge(is_array($fields) ? $fields : [], ($prev ? $this->get_prev_fields($arg, true, '') : [])); |
| 894 |
$fields = ($cb = $this->model.'::filter_fields') && wpjam_callback($cb) ? wpjam_try($cb, $fields, $arg, $this->name) : $fields; |
| 895 |
|
| 896 |
if(!in_array($this->name, ['add', 'duplicate']) && isset($fields[$this->primary_key])){ |
| 897 |
$fields[$this->primary_key]['type'] = 'view'; |
| 898 |
} |
| 899 |
|
| 900 |
if($output == 'object'){ |
| 901 |
$id = $this->parse_id($args); |
| 902 |
$args = ['id'=>$id]+$args+($id ? array_filter(['meta_type'=>wpjam_admin('meta_type'), 'model'=>$this->model]) : []); |
| 903 |
|
| 904 |
return wpjam_fields($fields, array_filter(['value_callback'=>$this->value_callback])+$args); |
| 905 |
} |
| 906 |
|
| 907 |
return $fields; |
| 908 |
} |
| 909 |
|
| 910 |
public function get_attr($args, $type='button'){ |
| 911 |
$data = wp_parse_args(($args['data'] ?? []), ($this->data ?: [])); |
| 912 |
$data += $this->layout == 'calendar' ? wpjam_pick($args, ['date']) : []; |
| 913 |
$keys = $type == 'export' ? ['export_action', '_wpnonce'] : ['action', 'nonce']; |
| 914 |
$attr = ['data'=>$data]+array_combine($keys, [$this->name, wp_create_nonce($this->parse_nonce_action($args))]); |
| 915 |
$attr += $this->overall ? [] : wpjam_pick($args, [$args['bulk'] ? 'ids' : 'id'])+($args['bulk'] ? $this->pick(['bulk', 'title']) : []); |
| 916 |
|
| 917 |
return $attr+($type == 'export' ? [] : $this->pick($type == 'button' ? ['direct', 'confirm'] : ['next'])); |
| 918 |
} |
| 919 |
|
| 920 |
public function render($args=[]){ |
| 921 |
$args += ['id'=>0, 'data'=>[], 'bulk'=>false, 'ids'=>[]]; |
| 922 |
$id = $args['id']; |
| 923 |
|
| 924 |
if(is_callable($this->show_if)){ |
| 925 |
$show_if = wpjam_trap($this->show_if, ...(!empty($args['item']) ? [$args['item'], null] : [$id, $this->name, null])); |
| 926 |
|
| 927 |
if(!$show_if){ |
| 928 |
return; |
| 929 |
}elseif(is_array($show_if)){ |
| 930 |
$args += $show_if; |
| 931 |
} |
| 932 |
}elseif($this->show_if){ |
| 933 |
if($id && !wpjam_match(($args['item'] ?? '') ?: $this->get_data($id), $this->show_if)){ |
| 934 |
return; |
| 935 |
} |
| 936 |
} |
| 937 |
|
| 938 |
if($this->builtin && $id){ |
| 939 |
if($this->data_type == 'post_type'){ |
| 940 |
if(!wpjam_compare(get_post_status($id), ...(array_filter([$this->post_status]) ?: ['!=', 'trash']))){ |
| 941 |
return; |
| 942 |
} |
| 943 |
}elseif($this->data_type == 'user'){ |
| 944 |
if($this->roles && !array_intersect(get_userdata($id)->roles, (array)$this->roles)){ |
| 945 |
return; |
| 946 |
} |
| 947 |
} |
| 948 |
} |
| 949 |
|
| 950 |
if(!$this->is_allowed($args)){ |
| 951 |
return ($v = $args['fallback'] ?? null) === true ? ($args['title'] ?? null) : $v; |
| 952 |
} |
| 953 |
|
| 954 |
$attr = wpjam_pick($args, ['class', 'style'])+['title'=>$this->page_title]; |
| 955 |
$tag = wpjam_tag($args['tag'] ?? 'a', $attr)->add_class($this->class)->style($this->style); |
| 956 |
|
| 957 |
if($this->redirect){ |
| 958 |
$href = maybe_callback($this->redirect, $id, $args); |
| 959 |
$tag = $href ? $tag->add_class('list-table-redirect')->attr(['href'=>str_replace('%id%', $id, $href)]+$this->pick(['target'])) : ''; |
| 960 |
}elseif($this->filter || $this->filter === []){ |
| 961 |
$filter = maybe_callback($this->filter, $id) ?? false; |
| 962 |
$tag = $filter === false ? '' : $tag->add_class('list-table-filter')->data('filter', array_merge(($this->data ?: []), (wpjam_is_assoc_array($filter) ? $filter : ($this->overall ? [] : wpjam_pick((array)$this->get_data($id), (array)$filter))), $args['data'])); |
| 963 |
}else{ |
| 964 |
$tag->add_class('list-table-'.(str_starts_with($this->response, 'move') ? str_replace('_', '-', $this->response).'-' : '').'action')->data($this->get_attr($args)); |
| 965 |
} |
| 966 |
|
| 967 |
$text = wpjam_icon($args) ?? ($args['title'] ?? null); |
| 968 |
$text ??= (!$tag->has_class('page-title-action') && ($this->layout == 'calendar' || !$this->title)) ? wpjam_icon($this) : null; |
| 969 |
$text ??= $this->title ?: $this->page_title; |
| 970 |
|
| 971 |
return $tag ? $tag->text($text)->wrap($args['wrap'] ?? '', $this->name) : null; |
| 972 |
} |
| 973 |
|
| 974 |
public static function registers($actions){ |
| 975 |
foreach($actions as $key => $args){ |
| 976 |
self::register($key, $args+['order'=>10.5]); |
| 977 |
} |
| 978 |
} |
| 979 |
} |
| 980 |
|
| 981 |
class WPJAM_List_Table_Column extends WPJAM_List_Table_Component{ |
| 982 |
public function __get($key){ |
| 983 |
$value = parent::__get($key); |
| 984 |
|
| 985 |
if($key == 'style'){ |
| 986 |
$value = $this->column_style ?: $value; |
| 987 |
$value = ($value && !preg_match('/\{([^\}]*)\}/', $value)) ? 'table.wp-list-table .column-'.$this->name.'{'.$value.'}' : $value; |
| 988 |
}elseif(in_array($key, ['title', 'callback', 'description', 'render'])){ |
| 989 |
$value = $this->{'column_'.$key} ?? $value; |
| 990 |
}elseif(in_array($key, ['sortable', 'sticky'])){ |
| 991 |
$value ??= $this->{$key.'_column'}; |
| 992 |
} |
| 993 |
|
| 994 |
return $value; |
| 995 |
} |
| 996 |
|
| 997 |
public function __invoke($args){ |
| 998 |
$id = $args['id']; |
| 999 |
$value = $this->_field->val(null)->value_callback($args) ?? wpjam_value_callback($args, $this->name) ?? $this->default; |
| 1000 |
|
| 1001 |
if(wpjam_is_assoc_array($value)){ |
| 1002 |
return $value; |
| 1003 |
} |
| 1004 |
|
| 1005 |
$cb = is_callable($this->callback) ? $this->callback : null; |
| 1006 |
$value = $cb ? wpjam_call($cb, $id, $this->name, $args['data'], $value) : $value; |
| 1007 |
|
| 1008 |
if($render = $this->render){ |
| 1009 |
if(is_callable($render)){ |
| 1010 |
return $render($value, $args['data'], $this->name, $id); |
| 1011 |
} |
| 1012 |
|
| 1013 |
if($this->type == 'img'){ |
| 1014 |
$size = wpjam_constrain_dimensions($this->size ?: '600x0', [600, 600]); |
| 1015 |
|
| 1016 |
return $value ? '<img src="'.wpjam_get_thumbnail($value, $size).'" '.image_hwstring($size['width']/2, $size['height']/2).' />' : ''; |
| 1017 |
}elseif($this->type == 'timestamp'){ |
| 1018 |
return $value ? wpjam_date('Y-m-d H:i:s', $value) : ''; |
| 1019 |
} |
| 1020 |
} |
| 1021 |
|
| 1022 |
return $cb ? $value : $this->filterable($value); |
| 1023 |
} |
| 1024 |
|
| 1025 |
protected function filterable($value){ |
| 1026 |
if(is_array($value)){ |
| 1027 |
return array_map([$this, 'filterable'], $value); |
| 1028 |
} |
| 1029 |
|
| 1030 |
if($value && str_contains($value, '[filter')){ |
| 1031 |
return $value; |
| 1032 |
} |
| 1033 |
|
| 1034 |
$filter = isset(wpjam_admin('list_table', 'filterable_fields')[$this->name]) ? [$this->_field->name => $value] : []; |
| 1035 |
$value = $this->options ? $this->_field->val($value)->render() : $value; |
| 1036 |
|
| 1037 |
return $filter ? ['filter'=>$filter, 'label'=>$value] : $value; |
| 1038 |
} |
| 1039 |
|
| 1040 |
public static function registers($fields){ |
| 1041 |
foreach($fields as $key => $field){ |
| 1042 |
$column = wpjam_pull($field, 'column'); |
| 1043 |
|
| 1044 |
if($field['show_admin_column'] ?? is_array($column)){ |
| 1045 |
self::register($key, ($column ?: [])+wpjam_except(array_diff_key($field, wpjam_admin('data_type', $field)), ['style', 'description', 'render'])+['order'=>10.5]); |
| 1046 |
} |
| 1047 |
} |
| 1048 |
} |
| 1049 |
} |
| 1050 |
|
| 1051 |
class WPJAM_List_Table_View extends WPJAM_List_Table_Component{ |
| 1052 |
public function __invoke(){ |
| 1053 |
if($this->_view){ |
| 1054 |
return $this->_view; |
| 1055 |
} |
| 1056 |
|
| 1057 |
$view = $this; |
| 1058 |
$cb = $this->callback; |
| 1059 |
|
| 1060 |
if($cb && is_callable($cb)){ |
| 1061 |
$view = wpjam_trap($cb, $this->name, null); |
| 1062 |
|
| 1063 |
if(!is_array($view)){ |
| 1064 |
return $view; |
| 1065 |
} |
| 1066 |
} |
| 1067 |
|
| 1068 |
if(!empty($view['label'])){ |
| 1069 |
$filter = $view['filter'] ?? []; |
| 1070 |
|
| 1071 |
return [ |
| 1072 |
$filter, |
| 1073 |
$view['label'].(is_numeric($view['count'] ?? '') ? wpjam_tag('span', ['count'], '('.$view['count'].')') : ''), |
| 1074 |
$view['class'] ?? (array_any($filter, fn($v, $k)=> (((($c = $this->get_param($k)) === null) xor ($v === null)) || $c != $v)) ? '' : 'current') |
| 1075 |
]; |
| 1076 |
} |
| 1077 |
} |
| 1078 |
|
| 1079 |
public static function registers($views){ |
| 1080 |
foreach(array_filter($views) as $name => $view){ |
| 1081 |
$name = is_numeric($name) ? 'view_'.$name : $name; |
| 1082 |
$view = is_array($view) ? array_diff_key($view, wpjam_admin('data_type', $view)) : $view; |
| 1083 |
$view = (is_string($view) || is_object($view)) ? ['_view'=>$view] : $view; |
| 1084 |
|
| 1085 |
self::register($name, $view); |
| 1086 |
} |
| 1087 |
} |
| 1088 |
} |