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

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

1,481 lines 47.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 trait WPJAM_Call_Trait{
3 public function call($name, ...$args){
4 if(is_string($name) && static::mod($name, '_by_model')){
5 $cb = [$this->model, $name];
6 }else{
7 $cb = is_closure($name) ? $name : $this->$name;
8 $cb = is_closure($cb) ? $cb->bindTo($this, $this) : $cb;
9 }
10
11 return wpjam_call($cb, ...$args);
12 }
13
14 public function try($name, ...$args){
15 return wpjam_try([$this, $name], ...$args);
16 }
17
18 public function catch($name, ...$args){
19 return wpjam_catch([$this, $name], ...$args);
20 }
21
22 protected function call_dynamic_method($name, ...$args){
23 return $this->call(wpjam_dynamic_method(static::class, $name), ...$args);
24 }
25
26 public static function add_dynamic_method($name, $closure){
27 wpjam_dynamic_method(static::class, $name, $closure);
28 }
29
30 public static function mod(&$method, $mod){
31 if(str_ends_with($mod, '_')){
32 return try_prefix($method, '-', $mod);
33 }elseif(str_starts_with($mod, '_')){
34 return try_suffix($method, '-', $mod);
35 }
36
37 if(($action = str_replace('_'.$mod.'_', '_', $method)) != $method){
38 $method = $action;
39 return true;
40 }
41
42 return static::mod($method, $mod.'_') || static::mod($method, '_'.$mod);
43 }
44
45 protected static function get_param($name='', $method='data'){
46 return wpjam_param($name, $method);
47 }
48
49 public static function get_called(){
50 return strtolower(static::class);
51 }
52 }
53
54 trait WPJAM_Items_Trait{
55 use WPJAM_Call_Trait;
56
57 public function get_items($field=''){
58 return $field == ':field'
59 ? (wpjam_get_annotation(static::class, 'items_field') ?: '_items')
60 : ($this->{$field ?: $this->get_items(':field')} ?: []);
61 }
62
63 public function update_items($items, $field=''){
64 $this->{$field ?: $this->get_items(':field')} = $items;
65
66 return $this;
67 }
68
69 public function process_items($cb, $field='', ...$args){
70 $field = $field ?: $this->get_items(':field');
71 $items = $this->get_items($field);
72
73 try{
74 if(count($args) == 3){
75 [$item, $key, $action] = $args;
76
77 $args[] = $field;
78 $add = $action === 'add';
79
80 if(isset($key) ? wpjam_exists($items, $key) === $add : !$add){
81 wpjam_throw('invalid_item_key', isset($key) ? ''.$key.''.($add ? '' : '').'存在,无法'.(['add'=>'添加', 'update'=>'编辑', 'delete'=>'删除'][$action] ?? '操作') : 'key不能为空');
82 }
83
84 if(isset($item)){
85 if($add && ($max = wpjam_get_annotation(static::class, 'max_items')) && count($items) >= $max){
86 wpjam_throw('quota_exceeded', '最多'.$max.'');
87 }
88
89 if(method_exists($this, 'validate_item')){
90 wpjam_if_error($this->validate_item(...$args), 'throw');
91 }
92
93 if(method_exists($this, 'sanitize_item')){
94 $args[0] = $this->sanitize_item(...$args);
95 }
96 }
97 }
98
99 return $this->update_items(wpjam_call($cb, $items, ...$args), $field);
100 }catch(Exception $e){
101 return wpjam_catch($e);
102 }
103 }
104
105 public function get_item($key, $field=''){
106 return wpjam_get($this->get_items($field), $key);
107 }
108
109 public function get_item_arg($key, $arg, $field=''){ // deprecated
110 return $this->get_item($key.'.'.$arg, $field);
111 }
112
113 public function add_item($key, ...$args){
114 [$key, $item] = ($args && !is_bool($key) && (is_scalar($key) || is_null($key))) ? [$key, array_shift($args)] : [null, $key];
115
116 return $this->process_items(fn($items, $item)=> wpjam_add_at($items, count($items), $key, $item), $args[0] ?? '', $item, $key, 'add');
117 }
118
119 public function add_items($items, $field=''){
120 return wpjam_map($items, fn(...$args)=> $this->add_item(...[...array_pad($args, -2, null), $field]), wp_is_numeric_array($items) ? 'v' : 'kv');
121 }
122
123 public function remove_item($item, $field=''){
124 return $this->process_items(fn($items)=> array_diff($items, [$item]), $field);
125 }
126
127 public function edit_item($key, $item, $field=''){
128 return $this->update_item($key, $item, $field);
129 }
130
131 public function update_item($key, $item, $field='', $action='update'){
132 return $this->process_items(fn($items, $item)=> array_replace($items, [$key=>$item]), $field, $item, $key, $action);
133 }
134
135 public function set_item($key, $item, $field=''){
136 return $this->update_item($key, $item, $field, 'set');
137 }
138
139 public function delete_item($key, $field=''){
140 return wpjam_tap(
141 $this->process_items(fn($items)=> wpjam_except($items, $key), $field, null, $key, 'delete'),
142 fn()=> wpjam_call_method($this, 'after_delete_item', $key, $field)
143 );
144 }
145
146 public function del_item($key, $field=''){
147 return $this->delete_item($key, $field);
148 }
149
150 public function move_item($orders, $field=''){
151 return $this->process_items(fn($items)=> array_merge(wpjam_pull($items, $orders), $items), $field);
152 }
153
154 public static function get_item_actions(){
155 $args = [
156 'row_action' => false,
157 'value_callback'=> fn()=> '',
158 'data_callback' => fn($id)=> wpjam_try([static::class, 'get_item'], $id, static::get_param('i'), static::get_param('_field')),
159 'callback' => fn($id, $data, $action)=> wpjam_try([static::class, $action], $id, static::get_param($action == 'move_item' ? 'item' : 'i'), ...[...($action == 'del_item' ? [] : [$data]), static::get_param('_field')])
160 ];
161
162 return [
163 'add_item' =>['page_title'=>'新增项目', 'title'=>'新增', 'dismiss'=>true]+array_merge($args, ['data_callback'=> fn()=> []]),
164 'edit_item' =>['page_title'=>'修改项目', 'dashicon'=>'edit']+$args,
165 'del_item' =>['page_title'=>'删除项目', 'dashicon'=>'no-alt', 'class'=>'del-icon', 'direct'=>true, 'confirm'=>true]+$args,
166 'move_item' =>['page_title'=>'移动项目', 'dashicon'=>'move', 'class'=>'move-item', 'direct'=>true]+$args,
167 ];
168 }
169 }
170
171 class WPJAM_Args implements ArrayAccess, IteratorAggregate, JsonSerializable{
172 use WPJAM_Call_Trait;
173
174 protected $args;
175
176 public function __construct($args=[]){
177 $this->args = $args;
178 }
179
180 public function __get($key){
181 $args = $this->get_args();
182
183 return wpjam_exists($args, $key) ? $args[$key] : ($key == 'args' ? $args : null);
184 }
185
186 public function __set($key, $value){
187 $this->filter_args();
188
189 $this->args[$key] = $value;
190 }
191
192 public function __isset($key){
193 return wpjam_exists($this->get_args(), $key) ?: ($this->$key !== null);
194 }
195
196 public function __unset($key){
197 $this->filter_args();
198
199 unset($this->args[$key]);
200 }
201
202 #[ReturnTypeWillChange]
203 public function offsetGet($key){
204 return $this->__get($key);
205 }
206
207 #[ReturnTypeWillChange]
208 public function offsetSet($key, $value){
209 $this->__set($key, $value);
210 }
211
212 #[ReturnTypeWillChange]
213 public function offsetExists($key){
214 return $this->__isset($key);
215 }
216
217 #[ReturnTypeWillChange]
218 public function offsetUnset($key){
219 $this->__unset($key);
220 }
221
222 #[ReturnTypeWillChange]
223 public function getIterator(){
224 return new ArrayIterator($this->get_args());
225 }
226
227 #[ReturnTypeWillChange]
228 public function jsonSerialize(){
229 return $this->get_args();
230 }
231
232 protected function filter_args(){
233 return $this->args = $this->args ?: [];
234 }
235
236 public function get_args(){
237 return $this->filter_args();
238 }
239
240 public function update_args($args, $replace=null){
241 $this->args = $replace === true ? $args : ($replace === false ? 'wp_parse_args' : 'array_replace')($this->get_args(), $args);
242
243 return $this;
244 }
245
246 public function process_arg($key, $cb){
247 $value = wpjam_call($cb, $this->get_arg($key));
248
249 return is_null($value) ? $this->delete_arg($key) : $this->update_arg($key, $value);
250 }
251
252 public function get_arg($key, $default=null, $callback=false){
253 $value = wpjam_get($this->get_args(), $key);
254
255 if($callback){
256 $value ??= is_string($key) ? wpjam_callback([$this->model, 'get_'.$key]) : null;
257 $value = is_closure($value) ? $value->bindTo($this, $this) : $value;
258 $value = $callback === 'callback' ? maybe_callback($value, $this->name) : $value;
259 }
260
261 return $value ?? $default;
262 }
263
264 public function update_arg($key, $value){
265 return $this->update_args(wpjam_set($this->get_args(), $key, $value), true);
266 }
267
268 public function delete_arg($key, ...$args){
269 return $this->update_args(wpjam_except($this->get_args(), $key, ...$args), true);
270 }
271
272 public function pull($key, ...$args){
273 $this->filter_args();
274
275 return wpjam_pull($this->args, $key, ...$args);
276 }
277
278 public function pick($keys){
279 return wpjam_pick($this, $keys);
280 }
281
282 public function to_array(){
283 return $this->get_args();
284 }
285
286 public function sandbox($cb, ...$args){
287 try{
288 $archive = $this->get_args();
289
290 return wpjam_call($cb, ...$args);
291 }finally{
292 $this->args = $archive;
293 }
294 }
295
296 public function is_active(){
297 return true;
298 }
299
300 protected function parse_method($name){
301 $cb = array_find([[$this->model, $name], $this->$name], fn($v)=> wpjam_callback($v));
302
303 return is_closure($cb) ? $cb->bindTo($this, $this) : $cb;
304 }
305
306 public function call_method($name, ...$args){
307 return ($cb = $this->parse_method($name)) ? wpjam_try($cb, ...$args) : (str_starts_with($name, 'filter_') ? array_shift($args) : null);
308 }
309 }
310
311 class WPJAM_Register extends WPJAM_Args{
312 use WPJAM_Items_Trait;
313
314 public function __construct($name, $args=[]){
315 $this->args = $args = ['name'=>$name]+$args;
316
317 if($this->is_active() || !empty($args['active'])){
318 $config = static::registry('get_arg', 'config') ?: [];
319 $model = empty($config['model']) ? '' : ($args['model'] ?? '');
320 $file = ($model || !empty($args['hooks']) || !empty($args['init'])) ? wpjam_pull($args, 'file') : '';
321
322 $file && is_file($file) && include_once $file;
323
324 if($model){
325 if(is_subclass_of($model, self::class)){
326 trigger_error(''.(is_object($model) ? get_class($model) : $model).'」是 '.self::class.' 子类');
327 }
328
329 if($config['model'] === 'object' && !is_object($model)){
330 if(class_exists($model, true)){
331 $model = $args['model'] = new $model(array_merge($args, ['object'=>$this]));
332 }else{
333 trigger_error('model 无效');
334 }
335 }
336
337 foreach(['hooks'=>'add_hooks', 'init'=>'init'] as $k => $m){
338 if(($args[$k] ?? ($k == 'hooks' || ($config[$k] ?? false))) === true){
339 $args[$k] = wpjam_callback([$model, $m]);
340 }
341 }
342 }
343 }
344
345 $this->args = $args;
346 }
347
348 protected function filter_args(){
349 $name = $this->args['name'];
350
351 return in_array($name, static::registry('get_arg', 'filtered[]'))
352 ? $this->args
353 : ($this->args = apply_filters(static::registry('update_arg', 'filtered[]', $name)->name.'_args', $this->args, $name));
354 }
355
356 public function get_arg($key, $default=null, $callback=true){
357 return parent::get_arg($key, $default, $callback ? 'callback' : 'parse');
358 }
359
360 public function get_parent(){
361 return $this->sub_name ? self::get($this->name) : null;
362 }
363
364 public function get_sub($name){
365 return self::get($this->name.':'.$name);
366 }
367
368 public function get_subs(){
369 return wpjam_array(self::get_by(['name'=>$this->name]), fn($k, $v)=> $v->sub_name);
370 }
371
372 public function register_sub($name, $args){
373 return self::register($this->name.':'.$name, new static($this->name, array_merge($args, ['sub_name'=>$name])));
374 }
375
376 public function unregister_sub($name){
377 return self::unregister($this->name.':'.$name);
378 }
379
380 public static function registry($method, ...$args){
381 $called = static::class;
382 $registry = wpjam_registry($called);
383
384 if(!$registry->config){
385 $registry->config = wpjam_get_annotation($called, 'config')+['model'=>true];
386 $registry->defaults = method_exists($called, 'get_defaults') ? static::get_defaults() : [];
387 }
388
389 return wpjam_catch([$registry, $method], ...$args);
390 }
391
392 public static function register($name, $args=[]){
393 return static::registry('add_object', $name, $args);
394 }
395
396 public static function unregister($name, $args=[]){
397 static::registry('remove_object', $name, $args);
398 }
399
400 public static function get_registereds($args=[], $output='objects', $operator='and'){
401 wpjam_map(static::registry('pull', 'defaults'), [static::class, 'register'], 'kv');
402
403 $objects = static::registry('get_objects', $args, $operator);
404
405 return $output == 'names' ? array_keys($objects) : $objects;
406 }
407
408 public static function get_by(...$args){
409 return self::get_registereds(...((!$args || is_array($args[0])) ? $args : [[$args[0]=> $args[1]]]));
410 }
411
412 public static function get($name, $by='registered', $top=''){
413 if($name && $by == 'registered'){
414 return static::registry('get_object', $name) ?: static::register($name, static::registry('pull', 'defaults['.$name.']'));
415 }
416
417 if($name && $by == 'model' && strcasecmp($name, $top) !== 0){
418 return array_find(static::get_registereds(), fn($v)=> is_string($v->model) && strcasecmp($name, $v->model) === 0) ?: static::get(get_parent_class($name), $by, $top);
419 }
420 }
421
422 public static function get_setting_fields($args=[]){
423 $objects = array_filter(static::get_registereds(wpjam_pull($args, 'filter_args')), fn($v)=> !isset($v->active));
424 $options = wpjam_options($objects, $args);
425
426 if(($args['type'] ?? '') == 'select'){
427 $name = wpjam_pull($args, 'name');
428 $args += ['options'=>$options];
429
430 return $name ? [$name => $args] : $args;
431 }
432
433 return $options;
434 }
435
436 public static function get_active($key=null){
437 return wpjam_array(static::get_registereds(), fn($k, $v)=> ($v->active ?? $v->is_active()) ? [$k, $key ? $v->get_arg($key) : $v] : null, true);
438 }
439
440 public static function by_active(...$args){
441 return self::call_active((did_action(current_filter()) ? 'on_' : 'filter_').wpjam_suffix(current_filter(), '-', 'wpjam_'), ...$args);
442 }
443
444 public static function call_active($method, ...$args){
445 $type = array_find(['filter', 'get'], fn($t)=> str_starts_with($method, $t.'_'));
446
447 foreach(static::get_active() as $object){
448 $res = $object->call_method($method, ...$args);
449
450 if($type == 'filter'){
451 $args[0] = $res;
452 }elseif($type == 'get'){
453 $return = array_merge($return ?? [], is_array($res) ? $res : []);
454 }
455 }
456
457 if($type == 'filter'){
458 return $args[0];
459 }elseif($type == 'get'){
460 return $return ?? [];
461 }
462 }
463 }
464
465 class WPJAM_Registry extends WPJAM_Args{
466 public function add_object(...$args){
467 $count = count($this->objects ?: []);
468 $called = $this->called ?: 'WPJAM_Args';
469
470 if(is_object($args[0]) || is_array($args[0])){
471 $v = $args[0];
472 $k = is_object($v) ? $v->name : (wpjam_pull($v, 'name') ?: ($args[1] ?: '__'.$count));
473 }else{
474 [$k, $v] = $args;
475 }
476
477 if(is_null($v)){
478 return;
479 }
480
481 if($e = $k ? (is_numeric($k) ? ''.$k.'」为纯数字' : (is_string($k) ? '' : ''.var_export($k, true).'」不为字符串')) : '为空'){
482 return trigger_error($this->name.'的注册 name'.$e) && false;
483 }
484
485 if(is_array($v)){
486 if(!empty($v['admin']) && !is_admin()){
487 return;
488 }
489
490 $v = new $called(...(is_subclass_of($called, 'WPJAM_Register') ? [$k, $v] : [$v+['name'=>$k]]));
491 }
492
493 $this->get_object($k) && trigger_error($this->name.''.$k.'」已经注册。');
494
495 $this->update_arg('objects['.$k.']', $v);
496
497 if(wpjam_call_method($v, 'is_active') || $v->active){
498 wpjam_hooks(maybe_callback($v->pull('hooks')));
499
500 wpjam_init($v->pull('init'));
501
502 wpjam_call_method($v, 'registered');
503 }
504
505 $count == 0 && wpjam_hooks($called.'::add_hooks');
506
507 return $v;
508 }
509
510 public function remove_object($k){
511 $this->delete_arg('objects['.$k.']');
512 }
513
514 public function get_object($k){
515 return $this->get_arg('objects['.$k.']');
516 }
517
518 public function get_objects(...$args){
519 $objects = wpjam_filter($this->objects ?: [], ...$args);
520 $by = $this->get_arg('config[orderby]');
521
522 return $by ? wpjam_sort($objects, ($by === true ? 'order' : $by), ($this->get_arg('config[order]') ?? 'DESC'), 10) : $objects;
523 }
524
525 public function on(...$args){
526 foreach($this as $name => $registry){
527 foreach(current_action() == 'wpjam_api' ? ['register_json'] : ['menu_page', 'admin_load'] as $key){
528 if($registry->get_arg('config['.$key.']')){
529 if($key == 'register_json'){
530 [$registry->called, 'call_active']('register_json', $args[0]);
531 }else{
532 wpjam_add($key, array_values([$registry->called, 'get_active']($key)));
533 }
534 }
535 }
536 }
537 }
538
539 public static function get_instance($name, $args=[]){
540 static $object;
541 $object ??= new static();
542
543 if(!$object->get_args()){
544 wpjam_hooks('wpjam_api, wpjam_admin_init', [$object, 'on']);
545 }
546
547 return $object->$name ??= new static(['name'=>$name, 'called'=> is_subclass_of($name, 'WPJAM_Args') ? $name : null]+$args);
548 }
549 }
550
551 class WPJAM_Data_Type extends WPJAM_Args{
552 public function get_path($args, $item){
553 return wpjam_if_error(wpjam_call_method($this->model.'::get_path', $args, $item), 'throw');
554 }
555
556 public function with_field($action, $value, $field){
557 $res = ($cb = $this->model.'::with_field') && wpjam_callback($cb) ? wpjam_try($cb, $action, $field, $value) : ($this->{$action.'_value'} ? $this->call($action.'_value', $value, $field) : $value);
558
559 return $action == 'validate' && is_null($res) ? wpjam_throw('invalid_field_value', $field->_title.''.$value.'」的值无效') : $res;
560 }
561
562 public function query_label($value){
563 if($value && $this->model && $this->label_field){
564 if(is_array($value)){
565 wpjam_call_method($this->model.'::update_caches', $value);
566
567 return array_map(fn($v)=> ($l = $this->query_label($v)) ? ['label'=>$l, 'value'=>$v] : $v, $value);
568 }
569
570 return ($this->model::get($value) ?: [])[$this->label_field] ?? null;
571 }
572 }
573
574 public function query_items($args){
575 $args = array_filter($args ?: [], fn($v)=> !is_null($v))+['number'=>10, 'data_type'=>true];
576
577 if($this->query_items){
578 return wpjam_try($this->query_items, $args);
579 }
580
581 if($this->model){
582 $args = isset($args['model']) ? wpjam_except($args, ['data_type', 'model', 'label_field', 'id_field']) : $args;
583 $res = wpjam_try($this->model.'::query_items', $args, 'items');
584 $items = wp_is_numeric_array($res) ? $res : ($res['items'] ?? []);
585
586 return $this->label_field ? wpjam_column($items, ['label'=>$this->label_field, 'value'=>$this->id_field]) : $items;
587 }
588
589 return [];
590 }
591
592 public function nonce_action($args){
593 return $this->name.':'.wpjam_json_encode(wpjam_sort(wpjam_except($args, ['search', 'exclude', 'parent']), 'k'));
594 }
595
596 public static function register($name, $args=[]){
597 return wpjam_register(static::class, $name, $args);
598 }
599
600 public static function get_instance($name, $args=[]){
601 if(!$name){
602 return;
603 }
604
605 $model = '';
606
607 if($name == 'model'){
608 $model = $args[$name] ?? '';
609 $name = $model && class_exists($model) ? wpjam_join(':', $model, $args['label_field'], $args['id_field']) : '';
610 }
611
612 if($object = wpjam_get_registered(static::class, $name) ?? self::register($name, $model ? $args : ([
613 'post_type' => ['model'=>'WPJAM_Post', 'schema'=>['type'=>'integer'], 'label_field'=>'post_title', 'id_field'=>'ID'],
614 'taxonomy' => ['model'=>'WPJAM_Term', 'schema'=>['type'=>'integer'], 'label_field'=>'name', 'id_field'=>'term_id'],
615 'author' => ['model'=>'WPJAM_User', 'schema'=>['type'=>'integer'], 'label_field'=>'display_name', 'id_field'=>'ID'],
616 'video' => ['parse_value'=>'wpjam_get_video_mp4'],
617 ][$name] ?? null))){
618 if($model){
619 $object->validate_value ??= fn($v)=> wpjam_try($args['model'].'::get', $v) ? $v : null;
620 }
621
622 if($object->model){
623 $object->meta_type ??= wpjam_call_method($object->model.'::get_meta_type') ?: '';
624 }
625 }
626
627 return $object;
628 }
629 }
630
631 class WPJAM_JSON extends WPJAM_Register{
632 public function __invoke(){
633 $method = $this->method ?: $_SERVER['REQUEST_METHOD'];
634 $attr = $method != 'POST' && !str_ends_with($this->name, '.config') ? ['page_title', 'share_title', 'share_image'] : [];
635 $response = wpjam_try('apply_filters', 'wpjam_pre_json', [], $this, $this->name);
636 $response += ['errcode'=>0, 'current_user'=>wpjam_try('wpjam_get_current_user', $this->pull('auth'))]+$this->pick($attr);
637
638 if($this->modules){
639 $modules = maybe_callback($this->modules, $this->name, $this->args);
640 $results = array_map([static::class, 'parse_module'], wp_is_numeric_array($modules) ? $modules : [$modules]);
641 }elseif($this->callback){
642 $fields = wpjam_try('maybe_callback', $this->fields ?: [], $this->name);
643 $data = $this->fields ? ($fields ? wpjam_params($method, $fields) : []) : $this->args;
644 $results[] = wpjam_try($this->pull('callback'), $data, $this->name);
645 }elseif($this->template){
646 $results[] = is_file($this->template) ? include $this->template : '';
647 }else{
648 $results[] = wpjam_except($this->args, 'name');
649 }
650
651 foreach($results as $v){
652 if(is_array($v)){
653 $response += wpjam_pull($v, $attr);
654 $response = array_merge($response, $v);
655 }
656 }
657
658 $response = apply_filters('wpjam_json', $response, $this->args, $this->name);
659
660 foreach($attr as $k){
661 if(($v = $response[$k] ?? '') || $k != 'share_image'){
662 $response[$k] = $k == 'share_image' ? wpjam_get_thumbnail($v, '500x400') : html_entity_decode($v ?: wp_get_document_title());
663 }
664 }
665
666 return $response;
667 }
668
669 public static function redirect($name){
670 header('X-Content-Type-Options: nosniff');
671
672 rest_send_cors_headers(false);
673
674 if('OPTIONS' === $_SERVER['REQUEST_METHOD']){
675 status_header(403);
676 exit;
677 }
678
679 ini_set('display_errors', 0);
680
681 wpjam_hook('wp_die_'.(array_find(['jsonp_', 'json_'], fn($v)=> wpjam_call('wp_is_'.$v.'request')) ?: '').'handler', fn()=> 'wpjam_die_handler');
682
683 if(!try_prefix($name, '-', 'mag.')){
684 return;
685 }
686
687 $name = wpjam_prefix($name, '-', 'mag.'); // �
688 �容
689 $name = str_replace('/', '.', $name);
690
691 ($user = wpjam_get_current_user()) && ($user_id = $user['user_id'] ?? '') && wp_set_current_user($user_id);
692
693 do_action('wpjam_api', wpjam_var('json', $name));
694
695 wpjam_send_json(wpjam_catch(self::get($name) ?: wp_die('接口未定义', 'invalid_api')));
696 }
697
698 public static function get_defaults(){
699 return array_fill_keys(['post.list', 'post.calendar', 'post.get'], ['modules'=>[static::class, 'callback']])+[
700 'media.upload' => ['modules'=>['callback'=>[static::class, 'media']]],
701 'site.config' => ['modules'=>['type'=>'config']],
702 ];
703 }
704
705 public static function get_current(){
706 return wpjam_var('json');
707 }
708
709 public static function get_rewrite_rule(){
710 return [
711 ['api/([^/]+)/(.*?)\.json?$', ['module'=>'json', 'action'=>'mag.$matches[1].$matches[2]'], 'top'],
712 ['api/([^/]+)\.json?$', 'index.php?module=json&action=$matches[1]', 'top'],
713 ];
714 }
715
716 public static function parse_module($module){
717 $type = wpjam_pull($module, 'type');
718 $cb = wpjam_pull($module, 'callback');
719 $args = $module['args'] ?? $module;
720 $args = is_array($args) ? $args : wpjam_parse_shortcode_attr(stripslashes_deep($args), 'module');
721
722 if($cb){
723 return wpjam_try($cb, $args);
724 }
725
726 if($type == 'data_type'){
727 return wpjam_query('data_type', $args+['search'=>wpjam_param('s')]);
728 }elseif($type == 'setting'){
729 return wpjam_setting($args);
730 }elseif($type == 'config'){
731 return wpjam_config($args['group'] ?? '');
732 }elseif(in_array($type, ['post_type', 'taxonomy', 'media'])){
733 return wpjam_try(static::class.'::'.$type, $args);
734 }
735 }
736
737 public static function post_type($args){
738 $action = wpjam_pull($args, 'action');
739 $output = wpjam_pull($args, 'output');
740
741 if($action == 'upload'){
742 return wpjam_media($args['media'] ?? '', ['media'=>true, 'output'=>$output, 'post_id'=>(int)wpjam_param('post_id', 'post')]);
743 }
744
745 $wp = $GLOBALS['wp'];
746 $query = $GLOBALS['wp_query'];
747 $object = wpjam_get_json('object');
748 $vars = $object->query_vars ??= $wp->query_vars;
749
750 if(in_array($action, ['list', 'calendar'])){
751 $sub = $action == 'calendar' ? false : wpjam_pull($args, 'sub');
752 $args = array_diff_key($args, WPJAM_Post::get_default_args());
753
754 if($sub){
755 $query = wpjam_query($args);
756 }else{
757 $vars = array_merge(wpjam_except($vars, ['module', 'action']), $args);
758 $args += ['format'=>($action == 'calendar' ? 'date' : $action)];
759
760 $wp->query_vars = $vars = wpjam_parse_query_vars($vars, $action);
761 $wp->query_posts();
762 }
763
764 $parsed = wpjam_get_posts($query, $args);
765 $result = [];
766
767 if(!$sub && $action != 'calendar'){
768 $is = wpjam_is($query);
769 $paged = $query->get('nopaging') ? 0 : ($query->get('paged') ?: 1);
770 $result = (is_string($is) ? ['is'=>$is] : [])+['total'=>$query->found_posts];
771 $result += ['total_pages'=>$paged ? $query->max_num_pages : ($result['total'] ? 1 : 0), 'current_page'=>$paged ?: 1];
772
773 if($paged == 1 && !$query->get('s') && in_array($query->get('orderby') ?: 'date', ['date', 'post_date'], true)){
774 $result += ['next_cursor'=>($parsed && $result['total_pages'] > 1) ? array_last($parsed)['timestamp'] : 0];
775 }
776
777 if($queried = $query->get_queried_object()){
778 if(in_array($is, ['category', 'tag', 'tax'], true)){
779 $result += ['current_taxonomy'=>$queried->taxonomy, 'current_term'=>wpjam_get_term($queried, $queried->taxonomy)];
780 }elseif($is === 'author'){
781 $result += ['current_author'=>wpjam_get_user($query->get('author'))];
782 }elseif($is === 'post_type_archive'){
783 $result += ['current_post_type'=>$queried->name];
784 }
785 }
786 }
787
788 if(!$output && !$sub && ($type = $query->get('post_type')) && is_string($type)){
789 $output = wpjam_get_post_type_setting($type, 'plural');
790 }
791
792 $output = $output ?: 'posts';
793
794 return apply_filters('wpjam_posts_json', $result+[$output=>$parsed], $query, $output);
795 }elseif($action == 'get'){
796 $type = $args['post_type'] ??= '';
797 $type = $type === 'any' ? '' : $type;
798 $status = $args['post_status'] ?? '';
799 $vars = ['cache_results'=>true]+($status ? ['status'=>$status] : [])+$vars;
800
801 if($type){
802 !post_type_exists($type) && wp_die('invalid_post_type');
803
804 $name = $vars[(is_post_type_hierarchical($type) ? 'pagename' : 'name')] ?? '';
805 }else{
806 $map = wp_list_pluck(get_post_types(['_builtin'=>false, 'query_var'=>true], 'objects'), 'query_var')+['post'=>'name', 'page'=>'pagename'];
807
808 $type = array_find_key($map, fn($v)=> !empty($vars[$v]));
809 $name = $type ? $vars[$map[$type]] : null;
810 }
811
812 if(!$name){
813 $id = ($args['id'] ?? 0) ?: (int)wpjam_param('id', ['required'=>true]);
814 $type ??= get_post_type($id);
815 $vars = ['p'=>($type && (get_post_type($id) == $type)) ? $id : wp_die('invalid_post_id')]+$vars;
816 }
817
818 $vars['post_type'] = $type;
819 $wp->query_vars = $vars;
820 $wp->query_posts();
821
822 if(empty($id) && !$status && !$query->have_posts()){
823 $id = apply_filters('old_slug_redirect_post_id', null) ?: wp_die('invalid_post_id');
824
825 $wp->query_vars = ['post_type'=>'any', 'p'=>$id]+wpjam_except($vars, ['name', 'pagename']);
826 $wp->query_posts();
827 }
828
829 $parsed = $query->have_posts() ? array_first(wpjam_get_posts($query, $args)) : wp_die('invalid_parameter');
830 $output = $output ?: $parsed['post_type'];
831
832 return wpjam_pull($parsed, ['share_title', 'share_image', 'share_data'])+[$output => $parsed];
833 }
834 }
835
836 public static function taxonomy($args){
837 $object = wpjam_get_taxonomy(wpjam_get($args, 'taxonomy')) ?: wpjam_throw('invalid_taxonomy');
838 $mapping = wpjam_array(wp_parse_args(wpjam_pull($args, 'mapping') ?: []), fn($k, $v)=> [$k, wpjam_param($v)], true);
839
840 return [(wpjam_pull($args, 'output') ?: $object->plural) => array_values(wpjam_query('term', array_merge($args, $mapping)))];
841 }
842
843 public static function media($args){
844 return wpjam_media($args['media'] ?? '', $args);
845 }
846
847 public static function callback($name, $args=[]){
848 $output = $args['output'] ?? null;
849
850 [$type, $action] = explode('.', $name);
851
852 if($type == 'post'){
853 $type = wpjam_param('post_type');
854 $output ??= $action == 'get' ? 'post' : 'posts';
855 }
856
857 $modules[] = array_filter(['type'=>'post_type', 'post_type'=>$type, 'action'=>$action, 'output'=>$output]+array_intersect_key($args, WPJAM_Post::get_default_args()), fn($v)=> !is_null($v));
858
859 foreach($action == 'list' && $type && is_string($type) && !str_contains($type, ',') ? get_object_taxonomies($type) : [] as $tax){
860 if(is_taxonomy_hierarchical($tax) && wpjam_get_taxonomy_setting($tax, 'show_in_posts_rest')){
861 $modules[] = ['type'=>'taxonomy', 'taxonomy'=>$tax, 'hide_empty'=>0];
862 }
863 }
864
865 return $modules;
866 }
867
868 public static function add_hooks(){
869 wpjam_is_json_request() && wpjam_hooks('-', [
870 ['the_title', 'convert_chars'],
871 ['init', ['wp_widgets_init', 'maybe_add_existing_user_to_blog']],
872 ['wp_loaded', ['_custom_header_background_just_in_time', '_add_template_loader_filters']],
873 ['plugins_loaded', ['wp_maybe_load_widgets', 'wp_maybe_load_embeds', '_wp_customize_include', '_wp_theme_json_webfonts_handler']]
874 ]);
875 }
876
877 public static function __callStatic($method, $args){
878 if(in_array($method, ['parse_post_list_module', 'parse_post_get_module'])){
879 return wpjam_catch('WPJAM_JSON::parse_module', [
880 'type' => 'post_type',
881 'action' => str_replace(['parse_post_', '_module'], '', $method)
882 ]+($args[0] ?? []));
883 }
884 }
885 }
886
887 /**
888 * @config menu_page, admin_load, register_json, init, orderby
889 **/
890 #[config('menu_page', 'admin_load', 'register_json', 'init', 'orderby')]
891 class WPJAM_Option_Setting extends WPJAM_Register{
892 public function __invoke(){
893 flush_rewrite_rules();
894
895 $submit = $_POST['submit'] ?? '';
896 $values = $this->validate_by_fields(wpjam_params('data')) ?: [];
897 $fix = is_network_admin() ? 'site_option' : 'option';
898
899 if($this->option_type == 'array'){
900 if($submit == 'reset'){
901 $values = wpjam_diff([$this, 'get_'.$fix](), $values, 'key');
902 }else{
903 $values = wpjam_filter(array_merge([$this, 'get_'.$fix](), $values), fn($v)=> !is_null($v), true);
904 $values = $this->call_method('sanitize_callback', $values, $this->name) ?? $values;
905 }
906
907 $cb = $this->update_callback;
908 $cb ? $cb($this->name, $values) : [$this, 'update_'.$fix]($values);
909 }else{
910 wpjam_map($values, ...($submit == 'reset' ? ['delete_'.$fix, 'k'] : ['update_'.$fix, 'kv']));
911 }
912
913 $errors = array_filter(get_settings_errors(), fn($e)=> !in_array($e['type'], ['updated', 'success', 'info']));
914 $errors && wp_die(implode('&emsp;', array_column($errors, 'message')));
915
916 return [
917 'type' => (!$this->ajax || $submit == 'reset') ? 'redirect' : $submit,
918 'notice' => $submit == 'reset' ? '设置已重置。' : '设置已保存。'
919 ];
920 }
921
922 public function __call($method, $args){
923 $parts = explode('_', $method);
924 $action = array_shift($parts);
925 $type = array_pop($parts);
926
927 if($type == 'fields'){
928 $fields = $action == 'render' ? array_shift($args) : array_merge(...array_column($this->get_sections($action !== 'validate'), 'fields'));
929
930 return $action == 'get' ? $fields : wpjam_fields($fields, ['value_callback'=>[$this, 'value_callback']])->$action(...$args);
931 }elseif($type == 'option' || $type == 'setting'){
932 $part = $parts ? '_'.$parts[0] : '';
933
934 if($part != '_site'){
935 $blog_id = $part == '_blog' ? array_shift($args) : $this->blog_id;
936 $part = $blog_id && is_multisite() ? '_blog' : '';
937 }
938
939 $params = $part == '_blog' ? [$blog_id] : [];
940
941 if($type == 'option'){
942 $args = $action == 'update' ? [wpjam_if_error($args[0], [])] : [];
943 $res = wpjam_call($action.$part.'_option', ...[...$params, $this->name, ...$args]);
944
945 return $action == 'get' ? (wpjam_if_error($res, []) ?: []) : $res;
946 }
947
948 $name = $args[0] ?? null;
949 $data = [$this, 'get'.$part.'_option'](...$params) ?: [];
950
951 if($action == 'get'){
952 if(is_array($name)){
953 return wpjam_fill(array_filter($name), fn($n)=> [$this, 'get'.$part.'_setting'](...[...$params, $n]));
954 }
955
956 $site_default = $part != '_site' && is_multisite() && $this->site_default;
957
958 if(!$name){
959 return $data+($site_default ? $this->get_site_option() : []);
960 }
961
962 $value = wpjam_if_error(wpjam_get(is_array($data) ? $data : [], $name), null);
963
964 if(is_null($value)){
965 if($site_default){
966 return $this->get_site_setting(...$args);
967 }
968
969 if(count($args) >= 2){
970 return $args[1];
971 }
972
973 if($this->field_default){
974 return wpjam_get(($this->_defaults ??= $this->get_defaults_by_fields()), $name);
975 }
976 }
977
978 return is_string($value) ? str_replace("\r\n", "\n", trim($value)) : $value;
979 }
980
981 return [$this, 'update'.$part.'_option'](...[...$params, ($action == 'update'
982 ? wpjam_reduce(is_array($name) ? $name : [$name=>$args[1]], fn($c, $v, $n)=> wpjam_set($c, $n, $v), $data)
983 : wpjam_except($data, $name)
984 )]);
985 }
986 }
987
988 public function get_arg($key, $default=null, $callback=true){
989 $value = parent::get_arg($key, $default, $callback);
990
991 if($key == 'menu_page'){
992 if(!$this->name || (is_network_admin() && !$this->site_default)){
993 return;
994 }
995
996 if(!$value){
997 if(!$this->post_type || !$this->title){
998 return $value;
999 }
1000
1001 $value = ['parent'=>wpjam_get_post_type_setting($this->post_type, 'plural'), 'order'=>1];
1002 }
1003
1004 if(wp_is_numeric_array($value)){
1005 return wpjam_array($value, function($k, $v){
1006 if(!empty($v['tab_slug']) && !empty($v['plugin_page'])){
1007 return [$k, $v];
1008 }elseif(!empty($v['menu_slug'])){
1009 return [$k, $v+($v['menu_slug'] == $this->name ? ['menu_title'=>$this->title] : [])];
1010 }
1011 });
1012 }
1013
1014 $value += ($value['function'] ??= 'option') == 'option' ? ['option_name'=>$this->name] : [];
1015
1016 if(!empty($value['tab_slug'])){
1017 return ($value['plugin_page'] ??= $this->plugin_page) ? $value+['title'=>$this->title] : null;
1018 }
1019
1020 $value += ['menu_slug'=>$this->plugin_page ?: $this->name, 'menu_title'=>$this->title];
1021 }elseif($key == 'admin_load'){
1022 $value = wp_is_numeric_array($value) ? $value : ($value ? [$value] : []);
1023 $value = array_map(fn($v)=> ($this->model && !isset($v['callback']) && !isset($v['model'])) ? $v+['model'=>$this->model] : $v, $value);
1024 }elseif($key == 'sections'){
1025 if(!$value || !is_array($value)){
1026 $id = $this->type == 'section' ? (string)$this->section_id : ($this->current_tab ?: $this->sub_name ?: $this->name);
1027 $value = [$id=>array_filter(['fields'=>$this->get_arg('fields', null, false)]) ?: $this->get_arg('section') ?: []];
1028 }
1029
1030 $value = wpjam_array($value, fn($k, $v)=> is_array($v) && isset($v['fields']) ? [$k, ['fields'=>maybe_callback($v['fields'] ?? [], $k, $this->name)]+$v] : null);
1031 }
1032
1033 return $value;
1034 }
1035
1036 public function get_current($output=''){
1037 $args = wpjam_pick(wpjam_admin('vars'), ['plugin_page', 'current_tab']);
1038
1039 return $output === 'args' ? $args : ($this->get_sub(wpjam_join(':', $args)) ?: $this);
1040 }
1041
1042 protected function get_sections($all=false, $filter=true){
1043 $sections = $this->get_arg('sections');
1044 $sections = count($sections) == 1 ? array_map(fn($s)=> $s+['title'=>$this->title ?: ''], $sections) : $sections;
1045 $sections = array_reduce($all ? $this->get_subs() : [], fn($c, $v)=> array_merge($c, $v->get_sections(false, false)), $sections);
1046
1047 if(!$filter){
1048 return $sections;
1049 }
1050
1051 $args = $all ? [] : wpjam_map(self::get_current('args'), fn($v)=> ['value'=>$v, 'if_null'=>true]);
1052 $objects = wpjam_sort(self::get_by(['type'=>'section', 'name'=>$this->name]+$args), 'order', 'desc', 10);
1053
1054 foreach(array_reverse(array_filter($objects, fn($v)=> $v->order > 10))+$objects as $object){
1055 foreach(($object->get_arg('sections') ?: []) as $id => $section){
1056 $id = $id ?: array_key_first($sections);
1057 $exist = isset($sections[$id]) ? ($object->order > 10 ? wpjam_merge($section, $sections[$id]) : $sections[$id]) : []; // 字段靠前
1058 $sections = wpjam_set($sections, $id, wpjam_merge($exist, $section));
1059 }
1060 }
1061
1062 return apply_filters('wpjam_option_setting_sections', array_filter($sections, fn($v)=> isset($v['title'], $v['fields'])), $this->name);
1063 }
1064
1065 public function add_section(...$args){
1066 $keys = ['model', 'fields', 'section'];
1067 $args = is_array($args[0]) ? $args[0] : ['section_id'=>$args[0]]+(array_any($keys, fn($k)=> isset($args[1][$k])) ? $args[1] : ['fields'=>$args[1]]);
1068 $args = array_any([...$keys, 'sections'], fn($k)=>isset($args[$k])) ? $args : ['sections'=>$args];
1069
1070 return self::register(md5(wpjam_serialize($args)), new static($this->name, $args+['type'=>'section']));
1071 }
1072
1073 public function value_callback($name=''){
1074 return $this->option_type == 'array' ? (is_network_admin() ? $this->get_site_setting($name) : $this->get_setting($name)) : get_option($name, null);
1075 }
1076
1077 public function render($page){
1078 $nav = $page->tab_slug ? '' : wpjam_tag('h2', ['nav-tab-wrapper', 'wp-clearfix']);
1079 $form = wpjam_tag('form', ['method'=>'POST', 'id'=>'wpjam_option', 'novalidate']);
1080 $tabs = [];
1081
1082 foreach($this->get_sections() as $id => $sec){
1083 $nav && $nav->append('a', ['class'=>'nav-tab', 'href'=>'#tab_'.$id, 'data'=>wpjam_pick($sec, ['show_if'])], $sec['title']);
1084
1085 $tabs[] = wpjam_tag($nav ? 'div' : '', ['id'=>'tab_'.$id, 'class'=>'tab'])->append([
1086 [$nav ? 'h2' : 'h3', [], $sec['title']],
1087 wpjam_ob($sec['callback'] ?? '', $sec),
1088 wpautop($sec['summary'] ?? ''),
1089 $this->render_fields($sec['fields'])
1090 ]);
1091 }
1092
1093 $form->data('nonce', wp_create_nonce($this->option_group))->append([
1094 ...(count($tabs) == 1 ? [$tabs[0]->tag('')] : $tabs),
1095 wpjam_tag('p', ['submit'])->append([
1096 get_submit_button('', 'primary', 'save', false),
1097 $this->reset ? get_submit_button('重置选项', 'secondary', 'reset', false) : ''
1098 ])
1099 ]);
1100
1101 return count($tabs) > 1 && $nav ? $form->before($nav)->wrap('div', ['tabs']) : $form;
1102 }
1103
1104 public function page_load(){
1105 wpjam_ajax('wpjam-option-action', [
1106 'admin' => true,
1107 'callback' => $this,
1108 'nonce_action' => fn()=> $this->option_group,
1109 'allow' => fn()=> current_user_can($this->capability)
1110 ]);
1111 }
1112
1113 public static function create($name, $args){
1114 $args = maybe_callback($args, $name)+[
1115 'option_group' => $name,
1116 'option_page' => $name,
1117 'option_type' => 'array',
1118 'capability' => 'manage_options',
1119 'ajax' => true
1120 ];
1121
1122 if($sub = wpjam_pick($args, ['plugin_page', 'current_tab'])){
1123 $rest = wpjam_except($args, ['model', 'menu_page', 'admin_load', 'plugin_page', 'current_tab']);
1124 }else{
1125 $args = ['primary'=>true]+$args;
1126 }
1127
1128 if($object = self::get($name)){
1129 if(!$sub && $object->primary && trigger_error('option_setting'.''.$name.'」已经注册。'.var_export($args, true))){
1130 return $object;
1131 }
1132
1133 $object->update_args($sub ? wpjam_except($rest, 'title') : $args);
1134 }else{
1135 $args['option_type'] == 'array' && !doing_filter('sanitize_option_'.$name) && is_null(get_option($name, null)) && add_option($name, []);
1136
1137 $object = self::register($name, $sub ? $rest : $args);
1138 }
1139
1140 return $sub ? $object->register_sub(wpjam_join(':', $sub), $args) : $object;
1141 }
1142
1143 public static function get_instance($name, ...$args){
1144 if($args && in_array($args[0], ['model', 'registered'], true)){
1145 return self::get($name, ...$args);
1146 }elseif($args && $args[0] === 'object'){
1147 array_shift($args);
1148 }
1149
1150 $blog_id = is_multisite() ? ($args[0] ?? 0) : 0;
1151 $object = $blog_id ? (!is_numeric($blog_id) && trigger_error($name.':'.$blog_id) && false) : self::get($name);
1152
1153 return $object ?: wpjam_var('option:'.wpjam_join('-', $name, $blog_id), fn()=> new static($name, ['blog_id'=>$blog_id]));
1154 }
1155 }
1156
1157 class WPJAM_Meta_Type extends WPJAM_Register{
1158 public function __call($method, $args){
1159 if(in_array($method, ['get_data', 'add_data', 'update_data', 'delete_data', 'data_exists'])){
1160 $args = [$this->name, ...$args];
1161 $cb = str_replace('data', 'metadata', $method);
1162 }elseif($this->mod($method, '_by_mid')){
1163 $args = [$this->name, ...$args];
1164 $cb = $method.'_metadata_by_mid';
1165 }elseif($this->mod($method, '_meta')){
1166 $cb = [$this, $method.'_data'];
1167 }elseif($this->mod($method, 'meta')){
1168 $cb = [$this, $method];
1169 }
1170
1171 return $cb(...$args);
1172 }
1173
1174 public function get_options($args=[]){
1175 if($this->name == 'post'){
1176 $key = 'post_type';
1177 $cb = 'wpjam_get_post_type_object';
1178 }elseif($this->name == 'term'){
1179 $key = 'taxonomy';
1180 $cb = 'wpjam_get_taxonomy';
1181 }
1182
1183 if(isset($key) && isset($args[$key])){
1184 $args[$key] = ['value'=>($value = $args[$key]), 'if_null'=>true, 'callable'=>true];
1185 $object = $cb($value);
1186
1187 $object && ($this->call_option($value.'_base') || $this->call_option($value.'_base', [
1188 $key => $value,
1189 'title' => $this->name == 'post' ? '基础信息' : '快速编辑',
1190 'row_action' => $this->name == 'term',
1191 'action_name' => 'set',
1192 'page_title' => '设置'.$object->title,
1193 'fields' => [$object, 'get_fields'],
1194 'list_table' => $object->show_ui,
1195 'order' => 99,
1196 ]));
1197 }
1198
1199 if(isset($args['list_table'])){
1200 $args['title'] = true;
1201 $args['list_table'] = $args['list_table'] ? true : ['compare'=>'!==', 'value'=>'only'];
1202 }
1203
1204 return wpjam_sort(wpjam_filter($this->get_arg('options[]'), $args), 'order', 'DESC', 10);
1205 }
1206
1207 public function call_option($name, ...$args){
1208 $del = try_prefix($name, '-', '-');
1209 $key = 'options['.$name.']';
1210
1211 if($del){
1212 return $this->delete_arg($key) && true;
1213 }
1214
1215 if($args){
1216 $args = $args[0];
1217
1218 if($this->name == 'post'){
1219 $args += ['fields'=>[], 'priority'=>'default'];
1220
1221 $args['post_type'] ??= wpjam_pull($args, 'post_types') ?: null;
1222 }elseif($this->name == 'term'){
1223 $args['taxonomy'] ??= wpjam_pull($args, 'taxonomies') ?: null;
1224
1225 if(!isset($args['fields'])){
1226 $args['fields'] = [$name => wpjam_except($args, 'taxonomy')];
1227 $args['from_field'] = true;
1228 }
1229 }
1230
1231 $this->update_arg($key, new WPJAM_Meta_Option(['name'=>$name, 'meta_type'=>$this->name]+$args));
1232 }
1233
1234 return $this->get_arg($key);
1235 }
1236
1237 public function get_table(){
1238 return _get_meta_table($this->name);
1239 }
1240
1241 public function get_column($name='object_id'){
1242 return $name == 'id'
1243 ? ('user' == $this->name ? 'umeta_id' : 'meta_id')
1244 : ($name == 'object_id' ? $this->name.'_id' : $name);
1245 }
1246
1247 public function get_data_with_default($id, ...$args){
1248 if(!$args){
1249 return $this->get_data($id);
1250 }
1251
1252 if($id && $args[0]){
1253 if(is_array($args[0])){
1254 return wpjam_array($args[0], fn($k, $v)=> [is_numeric($k) ? $v : $k, $this->get_data_with_default($id, ...(is_numeric($k) ? [$v, null] : [$k, $v]))]);
1255 }
1256
1257 if($args[0] == 'meta_input'){
1258 trigger_error('meta_input');
1259 return wpjam_map($this->get_data($id), fn($v, $k)=> $this->get_data($id, $k, true));
1260 }
1261
1262 if($this->data_exists($id, $args[0])){
1263 return $this->get_data($id, $args[0], true);
1264 }
1265 }
1266
1267 return is_array($args[0]) ? [] : ($args[1] ?? null);
1268 }
1269
1270 public function get_by_key($key, $value=null, $column=null){
1271 global $wpdb;
1272
1273 $where = array_filter([
1274 $key ? $wpdb->prepare('meta_key=%s', $key) : '',
1275 is_null($value) ? '' : $wpdb->prepare('meta_value=%s', maybe_serialize($value))
1276 ]);
1277
1278 if($where){
1279 $table = $this->get_table();
1280 $where = implode(' AND ', $where);
1281 $data = $wpdb->get_results("SELECT * FROM {$table} WHERE {$where}", ARRAY_A) ?: [];
1282
1283 return $data && $column ? array_first($data)[$this->get_column($column)] : array_map(fn($v)=> ['meta_value'=>maybe_unserialize($v['meta_value'])]+$v, $data);
1284 }
1285
1286 return $column ? null : [];
1287 }
1288
1289 public function update_data_with_default($id, $key, ...$args){
1290 if(is_array($key)){
1291 if(wpjam_is_assoc_array($key)){
1292 $defaults = $args && is_array($args[0]) ? $args[0] : [];
1293
1294 if(isset($key['meta_input']) && wpjam_is_assoc_array($key['meta_input'])){
1295 $this->update_data_with_default($id, wpjam_pull($key, 'meta_input'), wpjam_pull($defaults, 'meta_input'));
1296 }
1297
1298 wpjam_map($key, fn($v, $k)=> $this->update_data_with_default($id, $k, $v, wpjam_pull($defaults, $k)));
1299 }
1300
1301 return true;
1302 }
1303
1304 [$value, $default] = array_pad($args, 2, null);
1305
1306 if(is_closure($value)){
1307 $value = $value($this->get_data_with_default($id, $key, $default), $key, $id);
1308 }
1309
1310 if(is_array($value)){
1311 if($value && (!is_array($default) || array_diff_assoc($default, $value))){
1312 return $this->update_data($id, $key, $value);
1313 }
1314 }else{
1315 if(isset($value) && ((is_null($default) && ($value || is_numeric($value))) || (!is_null($default) && $value != $default))){
1316 return $this->update_data($id, $key, $value);
1317 }
1318 }
1319
1320 return $this->delete_data($id, $key);
1321 }
1322
1323 public function delete_empty_data(){
1324 $wpdb = $GLOBALS['wpdb'];
1325 $mids = $wpdb->get_col("SELECT ".$this->get_column('id')." FROM ".$this->get_table()." WHERE meta_value = ''") ?: [];
1326
1327 array_walk($mids, [$this, 'delete_by_mid']);
1328 }
1329
1330 public function delete_by_key($key, $value=''){
1331 return delete_metadata($this->name, null, $key, $value, true);
1332 }
1333
1334 public function delete_by_id($id){
1335 $wpdb = $GLOBALS['wpdb'];
1336 $table = $this->get_table();
1337 $column = $this->get_column();
1338 $mids = $wpdb->get_col($wpdb->prepare("SELECT meta_id FROM {$table} WHERE {$column} = %d ", $id)) ?: [];
1339
1340 array_walk($mids, [$this, 'delete_by_mid']);
1341 }
1342
1343 public function update_cache($ids){
1344 update_meta_cache($this->name, $ids);
1345 }
1346
1347 public function cleanup(){
1348 $wpdb = $GLOBALS['wpdb'];
1349 $key = $this->object_key;
1350 $table = $key ? $wpdb->{$this->name.'s'} : '';
1351
1352 if(!$key){
1353 $model = $this->object_model;
1354
1355 if(!$model || !is_callable([$model, 'get_table'])){
1356 return;
1357 }
1358
1359 $table = $model::get_table();
1360 $key = $model::get_primary_key();
1361 }
1362
1363 if(is_multisite() && !str_starts_with($this->get_table(), $wpdb->prefix) && wpjam_lock($this->name.':meta_type:cleanup', DAY_IN_SECONDS, true)){
1364 return;
1365 }
1366
1367 $mids = $wpdb->get_col("SELECT m.".$this->get_column('id')." FROM ".$this->get_table()." m LEFT JOIN ".$table." t ON t.".$key." = m.".$this->get_column()." WHERE t.".$key." IS NULL") ?: [];
1368
1369 array_walk($mids, [$this, 'delete_by_mid']);
1370 }
1371
1372 public function create_table(){
1373 $table = $this->get_table();
1374 $column = $this->name.'_id';
1375
1376 if($table != $GLOBALS['wpdb']->get_var("show tables like '{$table}'")){
1377 $GLOBALS['wpdb']->query("CREATE TABLE {$table} (
1378 meta_id bigint(20) unsigned NOT NULL auto_increment,
1379 {$column} bigint(20) unsigned NOT NULL default '0',
1380 meta_key varchar(255) default NULL,
1381 meta_value longtext,
1382 PRIMARY KEY (meta_id),
1383 KEY {$column} ({$column}),
1384 KEY meta_key (meta_key(191))
1385 )");
1386 }
1387 }
1388
1389 public function register_keys(){
1390 if($this->name == 'post'){
1391 foreach(get_post_types(['show_in_rest'=>true]) as $post_type){
1392 foreach($this->get_options(['post_type'=>$post_type]) as $option){
1393 if(wpjam_is_assoc_array($option->fields)){
1394 wpjam_fields($option->fields)->register_meta($this->name, $post_type);
1395 }
1396 }
1397 }
1398 }
1399 }
1400
1401 public function delete_if_default($check, $id, $key, $value){
1402 $type = $this->name;
1403 $subtype = $type == 'post' ? get_post_type($id) : ($type == 'term' ? get_term_taxonomy($id) : '');
1404 $registered = $subtype ? (get_registered_meta_keys($type, $subtype)[$key] ?? null) : null;
1405 $registered ??= get_registered_meta_keys($type)[$key] ?? null;
1406
1407 return $registered && array_key_exists('default', $registered) && $value === $registered['default']
1408 ? (delete_metadata($type, $id, $key) || true)
1409 : $check;
1410 }
1411
1412 public function registered(){
1413 add_action('init', [$this, 'register_keys'], 99);
1414
1415 wpjam_hooks('add_'.$this->name.'_metadata, update_'.$this->name.'_metadata', [$this, 'delete_if_default'], 10, 4);
1416 }
1417
1418 public static function get_defaults(){
1419 return array_merge([
1420 'post' => ['object_model'=>'WPJAM_Post', 'object_column'=>'title', 'object_key'=>'ID'],
1421 'term' => ['object_model'=>'WPJAM_Term', 'object_column'=>'name', 'object_key'=>'term_id'],
1422 'user' => ['object_model'=>'WPJAM_User', 'object_column'=>'display_name','object_key'=>'ID'],
1423 ], (is_multisite() ? [
1424 'blog' => ['object_key'=>'blog_id'],
1425 'site' => [],
1426 ] : []));
1427 }
1428 }
1429
1430 class WPJAM_Meta_Option extends WPJAM_Args{
1431 public function __get($key){
1432 $value = parent::__get($key);
1433
1434 if(isset($value)){
1435 return $value;
1436 }
1437
1438 if($key == 'list_table'){
1439 return did_action('current_screen') && !empty($GLOBALS['plugin_page']);
1440 }elseif($key == 'show_in_rest'){
1441 return true;
1442 }elseif($key == 'show_in_posts_rest'){
1443 return $this->show_in_rest;
1444 }
1445 }
1446
1447 public function __call($method, $args){
1448 if($method == 'prepare' && ($this->callback || $this->update_callback)){
1449 return [];
1450 }
1451
1452 $id = array_shift($args);
1453 $fields = maybe_callback($this->fields, $id, $this->name);
1454
1455 if($method == 'get_fields'){
1456 return $fields;
1457 }
1458
1459 $object = wpjam_fields($fields, array_merge($this->get_args(), ['id'=>$id]));
1460
1461 if($method == 'callback'){
1462 $data = wpjam_catch([$object, 'validate'], ...$args);
1463
1464 if(!wpjam_if_error($data, '')){
1465 return $data ?: true;
1466 }
1467
1468 if($callback = $this->callback ?: $this->update_callback){
1469 $res = is_callable($callback) ? call_user_func($callback, $id, $data, $fields) : false;
1470
1471 return $res === false ? new WP_Error('invalid_callback') : $res;
1472 }
1473
1474 return wpjam_update_metadata($this->meta_type, $id, $data, $object->get_defaults());
1475 }
1476
1477 $res = $object->$method(...$args);
1478
1479 return $method == 'render' ? wpjam_echo(wpautop($this->summary ?: '').$res) : $res;
1480 }
1481 }