PluginProbe
WPJAM Basic / trunk
WPJAM Basic vtrunk
wpjam-basic / includes / class-wpjam-list-table.php

class-wpjam-list-table.php in WPJAM Basic trunk, at includes/class-wpjam-list-table.php

1,088 lines 43.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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'], '&lsaquo;')->attr('title', __('Previous page', 'wpjam')),
464 wpjam_tag('span', [], $paged.' / '.$pages),
465 wpjam_tag('a', ['next-page'], '&rsaquo;')->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'=>'&#10132;'])
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')]), ['&lsaquo;', '今日', '&rsaquo;'], ['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 }