| 1 |
<?php |
| 2 |
abstract class WPJAM_Instance{ |
| 3 |
use WPJAM_Call_Trait; |
| 4 |
|
| 5 |
protected $id; |
| 6 |
|
| 7 |
protected function __construct($id){ |
| 8 |
$this->id = $id; |
| 9 |
} |
| 10 |
|
| 11 |
public function __isset($key){ |
| 12 |
return $this->$key !== null; |
| 13 |
} |
| 14 |
|
| 15 |
public function builtin($key){ |
| 16 |
$type = wpjam_get_builtin_type(get_class($this)); |
| 17 |
$object = $type ? wpjam_call('get_'.$type, $this->id) : null; |
| 18 |
|
| 19 |
return (!$object || !$key || $key === $type) ? $object : ($key === 'data' |
| 20 |
? $object->to_array() |
| 21 |
: $object->{(str_starts_with($key, $type.'_') ? '' : $type.'_').$key} ?? ($object->$key ?? $this->meta_get($key)) |
| 22 |
); |
| 23 |
} |
| 24 |
|
| 25 |
public function meta_get($key){ |
| 26 |
return wpjam_get_metadata(static::get_meta_type(), $this->id, $key); |
| 27 |
} |
| 28 |
|
| 29 |
public function meta_exists($key){ |
| 30 |
return metadata_exists(static::get_meta_type(), $this->id, $key); |
| 31 |
} |
| 32 |
|
| 33 |
public function meta_input(...$args){ |
| 34 |
return $args ? wpjam_update_metadata(static::get_meta_type(), $this->id, ...$args) : null; |
| 35 |
} |
| 36 |
|
| 37 |
abstract protected static function call_method($method, ...$args); |
| 38 |
|
| 39 |
public static function get_meta_type(){ |
| 40 |
return static::call_method('get_meta_type') ?: wpjam_get_builtin_type(static::class); |
| 41 |
} |
| 42 |
|
| 43 |
public static function write($data, $id=0){ |
| 44 |
wpjam_try(fn()=> static::validate_data($data, $id)); |
| 45 |
|
| 46 |
$input = ($type = static::get_meta_type()) ? wpjam_pull($data, 'meta_input') : []; |
| 47 |
$data = static::sanitize_data($data, $id); |
| 48 |
|
| 49 |
return wpjam_tap( |
| 50 |
static::call_method(...($id ? ['update', $id, $data] : ['insert', $data])), |
| 51 |
fn($v)=> $input && wpjam_update_metadata($type, $id ?: $v, $input) |
| 52 |
); |
| 53 |
} |
| 54 |
|
| 55 |
public static function insert($data){ |
| 56 |
return wpjam_catch([static::class, 'write'], $data); |
| 57 |
} |
| 58 |
|
| 59 |
public static function update($id, $data){ |
| 60 |
return wpjam_catch([static::class, 'write'], $data, $id); |
| 61 |
} |
| 62 |
|
| 63 |
public static function delete($id){ |
| 64 |
return wpjam_catch(fn()=> static::before_delete($id) || true ? static::call_method('delete', $id) : null); |
| 65 |
} |
| 66 |
|
| 67 |
public static function before_delete($id){ |
| 68 |
if(array_all(['is_deletable', 'get_instance'], fn($m)=> method_exists(static::class, $m))){ |
| 69 |
wpjam_try(static::class.'->is_deletable', $id) || wpjam_throw('indelible', '不可删除'); |
| 70 |
} |
| 71 |
} |
| 72 |
|
| 73 |
protected static function validate_data($data, $id=0){ |
| 74 |
return true; |
| 75 |
} |
| 76 |
|
| 77 |
protected static function sanitize_data($data, $id=0){ |
| 78 |
return $data; |
| 79 |
} |
| 80 |
|
| 81 |
public static function instance(...$args){ |
| 82 |
static $objects = []; |
| 83 |
|
| 84 |
[$key, $cb] = count($args) == 2 && is_callable($args[1]) ? $args : [($args ? implode(':', $args) : 'singleton'), null]; |
| 85 |
$called = self::get_called(); |
| 86 |
$object = $objects[$called][$key] ?? null; |
| 87 |
|
| 88 |
if(!$object){ |
| 89 |
$object = $cb ? $cb($key) : static::create_instance(...$args); |
| 90 |
|
| 91 |
if(!is_wp_error($object) && !is_null($object)){ |
| 92 |
$objects[$called][$key] = $object; |
| 93 |
} |
| 94 |
} |
| 95 |
|
| 96 |
return $object; |
| 97 |
} |
| 98 |
|
| 99 |
protected static function create_instance(...$args){ |
| 100 |
return new static(...$args); |
| 101 |
} |
| 102 |
} |
| 103 |
|
| 104 |
abstract class WPJAM_Model extends WPJAM_Instance implements ArrayAccess, IteratorAggregate{ |
| 105 |
protected $_data = []; |
| 106 |
|
| 107 |
public function __construct($data=[], $id=null){ |
| 108 |
if($id){ |
| 109 |
$this->id = $id; |
| 110 |
$this->_data = $data ? array_diff_assoc($data, static::get($id)) : []; |
| 111 |
}else{ |
| 112 |
$id = $data[static::get_primary_key()] ?? null; |
| 113 |
$exist = isset($id) ? static::get($id) : null; |
| 114 |
|
| 115 |
$exist && ($this->id = $id); |
| 116 |
|
| 117 |
$this->_data = $exist ? array_diff_assoc($data, $exist) : $data; |
| 118 |
} |
| 119 |
} |
| 120 |
|
| 121 |
public function __get($key){ |
| 122 |
return wpjam_exists($this->get_data(), $key) ? $this->get_data()[$key] : $this->meta_get($key); |
| 123 |
} |
| 124 |
|
| 125 |
public function __isset($key){ |
| 126 |
return wpjam_exists($this->get_data(), $key) || $this->meta_exists($key); |
| 127 |
} |
| 128 |
|
| 129 |
public function __set($key, $value){ |
| 130 |
$this->set_data($key, $value); |
| 131 |
} |
| 132 |
|
| 133 |
public function __unset($key){ |
| 134 |
$this->unset_data($key); |
| 135 |
} |
| 136 |
|
| 137 |
#[ReturnTypeWillChange] |
| 138 |
public function offsetExists($key){ |
| 139 |
return wpjam_exists($this->get_data(), $key); |
| 140 |
} |
| 141 |
|
| 142 |
#[ReturnTypeWillChange] |
| 143 |
public function offsetGet($key){ |
| 144 |
return $this->get_data($key); |
| 145 |
} |
| 146 |
|
| 147 |
#[ReturnTypeWillChange] |
| 148 |
public function offsetSet($key, $value){ |
| 149 |
$this->set_data($key, $value); |
| 150 |
} |
| 151 |
|
| 152 |
#[ReturnTypeWillChange] |
| 153 |
public function offsetUnset($key){ |
| 154 |
$this->unset_data($key); |
| 155 |
} |
| 156 |
|
| 157 |
#[ReturnTypeWillChange] |
| 158 |
public function getIterator(){ |
| 159 |
return new ArrayIterator($this->get_data()); |
| 160 |
} |
| 161 |
|
| 162 |
public function get_primary_id(){ |
| 163 |
return $this->get_data(static::get_primary_key()); |
| 164 |
} |
| 165 |
|
| 166 |
public function get_data($key=''){ |
| 167 |
$data = is_null($this->id) ? [] : static::get($this->id); |
| 168 |
$data = array_merge($data, $this->_data); |
| 169 |
|
| 170 |
return $key ? ($data[$key] ?? null) : $data; |
| 171 |
} |
| 172 |
|
| 173 |
public function set_data($key, $value){ |
| 174 |
if(!is_null($this->id) && static::get_primary_key() == $key){ |
| 175 |
trigger_error('不能修改主键的值'); |
| 176 |
}else{ |
| 177 |
$this->_data[$key] = $value; |
| 178 |
} |
| 179 |
|
| 180 |
return $this; |
| 181 |
} |
| 182 |
|
| 183 |
public function unset_data($key){ |
| 184 |
$this->_data[$key] = null; |
| 185 |
} |
| 186 |
|
| 187 |
public function reset_data($key=''){ |
| 188 |
$this->_data = $key ? wpjam_except($this->_data, $key) : []; |
| 189 |
|
| 190 |
return $this; |
| 191 |
} |
| 192 |
|
| 193 |
public function to_array(){ |
| 194 |
return $this->get_data(); |
| 195 |
} |
| 196 |
|
| 197 |
public function save($data=[]){ |
| 198 |
$data = array_merge($this->_data, $data); |
| 199 |
$data = $this->id ? wpjam_except($data, static::get_primary_key()) : $data; |
| 200 |
|
| 201 |
return wpjam_tap( |
| 202 |
$this->id ? static::update($this->id, $data) : static::insert($data), |
| 203 |
fn($v)=> ($this->id = $this->reset_data()->id ?: $v) |
| 204 |
); |
| 205 |
} |
| 206 |
|
| 207 |
public static function find($id){ |
| 208 |
return static::get_instance($id); |
| 209 |
} |
| 210 |
|
| 211 |
public static function get_actions(){ |
| 212 |
return [ |
| 213 |
'add' => ['title'=>'新建', 'dismiss'=>true], |
| 214 |
'edit' => ['title'=>'编辑'], |
| 215 |
'delete' => ['title'=>'删除', 'direct'=>true, 'confirm'=>true, 'bulk'=>true, 'order'=>1], |
| 216 |
]; |
| 217 |
} |
| 218 |
|
| 219 |
public static function get_instance($id){ |
| 220 |
return $id ? static::instance($id, fn($id)=> static::get($id) ? new static([], $id) : null) : null; |
| 221 |
} |
| 222 |
|
| 223 |
public static function call_method($method, ...$args){ |
| 224 |
$name = static::get_handler(); |
| 225 |
|
| 226 |
if($method == 'insert_multi'){ |
| 227 |
array_walk($args[0], fn($v)=> wpjam_try(fn()=> static::validate_data($v))); |
| 228 |
}elseif($method == 'delete_multi'){ |
| 229 |
array_walk($args[0], fn($v)=> static::before_delete($v)); |
| 230 |
} |
| 231 |
|
| 232 |
return wpjam_call_handler($name, $method, ...$args); |
| 233 |
} |
| 234 |
|
| 235 |
public static function get_handler(){ |
| 236 |
return wpjam_get_handler(self::get_called()) ?: (property_exists(static::class, 'handler') ? static::$handler : null); |
| 237 |
} |
| 238 |
|
| 239 |
public static function set_handler($handler){ |
| 240 |
return wpjam_register_handler(self::get_called(), $handler); |
| 241 |
} |
| 242 |
|
| 243 |
public static function __callStatic($method, $args){ |
| 244 |
return in_array($method, ['item_callback', 'render_item']) |
| 245 |
? $args[0] |
| 246 |
: static::call_method(wpjam_suffix($method, '-', '_by_handler'), ...$args); |
| 247 |
} |
| 248 |
} |
| 249 |
|
| 250 |
class WPJAM_Handler extends WPJAM_Args{ |
| 251 |
public function __call($method, $args){ |
| 252 |
$method = strtolower($method); |
| 253 |
$name = array_shift($args); |
| 254 |
$object = is_object($name) ? $name : ($this)($name); |
| 255 |
|
| 256 |
if(!$object){ |
| 257 |
return new WP_Error('undefined_handler'); |
| 258 |
} |
| 259 |
|
| 260 |
if(in_array($method, ['get_primary_key', 'get_meta_type', 'get_searchable_fields', 'get_filterable_fields'])){ |
| 261 |
return $object->{wpjam_prefix($method, '-', 'get_')}; |
| 262 |
} |
| 263 |
|
| 264 |
if(!method_exists($object, $method) && $this->mod($method, '_multi')){ |
| 265 |
return wpjam_catch(fn()=> array_walk($args[0], fn($item)=> wpjam_try([$object, $method], $item)) || true); |
| 266 |
} |
| 267 |
|
| 268 |
$cb = [$object, ['get_ids'=>'get_by_ids', 'get_all'=>'get_results'][$method] ?? $method]; |
| 269 |
|
| 270 |
return is_callable($cb) ? wpjam_catch($cb, ...$args) : new WP_Error('undefined_method', [$method]); |
| 271 |
} |
| 272 |
|
| 273 |
public function __invoke($name, $args=[], $force=false){ |
| 274 |
if(!$name){ |
| 275 |
return; |
| 276 |
} |
| 277 |
|
| 278 |
if(is_array($name)){ |
| 279 |
$args = $name; |
| 280 |
$name = wpjam_pull($args, 'name') ?: md5(serialize($args)); |
| 281 |
} |
| 282 |
|
| 283 |
$object = $force ? null : $this->get_arg('handlers['.$name.']'); |
| 284 |
|
| 285 |
if(!$object && $args){ |
| 286 |
$args = maybe_closure($args, $name); |
| 287 |
$object = is_object($args) ? $args : (!empty($args['table_name']) ? WPJAM_DB::create($args) : WPJAM_Items::create($args, $name)); |
| 288 |
|
| 289 |
$object && $this->update_arg('handlers['.$name.']', $object); |
| 290 |
} |
| 291 |
|
| 292 |
return $object; |
| 293 |
} |
| 294 |
} |
| 295 |
|
| 296 |
class WPJAM_DB extends WPJAM_Args{ |
| 297 |
private $query_vars; |
| 298 |
|
| 299 |
public function __construct($table, $args=[]){ |
| 300 |
$pk = $args['primary_key'] ??= 'id'; |
| 301 |
$ck = $args['cache_key'] ?? ''; |
| 302 |
$ck = $args['cache_key'] = $ck == $pk ? '' : $ck; |
| 303 |
|
| 304 |
$this->args = wp_parse_args($args, [ |
| 305 |
'table' => $table, |
| 306 |
'orderby' => $pk, |
| 307 |
'cache' => true, |
| 308 |
'cache_group' => $table, |
| 309 |
'cache_time' => DAY_IN_SECONDS |
| 310 |
]); |
| 311 |
|
| 312 |
$this->init(); |
| 313 |
$this->process_arg('group_cache_key', fn($v)=> array_merge((array)$v, $ck ? [$ck] : [])); |
| 314 |
} |
| 315 |
|
| 316 |
public function __call($method, $args){ |
| 317 |
if($this->mod($method, 'where_')){ |
| 318 |
if($method == 'fragment'){ |
| 319 |
return $this->where('', ...$args); |
| 320 |
} |
| 321 |
|
| 322 |
if(in_array($method, ['any', 'all'])){ |
| 323 |
$args[0] = array_filter(wpjam_map($args[0], fn($v, $k)=> is_numeric($k) ? '('.$v.')' : $this->where($k, $v, 'value'))); |
| 324 |
$args[0] = implode($method == 'any' ? ' OR ' : ' AND ', $args[0]); |
| 325 |
|
| 326 |
return $this->where('', ...$args); |
| 327 |
} |
| 328 |
|
| 329 |
return $this->operator($method) ? $this->where(array_shift($args).'__'.$method, ...$args) : $this; |
| 330 |
}elseif(array_key_exists($method, $this->query_vars)){ |
| 331 |
$value = $args ? $args[0] : ($method == 'found_rows' ? true : null); |
| 332 |
|
| 333 |
if(!is_null($value)){ |
| 334 |
$this->query_vars[$method] = $value; |
| 335 |
} |
| 336 |
|
| 337 |
return $this; |
| 338 |
}elseif($this->mod($method, '_by_db')){ |
| 339 |
global $wpdb; |
| 340 |
|
| 341 |
if($method == 'last_error'){ |
| 342 |
return new WP_Error($args[0].'_error', $wpdb->$method); |
| 343 |
} |
| 344 |
|
| 345 |
if(in_array($method, ['insert', 'update', 'delete', 'replace'])){ |
| 346 |
if($this->field_types){ |
| 347 |
$wpdb->field_types = array_merge(($types = $wpdb->field_types), $this->field_types); |
| 348 |
} |
| 349 |
}elseif(in_array($method, ['get_results', 'get_ids', 'get_row', 'get_col', 'get_var'])){ |
| 350 |
if($method == 'get_ids'){ |
| 351 |
$args[0]['fields'] = $this->table.'.'.$this->primary_key; |
| 352 |
|
| 353 |
$method = 'get_col'; |
| 354 |
} |
| 355 |
|
| 356 |
if(is_array($args[0])){ |
| 357 |
$args[0] = $this->get_request($args[0]+(in_array($method, ['get_row', 'get_var']) ? ['limits'=> 'LIMIT 1'] : [])); |
| 358 |
} |
| 359 |
|
| 360 |
if(in_array($method, ['get_results', 'get_row'])){ |
| 361 |
$args[1] = ARRAY_A; |
| 362 |
} |
| 363 |
} |
| 364 |
|
| 365 |
try{ |
| 366 |
return $wpdb->$method(...$args); |
| 367 |
}finally{ |
| 368 |
isset($types) && ($wpdb->field_types = $types); |
| 369 |
} |
| 370 |
}elseif(in_array($method, ['get_col', 'get_var', 'get_row', 'get_results'])){ |
| 371 |
$clauses = $this->get_clauses($args[0] ?? []); |
| 372 |
$action = $method == 'get_results' && in_array($clauses['fields'], ['*', $this->table.'.*']) ? 'get_ids' : $method; |
| 373 |
$items = [$this, $action.'_by_db']($clauses); |
| 374 |
$total = $method == 'get_results' && ($args[1] ?? null) ? $this->find_total() : null; |
| 375 |
$items = $action == 'get_ids' ? array_values($this->get_by_ids($items)) : $items; |
| 376 |
|
| 377 |
return isset($total) ? ['items'=>$items, 'total'=>$total] : $items; |
| 378 |
}elseif(str_contains($method, '_meta')){ |
| 379 |
return ($object = wpjam_get_meta_type($this->meta_type)) ? $object->$method(...$args) : null; |
| 380 |
}elseif($this->mod($method, 'cache_')){ |
| 381 |
return [WPJAM_Cache::create($this), $method](...$args); |
| 382 |
}elseif($this->mod($method, '_last_changed')){ |
| 383 |
$ck = $this->group_cache_key; |
| 384 |
$ck = $ck && is_array($args[0] ?? '') ? array_find_key($args[0], fn($v, $k)=> !is_array($v) && in_array($k, $ck)) : []; |
| 385 |
$k = 'last_changed'.($ck ? ':'.$ck.':'.$args[0][$ck] : ''); |
| 386 |
|
| 387 |
return $method == 'get' |
| 388 |
? ($this->cache_get($k) ?: wpjam_tap(microtime(), fn($v)=> $this->cache_set($k, $v))) |
| 389 |
: ($method == 'delete' ? $this->cache_delete($k) : null); |
| 390 |
} |
| 391 |
|
| 392 |
return new WP_Error('undefined_method', [$method]); |
| 393 |
} |
| 394 |
|
| 395 |
protected function init(){ |
| 396 |
$this->meta_query = null; |
| 397 |
$this->query_vars = [ |
| 398 |
'where' => [], |
| 399 |
'limit' => 0, |
| 400 |
'offset' => 0, |
| 401 |
'orderby' => null, |
| 402 |
'order' => null, |
| 403 |
'groupby' => null, |
| 404 |
'having' => null, |
| 405 |
'found_rows' => false, |
| 406 |
'search_term' => null, |
| 407 |
'search_columns' => null |
| 408 |
]; |
| 409 |
} |
| 410 |
|
| 411 |
public function find_by($field, $value, $order='ASC', $output='results'){ |
| 412 |
return [$this, 'get_'.$output.'_by_db']([ |
| 413 |
'where' => 'WHERE '.(is_array($field) ? $this->where_all($field, 'value') : $this->where($field, $value, 'value')), |
| 414 |
'orderby' => $order ? 'ORDER BY `'.$this->get_arg('orderby').'` '.$order : '' |
| 415 |
]); |
| 416 |
} |
| 417 |
|
| 418 |
public function find_one($value, $field='', $order=''){ |
| 419 |
return $this->find_by($field ?: $this->primary_key, $value, $order, 'row'); |
| 420 |
} |
| 421 |
|
| 422 |
public function find_total(){ |
| 423 |
return $this->get_var_by_db("SELECT FOUND_ROWS();"); |
| 424 |
} |
| 425 |
|
| 426 |
public function get($id){ |
| 427 |
return $this->get_by($this->primary_key, $id); |
| 428 |
} |
| 429 |
|
| 430 |
public function operator($name){ |
| 431 |
return ($this->operator ??= array_reduce(['in', 'like', 'between', 'exists', 'regexp'], fn($c, $v)=>$c+[$v=>strtoupper($v), 'not_'.$v=>'NOT '.strtoupper($v)], ['not'=>'!=', 'lt'=>'<', 'lte'=>'<=', 'gt'=>'>', 'gte'=>'>=']))[$name] ?? null; |
| 432 |
} |
| 433 |
|
| 434 |
public function get_by($field, $value=null, $order='ASC'){ |
| 435 |
$pk = $this->primary_key; |
| 436 |
$type = is_array($field) ? '' : ($field == $pk ? 'primary' : (in_array($field, $this->group_cache_key) ? 'cache' : '')); |
| 437 |
$multi = is_array($value); |
| 438 |
|
| 439 |
if($multi){ |
| 440 |
$value = wpjam_filter(array_filter($value), 'unique'); |
| 441 |
|
| 442 |
if(!$value){ |
| 443 |
return []; |
| 444 |
} |
| 445 |
|
| 446 |
if($type == 'primary'){ |
| 447 |
$ids = $value; |
| 448 |
$data = array_filter($this->cache_get_multiple($ids) ?: [], 'is_array'); |
| 449 |
$rest = array_diff($ids, array_keys($data)); |
| 450 |
|
| 451 |
if($rest){ |
| 452 |
if($result = $this->find_by($pk, $rest)){ |
| 453 |
$result = array_column($result, null, $pk); |
| 454 |
$data += $result; |
| 455 |
$rest = array_diff($rest, array_keys($result)); |
| 456 |
|
| 457 |
$this->cache_set_multiple($result); |
| 458 |
} |
| 459 |
|
| 460 |
$rest && $this->cache_set_multiple(array_fill_keys($rest, []), 5); |
| 461 |
} |
| 462 |
|
| 463 |
if($data){ |
| 464 |
$data = wpjam_pick($data, $ids); |
| 465 |
|
| 466 |
$this->meta_type && wpjam_lazyload($this->meta_type.'_meta', array_keys($data)); |
| 467 |
|
| 468 |
wpjam_lazyload($this->lazyload_key, $data); |
| 469 |
} |
| 470 |
|
| 471 |
return $data; |
| 472 |
}elseif($type == 'cache'){ |
| 473 |
$result = wpjam_fill($value, fn($v)=> $this->query([$field=>$v, 'order'=>$order], 'cache')); |
| 474 |
$data = wpjam_map(array_filter($result, fn($v)=> is_array($v[0]) && isset($v[0]['items'])), fn($v)=> $v[0]['items']); |
| 475 |
$rest = array_diff_key($result, $data); |
| 476 |
$ids = array_merge($rest ? $this->query([$field.'__in'=>array_keys($rest), 'order'=>$order], 'ids') : [], ...array_values($data)); |
| 477 |
$result = array_values($this->get_by_ids($ids)); |
| 478 |
$data = array_map(fn($ids)=> array_values($this->get_by_ids($ids)), $data); |
| 479 |
|
| 480 |
foreach($rest as $v => $r){ |
| 481 |
$data[$v] = wp_list_filter($result, [$field => $v]) ?: []; |
| 482 |
|
| 483 |
$this->cache_set_salted($r[1], ['items'=>array_column($data[$v], $pk)], $r[2]); |
| 484 |
} |
| 485 |
|
| 486 |
return $data; |
| 487 |
} |
| 488 |
}else{ |
| 489 |
if($type && ($queue = $this->pending_queue)){ |
| 490 |
$queue = is_array($queue) ? ($queue[$field] ?? '') : ($type == 'primary' ? $queue : ''); |
| 491 |
$queue && wpjam_load_pending($queue, fn($pending)=> $this->get_by($field, $pending, $order)); |
| 492 |
} |
| 493 |
|
| 494 |
if($type == 'primary'){ |
| 495 |
return ($v = ($id = $value) ? $this->cache_get($id) : []) === false |
| 496 |
? wpjam_tap($this->find_one($id), fn($v)=> $this->cache_set($id, $v ?: [], $v ? $this->cache_time : 60)) |
| 497 |
: $v; |
| 498 |
}elseif($type == 'cache'){ |
| 499 |
return $this->query([$field=>$value, 'order'=>$order], 'items'); |
| 500 |
} |
| 501 |
} |
| 502 |
|
| 503 |
return $this->find_by($field, $value, $order); |
| 504 |
} |
| 505 |
|
| 506 |
public function get_by_values($field, $values, $order='ASC'){ |
| 507 |
return $this->get_by($field, $values, $order); |
| 508 |
} |
| 509 |
|
| 510 |
public function get_by_ids($ids){ |
| 511 |
return $this->get_by($this->primary_key, $ids); |
| 512 |
} |
| 513 |
|
| 514 |
public function get_ids($ids){ |
| 515 |
return $this->get_by_ids($ids); |
| 516 |
} |
| 517 |
|
| 518 |
public function update_caches($values, $primary=false){ |
| 519 |
return $this->get_by(($primary ? '' : $this->cache_key) ?: $this->primary_key, $values); |
| 520 |
} |
| 521 |
|
| 522 |
protected function get_clauses($fields=[]){ |
| 523 |
$table = $this->table; |
| 524 |
$pk = $this->primary_key; |
| 525 |
$vars = $this->query_vars; |
| 526 |
$meta_query = $this->meta_query; |
| 527 |
$clauses = $meta_query ? $meta_query->get_sql($this->meta_type, $table, $pk, $this) : []; |
| 528 |
$limit = (int)$vars['limit']; |
| 529 |
$offset = (int)$vars['offset']; |
| 530 |
$found_rows = $limit && $vars['found_rows']; |
| 531 |
$groupby = $this->group_by() ?: ($meta_query ? $table.'.'.$pk : ''); |
| 532 |
$orderby = $this->order_by(); |
| 533 |
$where = (($where = $this->where()) || ($clauses['where'] ?? '')) ? ($where ?: '1=1').wpjam_pull($clauses, 'where') : ''; |
| 534 |
|
| 535 |
return $clauses + [ |
| 536 |
'found_rows'=> $found_rows ? 'SQL_CALC_FOUND_ROWS' : '', |
| 537 |
'fields' => $fields ?: ($meta_query ? $table.'.*' : '*'), |
| 538 |
'where' => $where ? ' WHERE '.$where : '', |
| 539 |
'groupby' => $groupby ? ' GROUP BY '.$groupby.($vars['having'] ? ' HAVING '.$vars['having'] : '') : '', |
| 540 |
'orderby' => $orderby ? ' ORDER BY '.$orderby : '', |
| 541 |
'limits' => $limit ? ' LIMIT '.($offset ?: 0).', '.$limit : ($offset ? ' OFFSET '.$offset : '') |
| 542 |
]; |
| 543 |
} |
| 544 |
|
| 545 |
public function get_request($clauses=null){ |
| 546 |
$clauses ??= $this->get_clauses(); |
| 547 |
$fields = wpjam_pull($clauses, 'fields'); |
| 548 |
$clauses += ['fields'=>(is_array($fields) ? $this->format($fields, 'fields') : $fields) ?: '*']; |
| 549 |
|
| 550 |
return sprintf("SELECT %s %s %s FROM `{$this->table}` %s %s %s %s %s", ...array_map(fn($k)=> $clauses[$k] ?? '', ['found_rows', 'distinct', 'fields', 'join', 'where', 'groupby', 'orderby', 'limits'])); |
| 551 |
} |
| 552 |
|
| 553 |
public function get_sql($fields=[]){ |
| 554 |
return $this->get_request($this->get_clauses($fields)); |
| 555 |
} |
| 556 |
|
| 557 |
public function search(...$args){ |
| 558 |
if($args){ |
| 559 |
return $this->search_term(...$args); |
| 560 |
} |
| 561 |
|
| 562 |
$term = $this->query_vars['search_term']; |
| 563 |
$fields = $this->query_vars['search_columns'] ?: $this->searchable_fields; |
| 564 |
$fields = is_array($fields) ? (wp_is_numeric_array($fields) ? $fields : array_keys($fields)) : wp_parse_list($fields ?: ''); |
| 565 |
|
| 566 |
return $term && $fields ? $this->where_any(wpjam_array($fields, fn($i, $k)=> [$k.'__like', '%'.$term.'%'])) : $this; |
| 567 |
} |
| 568 |
|
| 569 |
public function group_by(...$args){ |
| 570 |
if($args){ |
| 571 |
return $this->groupby(...$args); |
| 572 |
} |
| 573 |
|
| 574 |
return ($by = $this->query_vars['groupby']) ? (array_any([',', '(',], fn($v)=> str_contains($by, $v)) ? $by : $this->prepare_by_db('%i', $by)) : ''; |
| 575 |
} |
| 576 |
|
| 577 |
public function order_by(...$args){ |
| 578 |
if(count($args) >= 2){ |
| 579 |
[$by, $order] = $args; |
| 580 |
|
| 581 |
if($by == 'rand'){ |
| 582 |
return 'RAND()'; |
| 583 |
}elseif(preg_match('/RAND\(([0-9]+)\)/i', $by, $matches)){ |
| 584 |
return $by; |
| 585 |
}elseif(str_contains($by, ',')){ |
| 586 |
return $by; |
| 587 |
}elseif(str_contains($by, '(') && str_contains($by, ')')){ |
| 588 |
return $by; |
| 589 |
}elseif(str_ends_with($by, '__in')){ |
| 590 |
return null; |
| 591 |
// $field = str_replace('__in', '', $by); |
| 592 |
} |
| 593 |
|
| 594 |
if($this->meta_query && ($clauses = $this->meta_query->get_clauses())){ |
| 595 |
if($mq = in_array($by, [array_first($clauses)['key'] ?? '', 'meta_value', 'meta_value_num']) |
| 596 |
? array_first($clauses) |
| 597 |
: ($clauses[$by] ?? null) |
| 598 |
){ |
| 599 |
$num = $by == 'meta_value_num'; |
| 600 |
$by = $mq['alias'].".meta_value"; |
| 601 |
|
| 602 |
return ($num ? $by.'+0' : (empty($mq['type']) ? $by : "CAST(".$by." AS {$mq['cast']})")).' '.$order; |
| 603 |
} |
| 604 |
} |
| 605 |
|
| 606 |
return $this->prepare_by_db('%i', $by).' '.$order; |
| 607 |
}elseif($args){ |
| 608 |
return $this->orderby(...$args); |
| 609 |
} |
| 610 |
|
| 611 |
$vars = $this->query_vars; |
| 612 |
$by = $vars['orderby']; |
| 613 |
$order = $vars['order'] ?? ($this->get_arg('order') ?: 'DESC'); |
| 614 |
$order = is_string($order) && 'ASC' === strtoupper($order) ? 'ASC' : 'DESC'; |
| 615 |
|
| 616 |
return isset($by) |
| 617 |
? ($by ? implode(', ', wpjam_array(is_array($by) ? $by : [$by=>$order], fn($k, $v)=> [null, $this->order_by($k, $v)], true)) : '') |
| 618 |
: (($by = $vars['groupby'] ? '' : $this->get_arg('orderby')) ? $by.' '.$order : ''); |
| 619 |
} |
| 620 |
|
| 621 |
public function insert($data){ |
| 622 |
$wpdb = $GLOBALS['wpdb']; |
| 623 |
$multi = wp_is_numeric_array($data); |
| 624 |
|
| 625 |
[$data,$id] = $multi ? [array_filter($data), null] : [array_filter($data, fn($v)=> !is_null($v)), $data[$this->primary_key] ?? null]; |
| 626 |
|
| 627 |
if($multi && !$data){ |
| 628 |
return 0; |
| 629 |
} |
| 630 |
|
| 631 |
$this->clear([], $data); |
| 632 |
|
| 633 |
if($id || $multi){ |
| 634 |
$wpdb->check_current_query = false; |
| 635 |
|
| 636 |
$data = $multi ? $data : [$data]; |
| 637 |
$fields = $this->format(array_keys(array_first($data)), 'fields'); |
| 638 |
$update = implode(', ', array_map(fn($v)=> $v.' = VALUES('.$v.')', explode(', ', $fields))); |
| 639 |
$values = implode(', ', array_map(fn($v)=> '('.$this->format($v, 'values').')', $data)); |
| 640 |
$result = $this->query_by_db("INSERT INTO `{$this->table}` ({$fields}) VALUES {$values} ON DUPLICATE KEY UPDATE {$update}"); |
| 641 |
}else{ |
| 642 |
$result = $this->insert_by_db($this->table, $data); |
| 643 |
} |
| 644 |
|
| 645 |
return $result === false |
| 646 |
? $this->last_error_by_db('insert') |
| 647 |
: ($multi ? $result : wpjam_tap($id ?: $wpdb->insert_id, fn($id)=> $this->cache_delete($id))); |
| 648 |
} |
| 649 |
|
| 650 |
public function insert_multi($data){ |
| 651 |
return $this->insert(array_values(array_filter($data))); // 自增的� |
| 652 |
况可能无法无法删除缓存,请注意 |
| 653 |
} |
| 654 |
|
| 655 |
/* |
| 656 |
update($id, $data); |
| 657 |
update($data, $where); |
| 658 |
update($data); |
| 659 |
*/ |
| 660 |
public function update(...$args){ |
| 661 |
if(!$args){ |
| 662 |
return 0; |
| 663 |
} |
| 664 |
|
| 665 |
[$id, $data, $where] = count($args) == 1 ? [null, ...$args, $this->where()] : (is_array($args[0]) ? [null, ...$args] : [$args[0], $args[1], null]); |
| 666 |
|
| 667 |
if(!$data || (isset($where) && !$where)){ |
| 668 |
return 0; |
| 669 |
} |
| 670 |
|
| 671 |
$this->clear($id, $data, $where); |
| 672 |
|
| 673 |
if(count($args) >= 2){ |
| 674 |
$result = $this->update_by_db($this->table, $data, $where ?? [$this->primary_key => $id]); |
| 675 |
}else{ |
| 676 |
$data = $this->format($data, 'data'); |
| 677 |
$result = $this->query_by_db("UPDATE `{$this->table}` SET {$data} WHERE {$where}"); |
| 678 |
} |
| 679 |
|
| 680 |
return $result === false ? $this->last_error_by_db('update') : $result; |
| 681 |
} |
| 682 |
|
| 683 |
/* |
| 684 |
delete($where); |
| 685 |
delete($id); |
| 686 |
delete(); |
| 687 |
*/ |
| 688 |
public function delete(...$args){ |
| 689 |
[$id, $where] = $args ? (wpjam_is_assoc_array($args[0]) ? [null, $args[0]] : [$args[0], null]) : [null, $this->where()]; |
| 690 |
|
| 691 |
if(isset($where) && !$where){ |
| 692 |
return 0; |
| 693 |
} |
| 694 |
|
| 695 |
$this->clear($id, [], $where); |
| 696 |
|
| 697 |
$key = $this->primary_key; |
| 698 |
$where ??= is_array($id) ? $this->where($key, $id, 'value') : [$key => $id]; |
| 699 |
|
| 700 |
if(is_array($where)){ |
| 701 |
$result = $this->delete_by_db($this->table, $where); |
| 702 |
}else{ |
| 703 |
$result = $this->query_by_db("DELETE FROM `{$this->table}` WHERE {$where}"); |
| 704 |
} |
| 705 |
|
| 706 |
if($result === false){ |
| 707 |
return $this->last_error_by_db('delete'); |
| 708 |
} |
| 709 |
|
| 710 |
if($id){ |
| 711 |
wpjam_map((array)$id, [$this, 'delete_meta_by_id']); |
| 712 |
}else{ |
| 713 |
$this->delete_orphan_meta($this->table, $key); |
| 714 |
} |
| 715 |
|
| 716 |
return $result; |
| 717 |
} |
| 718 |
|
| 719 |
public function delete_by($field, $value){ |
| 720 |
return $this->delete([$field => $value]); |
| 721 |
} |
| 722 |
|
| 723 |
public function delete_multi($ids){ |
| 724 |
return $ids ? $this->delete(array_values(array_filter($ids))) : 0; |
| 725 |
} |
| 726 |
|
| 727 |
protected function clear($ids=[], $data=[], $where=[]){ |
| 728 |
$this->delete_last_changed(); |
| 729 |
|
| 730 |
if(($ck = $this->group_cache_key) || $this->cache){ |
| 731 |
$ids = (array)$ids; |
| 732 |
$pk = $this->primary_key; |
| 733 |
$where = $where ? [is_array($where) ? $this->where_all($where, 'value') : $where] : []; |
| 734 |
$data = wp_is_numeric_array($data) ? $data : [$data]; |
| 735 |
$ids = array_merge($ids, array_column($data, $pk)); |
| 736 |
|
| 737 |
$ids && $this->cache_delete_multiple($ids); |
| 738 |
$ids && $ck && ($where[] = $this->where($pk, $ids, 'value')); |
| 739 |
|
| 740 |
if($where){ |
| 741 |
if($result = $this->get_results_by_db([ |
| 742 |
'fields' => [$pk, ...$ck], |
| 743 |
'where' => 'WHERE '.$this->where_any($where, 'value') |
| 744 |
])){ |
| 745 |
$this->cache_delete_multiple(array_column($result, $pk)); |
| 746 |
|
| 747 |
$ck && ($data = array_merge($data, $result)); |
| 748 |
} |
| 749 |
} |
| 750 |
|
| 751 |
$ck && wpjam_map($ck, fn($k)=> wpjam_map(array_unique(array_column($data, $k)), fn($v)=> $this->delete_last_changed([$k => $v]))); |
| 752 |
} |
| 753 |
} |
| 754 |
|
| 755 |
protected function format($field, ...$args){ |
| 756 |
$types = $this->field_types ?: []; |
| 757 |
|
| 758 |
if(is_array($field)){ |
| 759 |
$type = $args[0]; |
| 760 |
|
| 761 |
if($type == 'fields'){ |
| 762 |
$query = array_fill(0, count($field), '%i'); |
| 763 |
$args = $field; |
| 764 |
}else{ |
| 765 |
$query = $args = []; |
| 766 |
|
| 767 |
foreach($field as $k => $v){ |
| 768 |
$query[] = ($type == 'data' ? "%i = " : '').(is_null($v) ? 'NULL' : ($types[$k] ?? '%s')); |
| 769 |
$args = array_merge($args, ($type == 'data' ? [$k] : []), (is_null($v) ? [] : [$v])); |
| 770 |
} |
| 771 |
} |
| 772 |
|
| 773 |
$query = implode(', ', $query); |
| 774 |
}else{ |
| 775 |
$op = $args[0]; |
| 776 |
$v = $args[1]; |
| 777 |
$f = $types[$field] ?? '%s'; |
| 778 |
|
| 779 |
if(in_array($op, ['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'])){ |
| 780 |
$v = is_array($v) ? $v : array_map('trim', explode(',', $v)); |
| 781 |
$c = count($v); |
| 782 |
|
| 783 |
if(in_array($op, ['IN', 'NOT IN'])){ |
| 784 |
if($c <= 1){ |
| 785 |
$v = array_first($v ?: ['']); |
| 786 |
$op = $op == 'IN' ? '=' : '!='; |
| 787 |
}else{ |
| 788 |
$f = '('.implode(', ', array_fill(0, $c, $f)).')'; |
| 789 |
} |
| 790 |
}else{ |
| 791 |
$f = $f.' AND '.$f; |
| 792 |
} |
| 793 |
}elseif(in_array($op, ['LIKE', 'NOT LIKE'])){ |
| 794 |
$f = '%s'; |
| 795 |
$v = (str_starts_with($v, '%') ? '%' : '').$this->esc_like_by_db(trim($v, '%')).(str_ends_with($v, '%') ? '%' : ''); |
| 796 |
}elseif(in_array($op, ['REGEXP', 'NOT REGEXP', 'RLIKE'])){ |
| 797 |
$f = '%s'; |
| 798 |
}elseif(in_array($op, ['EXISTS', 'NOT EXISTS'])){ |
| 799 |
$query = '%i IS '.($op == 'EXISTS' ? 'NOT NULL' : 'NULL'); |
| 800 |
|
| 801 |
return $this->prepare_by_db($query, [$field]); |
| 802 |
} |
| 803 |
|
| 804 |
$args = [$field, ...(array)$v]; |
| 805 |
$query = '%i '.$op.' '.$f; |
| 806 |
} |
| 807 |
|
| 808 |
return $args ? $this->prepare_by_db($query, $args) : $query; |
| 809 |
} |
| 810 |
|
| 811 |
public function where(...$args){ |
| 812 |
if(!$args){ |
| 813 |
return wpjam_tap(implode(' AND ', $this->search()->query_vars['where']), fn()=> $this->init()); |
| 814 |
} |
| 815 |
|
| 816 |
[$field, $value, $output] = $args+['', null, 'object']; |
| 817 |
|
| 818 |
if(!$field){ |
| 819 |
$value = $value ? '('.$value.')' : ''; |
| 820 |
}elseif(isset($value)){ |
| 821 |
$op = ($pos = strrpos($field, '__')) ? $this->operator(substr($field, $pos + 2)) : ''; |
| 822 |
$field = $op ? substr($field, 0, $pos) : $field; |
| 823 |
$value = $this->format($field, $op ?: (is_array($value) ? 'IN' : '='), $value); |
| 824 |
} |
| 825 |
|
| 826 |
if($output == 'object'){ |
| 827 |
if($value){ |
| 828 |
$this->query_vars['where'][] = $value; |
| 829 |
} |
| 830 |
|
| 831 |
return $this; |
| 832 |
} |
| 833 |
|
| 834 |
return $value; |
| 835 |
} |
| 836 |
|
| 837 |
public function query(...$args){ |
| 838 |
if(!$args){ |
| 839 |
return $this; |
| 840 |
} |
| 841 |
|
| 842 |
$vars = $args[0]; |
| 843 |
$output = $args[1] ?? 'object'; |
| 844 |
|
| 845 |
if(in_array($output, ['cache', 'items', 'ids'])){ |
| 846 |
$vars += ['no_found_rows'=>true, 'suppress_filters'=>true]; |
| 847 |
}else{ |
| 848 |
$vars = apply_filters('wpjam_query_vars', $vars, $this); |
| 849 |
$vars = isset($vars['groupby']) ? ['no_found_rows'=>true]+wpjam_except($vars, ['since', 'cursor']) : $vars; |
| 850 |
$vars += empty($vars['no_found_rows']) ? ['number'=>50] : []; |
| 851 |
} |
| 852 |
|
| 853 |
$pk = $this->primary_key; |
| 854 |
$qv = $vars; |
| 855 |
$orderby = $qv['orderby'] ?? $this->get_arg('orderby'); |
| 856 |
$fields = wpjam_pull($qv, 'fields'); |
| 857 |
$cache = wpjam_pull($qv, 'cache_results', $output != 'ids') || $output == 'cache'; |
| 858 |
$suppress = wpjam_pull($qv, 'suppress_filters'); |
| 859 |
$found_rows = !wpjam_pull($qv, 'no_found_rows'); |
| 860 |
|
| 861 |
if($this->meta_type && ($meta_query = wpjam_pull($qv, ['meta_key', 'meta_value', 'meta_compare', 'meta_compare_key', 'meta_type', 'meta_type_key', 'meta_query']))){ |
| 862 |
$this->meta_query = new WP_Meta_Query(); |
| 863 |
$this->meta_query->parse_query_vars($meta_query); |
| 864 |
} |
| 865 |
|
| 866 |
foreach($qv as $k => $v){ |
| 867 |
if(is_null($v)){ |
| 868 |
continue; |
| 869 |
} |
| 870 |
|
| 871 |
if(array_key_exists($k, $this->query_vars)){ |
| 872 |
$this->query_vars[$k] = $v; |
| 873 |
}elseif($k == 'number'){ |
| 874 |
if($v == -1){ |
| 875 |
$found_rows = false; |
| 876 |
}else{ |
| 877 |
$this->limit($v); |
| 878 |
} |
| 879 |
}elseif(in_array($k, ['s', 'search'])){ |
| 880 |
$this->search($v); |
| 881 |
}elseif(in_array($k, ['cursor', 'since'])){ |
| 882 |
$v > 0 && $this->where($orderby.'__'.($k == 'cursor' ? 'lt' : 'gt'), $v); |
| 883 |
}elseif(in_array($k, ['exclude', 'include'])){ |
| 884 |
$v && is_array($v) && $this->where($pk.'__'.($k == 'include' ? 'in' : 'not_in'), $v); |
| 885 |
}else{ |
| 886 |
$this->where($k, $v); |
| 887 |
} |
| 888 |
} |
| 889 |
|
| 890 |
$found_rows && $this->found_rows(); |
| 891 |
|
| 892 |
$clauses = $this->get_clauses($fields); |
| 893 |
$clauses = $suppress ? $clauses : apply_filters('wpjam_clauses', $clauses, $this); |
| 894 |
$request = $this->get_request($clauses); |
| 895 |
$cache = $cache && !str_contains(strtoupper($orderby), ' RAND(') && in_array($clauses['fields'], ['*', $this->table.'.*']); |
| 896 |
$data = false; |
| 897 |
|
| 898 |
if($cache){ |
| 899 |
$vars = map_deep($vars, 'strval'); |
| 900 |
$lc = $this->get_last_changed($vars); |
| 901 |
$ck = md5(serialize($vars).$this->remove_placeholder_escape_by_db($request)); |
| 902 |
$data = $this->cache_get_salted($ck, $lc); |
| 903 |
|
| 904 |
if($output == 'cache'){ |
| 905 |
return [$data, $ck, $lc]; |
| 906 |
} |
| 907 |
} |
| 908 |
|
| 909 |
if($data === false || !isset($data['items'])){ |
| 910 |
$items = ($cache || $output == 'ids') ? $this->get_ids_by_db($clauses) : $this->get_results_by_db($request); |
| 911 |
$data = ['items'=>$items]+($found_rows ? ['total'=>$this->find_total()] : []); |
| 912 |
|
| 913 |
$cache && $this->cache_set_salted($ck, $data, $lc); |
| 914 |
} |
| 915 |
|
| 916 |
if($output == 'ids'){ |
| 917 |
return $data['items']; |
| 918 |
} |
| 919 |
|
| 920 |
if($cache){ |
| 921 |
$data['items'] = array_values($this->get_by_ids($data['items'])); |
| 922 |
} |
| 923 |
|
| 924 |
if($output == 'items'){ |
| 925 |
return $data['items']; |
| 926 |
} |
| 927 |
|
| 928 |
if($found_rows){ |
| 929 |
$data['next_cursor'] = 0; |
| 930 |
|
| 931 |
if(!empty($qv['number'])){ |
| 932 |
$data['max_num_pages'] = ceil($data['total'] / $qv['number']); |
| 933 |
$data['next_cursor'] = $data['items'] && $data['max_num_pages'] > 1 ? (int)(end($data['items'])[$orderby]) : 0; |
| 934 |
} |
| 935 |
}else{ |
| 936 |
$data['total'] = count($data['items']); |
| 937 |
} |
| 938 |
|
| 939 |
$data['datas'] = &$data['items']; // � |
| 940 |
�容 |
| 941 |
$data['found_rows'] = &$data['total']; // � |
| 942 |
�容 |
| 943 |
$data['request'] = $request; |
| 944 |
|
| 945 |
return $output == 'object' ? (object)$data : $data; |
| 946 |
} |
| 947 |
|
| 948 |
public function query_items(...$args){ |
| 949 |
if(is_array($args[0])){ |
| 950 |
return $this->query(...($args+['', 'array'])); |
| 951 |
} |
| 952 |
|
| 953 |
wpjam_map(['orderby'=>'orderby', 'order'=>'order', 's'=>'search_term'], fn($v, $k)=> is_null($this->$v) && $this->$v($this->get_param($k))); |
| 954 |
|
| 955 |
wpjam_map($this->filterable_fields ?: [], fn($k)=> $this->where($k, $this->get_param($k))); |
| 956 |
|
| 957 |
return $this->limit($args[0])->offset($args[1])->found_rows()->get_results([], true); |
| 958 |
} |
| 959 |
|
| 960 |
public function get_one_by($field, $value, $order='ASC'){ |
| 961 |
return ($items = $this->get_by($field, $value, $order)) ? array_first($items) : []; |
| 962 |
} |
| 963 |
|
| 964 |
public function find($fields=[]){ |
| 965 |
trigger_error('find'); |
| 966 |
return $this->get_results($fields); |
| 967 |
} |
| 968 |
|
| 969 |
public function cache_delete_by($field, $value, $order='ASC'){ // del 2026-06-30 |
| 970 |
trigger_error('cache_delete_by'); |
| 971 |
|
| 972 |
in_array($field, $this->group_cache_key) && wpjam_map((array)$value, fn($v)=> $this->cache_delete($this->query([$field=>$v, 'order'=>$order], 'cache')[1])); |
| 973 |
} |
| 974 |
|
| 975 |
public function get_wheres(){ |
| 976 |
return $this->where(); |
| 977 |
} |
| 978 |
|
| 979 |
public static function create($args, $name=null){ |
| 980 |
return ($name ??= $args['table_name'] ?? '') ? new static($name, $args) : null; |
| 981 |
} |
| 982 |
} |
| 983 |
|
| 984 |
class WPJAM_Items extends WPJAM_Args{ |
| 985 |
public function __construct($args=[]){ |
| 986 |
$args += ['items_type'=> wpjam_pull($args, 'type')]; |
| 987 |
$type = $args['items_type']; |
| 988 |
|
| 989 |
$this->args = $args+($type ? ([ |
| 990 |
'option' => ['primary_key'=>'option_key'], |
| 991 |
'transient' => ['item_type'=>''], |
| 992 |
'meta' => ['parent_key'=>($args['meta_type'] ?? '').'_id'], |
| 993 |
'post_content' => ['parent_key'=>'post_id'], |
| 994 |
'cache' => ['item_type'=>'', 'retry_times'=> 10, 'cache_group'=>'list_cache'], |
| 995 |
][$type] ?? []) : [])+[ |
| 996 |
'item_type' => 'array', |
| 997 |
'primary_key' => 'id' |
| 998 |
]; |
| 999 |
} |
| 1000 |
|
| 1001 |
public function __call($method, $args){ |
| 1002 |
if(str_ends_with($method, '_items')){ |
| 1003 |
if($method == 'get_items'){ |
| 1004 |
$res = $this->cached_items; |
| 1005 |
|
| 1006 |
if(is_array($res)){ |
| 1007 |
return $res; |
| 1008 |
} |
| 1009 |
} |
| 1010 |
|
| 1011 |
if($method == 'update_items'){ |
| 1012 |
if($this->compressed && is_array($args[0])){ |
| 1013 |
$args[0] = wpjam_compress($args[0]); |
| 1014 |
} |
| 1015 |
} |
| 1016 |
|
| 1017 |
if($this->$method){ |
| 1018 |
$res = $this->call($method, ...$args); |
| 1019 |
}else{ |
| 1020 |
$type = $this->items_type; |
| 1021 |
|
| 1022 |
if($type == 'option'){ |
| 1023 |
$keys = ['option_name']; |
| 1024 |
$map = ['get_items'=>'get_option', 'update_items'=>'update_option']; |
| 1025 |
}elseif($type == 'setting'){ |
| 1026 |
$keys = ['option_name', 'setting_name']; |
| 1027 |
$map = ['get_items'=>'wpjam_get_setting', 'update_items'=>'wpjam_update_setting']; |
| 1028 |
}elseif($type == 'transient'){ |
| 1029 |
$keys = ['transient']; |
| 1030 |
$map = ['get_items'=>'get_transient', 'update_items'=>'set_transient']; |
| 1031 |
$args = [...$args, ...($method == 'update_items' ? [DAY_IN_SECONDS] : [])]; |
| 1032 |
}elseif($type == 'meta'){ |
| 1033 |
$keys = ['meta_type', 'object_id', 'meta_key']; |
| 1034 |
$map = ['get_items'=>'get_metadata', 'update_items'=>'update_metadata', 'delete_items'=>'delete_metadata']; |
| 1035 |
$args = [...$args, ...($method == 'get_items' ? [true] : ($method == 'update_items' ? [$this->pre_value] : []))]; |
| 1036 |
}elseif($type == 'post_content'){ |
| 1037 |
if($post_id = $this->post_id){ |
| 1038 |
$object = wpjam_get_post($this->post_id, 'object'); |
| 1039 |
|
| 1040 |
if($method == 'get_items'){ |
| 1041 |
$res = $object->content; |
| 1042 |
$res = (!$this->compressed || is_serialized($res)) ? $object->get_unserialized() : $res; |
| 1043 |
}elseif($method == 'update_items'){ |
| 1044 |
$res = $object->save(['content'=>$args[0] ?: '']); |
| 1045 |
} |
| 1046 |
} |
| 1047 |
}elseif($type == 'cache'){ |
| 1048 |
if($key = $this->cache_key){ |
| 1049 |
$object = WPJAM_Cache::create($this); |
| 1050 |
|
| 1051 |
if($method == 'get_items'){ |
| 1052 |
$res = [$object, 'get_with_cas']($key); |
| 1053 |
|
| 1054 |
[$res, $this->cas_token] = $res === false ? [false, null] : [$res['value'], $res['cas']]; |
| 1055 |
}elseif($method == 'update_items'){ |
| 1056 |
$cas = $this->cas_token; |
| 1057 |
$res = [$object, $cas ? 'cas' : 'add'](...[...($cas ? [$cas] : []), $key, ...$args]); |
| 1058 |
} |
| 1059 |
} |
| 1060 |
} |
| 1061 |
|
| 1062 |
if(!isset($res)){ |
| 1063 |
if(isset($map[$method]) && array_all($keys, fn($k)=> $this->$k)){ |
| 1064 |
$res = ($map[$method])(...[...array_values(wpjam_pick($this, $keys)), ...$args]); |
| 1065 |
|
| 1066 |
if($type == 'meta' && $method == 'get_items'){ |
| 1067 |
$this->pre_value = $res; |
| 1068 |
} |
| 1069 |
}else{ |
| 1070 |
$res = $method == 'delete_items' ? $this->update_items([]) : true; |
| 1071 |
} |
| 1072 |
} |
| 1073 |
} |
| 1074 |
|
| 1075 |
if($method == 'get_items'){ |
| 1076 |
$res = $this->compressed && $res && !is_array($res) ? wpjam_uncompress($res) : ($res ?: []); |
| 1077 |
$res = $this->cached_items = $this->filter_items ? $this->filter_items($res) : $res; |
| 1078 |
}else{ |
| 1079 |
unset($this->cached_items); |
| 1080 |
} |
| 1081 |
|
| 1082 |
return $res; |
| 1083 |
}elseif(str_contains($method, '_setting')){ |
| 1084 |
if($this->option_name){ |
| 1085 |
$i = str_starts_with($method, 'update_') ? 2 : 1; |
| 1086 |
$args = (isset($args[$i]) && !is_numeric($args[$i])) ? array_slice($args, 0, $i) : $args; |
| 1087 |
|
| 1088 |
return ('wpjam_'.$method)($this->option_name, ...$args); |
| 1089 |
} |
| 1090 |
}elseif(in_array($method, ['increment', 'decrement'])){ |
| 1091 |
if($this->item_type == 'array'){ |
| 1092 |
[$k, $v] = array_pad($args, 2, 1); |
| 1093 |
|
| 1094 |
return wpjam_tap( |
| 1095 |
(int)$this->get($k)+($method == 'increment' ? $v : (0-$v)), |
| 1096 |
fn($v)=> $this->process(fn($items)=> array_merge($items, [$k=>$v])) |
| 1097 |
); |
| 1098 |
} |
| 1099 |
}elseif(in_array($method, ['insert', 'add', 'update', 'replace', 'set', 'delete', 'remove'])){ |
| 1100 |
return wpjam_retry($this->retry_times ?: 1, fn()=> $this->retry($method, $args)); |
| 1101 |
} |
| 1102 |
} |
| 1103 |
|
| 1104 |
protected function retry($method, $args){ |
| 1105 |
$items = $this->get_items(); |
| 1106 |
$type = $this->item_type; |
| 1107 |
$key = $this->primary_key; |
| 1108 |
$title = $this->primary_title ?: 'ID'; |
| 1109 |
$add = $method == 'insert' || ($method == 'add' && count($args) <= 1); |
| 1110 |
$id = $add ? null : array_shift($args); |
| 1111 |
|
| 1112 |
if(!$add){ |
| 1113 |
$id || wpjam_throw('empty_'.($key ?: 'id'), ($key ? $title : 'ID').'不能为空'); |
| 1114 |
|
| 1115 |
if($method == 'set'){ |
| 1116 |
$method = isset($items[$id]) ? 'set' : 'add'; |
| 1117 |
}elseif(($exist = isset($items[$id])) === ($method == 'add')){ |
| 1118 |
wpjam_throw(($exist ? 'duplicate_' : 'invalid_').($key ?: 'id'), ($key ? $title : 'ID').'-「'.$id.'」'.($exist ? '已存在' : '不存在')); |
| 1119 |
} |
| 1120 |
} |
| 1121 |
|
| 1122 |
if(in_array($method, ['delete', 'remove'])){ |
| 1123 |
unset($items[$id]); |
| 1124 |
}else{ |
| 1125 |
if(!$args || ($type == 'array' && !is_array($args[0]))){ |
| 1126 |
wpjam_throw(...($args ? ['invalid_item', '不是数组'] : ['empty_item', '不能为空'])); |
| 1127 |
} |
| 1128 |
|
| 1129 |
$item = $args[0]; |
| 1130 |
|
| 1131 |
if($type == 'array'){ |
| 1132 |
if($add){ |
| 1133 |
if(in_array($key, ['option_key', 'id'])){ |
| 1134 |
$id = $items ? max(array_map(fn($id)=> (int)str_replace('option_key_', '', $id), array_keys($items)))+1 : 1; |
| 1135 |
$id = $key == 'option_key' ? 'option_key_'.$id : $id; |
| 1136 |
}else{ |
| 1137 |
$id = $item[$key] ?? ''; |
| 1138 |
|
| 1139 |
(!$id || isset($items[$id])) && wpjam_throw(($id ? 'duplicate_' : 'empty_').$key, $title.($id ? '「'.$id.'」已被使用' : '不能为空')); |
| 1140 |
} |
| 1141 |
} |
| 1142 |
|
| 1143 |
if(($uk = $this->unique_key) && ($add || isset($item[$uk]))){ |
| 1144 |
$uv = $item[$uk] ?? ''; |
| 1145 |
$blank = !$uv && !is_numeric($uv); |
| 1146 |
|
| 1147 |
if($blank || array_find($items, fn($v, $k)=> ($add || $id != $k) && $v[$uk] == $uv)){ |
| 1148 |
wpjam_throw(($blank ? 'empty_' : 'duplicate_').$uk, ($this->unique_title ?: $uk).($blank ? '不能为空' : '「'.$uv.'」已被使用')); |
| 1149 |
} |
| 1150 |
} |
| 1151 |
|
| 1152 |
$item = [$key=>$id]+$item; |
| 1153 |
}else{ |
| 1154 |
$add && in_array($item, $items) && wpjam_throw('duplicate_item', '不能重复'); |
| 1155 |
} |
| 1156 |
|
| 1157 |
$item = $this->prepare_item ? $this->call('prepare_item', $item, $id, $method, $items) : $item; |
| 1158 |
} |
| 1159 |
|
| 1160 |
if(in_array($method, ['add', 'insert'])){ |
| 1161 |
($max = $this->max_items) && count($items) >= $max && wpjam_throw('over_max_items', '最大� |
| 1162 |
�许数量:'.$max); |
| 1163 |
$last = $this->last ?? ($method == 'add'); |
| 1164 |
|
| 1165 |
if($type == 'array' || $id){ |
| 1166 |
$item = $type == 'array' ? array_filter($item, fn($v)=> !is_null($v)) : $item; |
| 1167 |
$items = $last ? array_replace($items, [$id=>$item]) : [$id=>$item]+$items; |
| 1168 |
}else{ |
| 1169 |
$last ? array_push($items, $item) : array_unshift($items, $item); |
| 1170 |
} |
| 1171 |
}elseif(in_array($method, ['set', 'update', 'replace'])){ |
| 1172 |
$items[$id] = $method == 'update' && $type == 'array' ? wp_parse_args($item, $items[$id]) : $item; |
| 1173 |
} |
| 1174 |
|
| 1175 |
if($type == 'array' && $items && in_array($key, ['option_key','id'])){ |
| 1176 |
$except = array_filter([$key, $this->parent_key]); |
| 1177 |
$items = wpjam_map($items, fn($item)=> wpjam_except($item, $except)); |
| 1178 |
} |
| 1179 |
|
| 1180 |
$result = $this->update_items($items); |
| 1181 |
|
| 1182 |
return $result && $method == 'insert' && $type == 'array' ? ['id'=>$id, 'last'=>(bool)$last] : $result; |
| 1183 |
} |
| 1184 |
|
| 1185 |
public function process($cb){ |
| 1186 |
return is_closure($cb) ? wpjam_retry($this->retry_times ?: 1, fn()=> $this->update_items(wpjam_call($cb, $this->get_items()))) : null; |
| 1187 |
} |
| 1188 |
|
| 1189 |
public function query_items($args){ |
| 1190 |
$s = trim(wpjam_pull($args, 's') ?: ''); |
| 1191 |
$number = wpjam_pull($args, 'number') ?: 50; |
| 1192 |
$offset = wpjam_pull($args, 'offset') ?: 0; |
| 1193 |
$items = $this->parse_items(); |
| 1194 |
$items = array_values(($args || $s) ? array_filter($items, fn($item)=> (!$args || wpjam_matches($item, $args)) && (!$s || array_any($item, fn($v)=> str_contains($v, $s))) ) : $items); |
| 1195 |
|
| 1196 |
return ['total'=>count($items), 'items'=>array_slice($items, $offset, $number)]; |
| 1197 |
} |
| 1198 |
|
| 1199 |
public function parse_items($items=null){ |
| 1200 |
$items ??= $this->get_items(); |
| 1201 |
|
| 1202 |
if($this->item_type == 'array'){ |
| 1203 |
$items = $items && is_array($items) ? wpjam_map($items, fn($v, $k)=> [$this->primary_key => $k]+(is_array($v) ? $v : [])) : []; |
| 1204 |
$items && wpjam_lazyload($this->lazyload_key, $items); |
| 1205 |
} |
| 1206 |
|
| 1207 |
return $items; |
| 1208 |
} |
| 1209 |
|
| 1210 |
public function get_results(){ |
| 1211 |
return $this->parse_items(); |
| 1212 |
} |
| 1213 |
|
| 1214 |
public function reset(){ |
| 1215 |
return $this->delete_items(); |
| 1216 |
} |
| 1217 |
|
| 1218 |
public function empty(){ |
| 1219 |
return wpjam_tap($this->get_items(), fn($v)=> $v && $this->delete_items()); |
| 1220 |
} |
| 1221 |
|
| 1222 |
public function move($id, $data){ |
| 1223 |
return $this->process(fn($items)=> wpjam_pick($items, wpjam_move(array_keys($items), $id, $data))); |
| 1224 |
} |
| 1225 |
|
| 1226 |
public function get($id){ |
| 1227 |
return ($items = wpjam_pick($this->get_items(), [$id])) ? $this->parse_items($items)[$id] : null; |
| 1228 |
} |
| 1229 |
|
| 1230 |
public static function create($args, $name=null){ |
| 1231 |
if(!empty($args['option_name'])){ |
| 1232 |
$args += array_filter(['setting_name'=>wpjam_pull($args, 'items_field')]); |
| 1233 |
$args += ['type'=>(!empty($args['setting_name']) ? 'setting' : 'option')]; |
| 1234 |
}elseif(!empty($args['items_model'])){ // 不建议 |
| 1235 |
$args += wpjam_fill(['get_items', 'update_items'], fn($k)=> [$args['items_model'], $k]); |
| 1236 |
}elseif(wpjam_pull($args, 'type') == 'option_items'){ // 不建议 |
| 1237 |
$args += ['type'=>'option', 'option_name'=>$name]; |
| 1238 |
}else{ |
| 1239 |
$args = (!empty($args['items_type']) || (!empty($args['get_items']) && !empty($args['update_items']))) ? $args : []; |
| 1240 |
} |
| 1241 |
|
| 1242 |
return $args ? new static($args) : null; |
| 1243 |
} |
| 1244 |
} |
| 1245 |
|
| 1246 |
class WPJAM_Notice extends WPJAM_Items{ |
| 1247 |
public static function render($type){ |
| 1248 |
foreach((self::get_instance($type))->get_items() as $key => $item){ |
| 1249 |
$data = ['notice_key'=>$key, 'notice_type'=>$type]; |
| 1250 |
$item += ['class'=>'is-dismissible', 'title'=>'', 'modal'=>0]; |
| 1251 |
$notice = trim($item['notice'] ?? ''); |
| 1252 |
$notice .= !empty($item['admin_url']) ? (($item['modal'] ? "\n\n" : ' ').'<a href="'.add_query_arg($data, home_url($item['admin_url'])).'">点击查看<span class="dashicons dashicons-arrow-right-alt"></span></a>') : ''; |
| 1253 |
|
| 1254 |
echo wpjam_tag('div', [ |
| 1255 |
'class' => $item['modal'] ? 'hidden notice-modal' : 'notice notice-'.$item['type'].' '.$item['class'], |
| 1256 |
'data' => $item['modal'] ? ['title'=>$item['title'] ?: '消息'] : [], |
| 1257 |
], wpautop($notice).wpjam_get_page_button('delete_notice', ['data'=>$data]))."\n"; |
| 1258 |
} |
| 1259 |
} |
| 1260 |
|
| 1261 |
public static function add($item, $type='admin', $id=0){ |
| 1262 |
if(!$id || ($type == 'admin' ? (!is_multisite() || get_site($id)) : get_userdata($id))){ |
| 1263 |
$item = is_array($item) ? $item : ['notice'=>$item]; |
| 1264 |
|
| 1265 |
return (self::get_instance($type, $id))->insert($item+['type'=>'error', 'time'=>time(), 'key'=>md5(serialize($item))]); |
| 1266 |
} |
| 1267 |
} |
| 1268 |
|
| 1269 |
public static function callback($data=[], $wp_die=true){ |
| 1270 |
$type = $data['notice_type'] ?? ''; |
| 1271 |
$key = $data['notice_key'] ?? ''; |
| 1272 |
|
| 1273 |
return ($type == 'user' || ($type == 'admin' && current_user_can('manage_options'))) |
| 1274 |
? ($key ? (self::get_instance($type))->delete($key) : true) |
| 1275 |
: ($wp_die ? wp_die('无效的类型') : true); |
| 1276 |
} |
| 1277 |
|
| 1278 |
public static function get_instance($type='admin', $id=0){ |
| 1279 |
$name = 'wpjam_notices'; |
| 1280 |
$id = (int)$id ?: ($type == 'user' ? get_current_user_id() : get_current_blog_id()); |
| 1281 |
|
| 1282 |
return wpjam_get_handler('notice:'.$type.':'.$id, static::create(($type == 'user' ? [ |
| 1283 |
'items_type' => 'meta', |
| 1284 |
'meta_type' => 'user', |
| 1285 |
'meta_key' => $name, |
| 1286 |
'object_id' => $id |
| 1287 |
] : [ |
| 1288 |
'get_items' => fn()=> wpjam_get_option($name, $id), |
| 1289 |
'update_items' => fn($items)=> wpjam_update_option($name, $items, $id), |
| 1290 |
])+[ |
| 1291 |
'primary_key' => 'key', |
| 1292 |
'filter_items' => fn($items)=> array_filter(($items ?: []), fn($v)=> $v['time']>(time()-MONTH_IN_SECONDS*3) && trim($v['notice'])) |
| 1293 |
])); |
| 1294 |
} |
| 1295 |
|
| 1296 |
public static function on_admin_init(){ |
| 1297 |
static::callback($_GET, false); |
| 1298 |
|
| 1299 |
add_action('all_admin_notices', fn()=> [current_user_can('manage_options') && static::render('admin'), static::render('user')]); |
| 1300 |
|
| 1301 |
wpjam_register_page_action('delete_notice', [ |
| 1302 |
'button_text' => '删除', |
| 1303 |
'tag' => 'span', |
| 1304 |
'class' => 'hidden delete-notice', |
| 1305 |
'validate' => true, |
| 1306 |
'direct' => true, |
| 1307 |
'callback' => [static::class, 'callback'] |
| 1308 |
]); |
| 1309 |
} |
| 1310 |
} |
| 1311 |
|
| 1312 |
class WPJAM_Lazyloader extends WPJAM_Args{ |
| 1313 |
public function __call($method, $args){ |
| 1314 |
$name = $method.'['.array_shift($args).']'; |
| 1315 |
|
| 1316 |
return $args ? $this->update_arg($name, ...$args) : $this->get_arg($name); |
| 1317 |
} |
| 1318 |
|
| 1319 |
public function queue($name, $ids){ |
| 1320 |
if(!$name || !($ids = array_filter($ids))){ |
| 1321 |
return; |
| 1322 |
} |
| 1323 |
|
| 1324 |
if(is_array($name)){ |
| 1325 |
return array_walk($name, fn($n, $k)=> $this->queue($n, is_numeric($k) ? $ids : array_column($ids, $k))); |
| 1326 |
} |
| 1327 |
|
| 1328 |
$ids = array_unique($ids); |
| 1329 |
|
| 1330 |
if($name == 'post'){ |
| 1331 |
_prime_post_caches($ids, false, false); |
| 1332 |
|
| 1333 |
wpjam_lazyload('post_meta', $ids); |
| 1334 |
}elseif(in_array($name, ['blog', 'site', 'term', 'comment'])){ |
| 1335 |
('_prime_'.($name == 'blog' ? 'site' : $name).'_caches')($ids); |
| 1336 |
}elseif(in_array($name, ['term_meta', 'comment_meta', 'blog_meta'])){ |
| 1337 |
wp_metadata_lazyloader()->queue_objects(wpjam_suffix($name, '-', '_meta'), $ids); |
| 1338 |
}else{ |
| 1339 |
$pending = $this->pending($name); |
| 1340 |
|
| 1341 |
if(!$pending){ |
| 1342 |
$loader = $this->loader($name) ?: (str_ends_with($name, '_meta') ? [ |
| 1343 |
'filter' => 'get_'.$name.'data', |
| 1344 |
'callback' => fn($v)=> update_meta_cache(wpjam_suffix($name, '-', '_meta'), $v) |
| 1345 |
] : []); |
| 1346 |
|
| 1347 |
$loader && wpjam_once($loader['filter'], 'tap', fn()=> $this->load($name, $loader['callback'])); |
| 1348 |
} |
| 1349 |
|
| 1350 |
$this->pending($name, array_merge($pending ?: [], $ids)); |
| 1351 |
} |
| 1352 |
} |
| 1353 |
|
| 1354 |
public function load($name, $cb){ |
| 1355 |
if($name && ($pending = $this->pending($name))){ |
| 1356 |
wpjam_call($cb, array_unique($pending)); |
| 1357 |
|
| 1358 |
$this->pending($name, []); |
| 1359 |
} |
| 1360 |
} |
| 1361 |
|
| 1362 |
public static function get_instance(){ |
| 1363 |
static $object; |
| 1364 |
return $object ??= new self(); |
| 1365 |
} |
| 1366 |
} |
| 1367 |
|
| 1368 |
class WPJAM_Cache extends WPJAM_Args{ |
| 1369 |
public function __call($method, $args){ |
| 1370 |
$method = wpjam_prefix($method, '-', 'cache_'); |
| 1371 |
$multi = str_contains($method, '_multiple'); |
| 1372 |
$gnd = array_any(['get', 'delete'], fn($k)=> str_contains($method, $k)); |
| 1373 |
|
| 1374 |
$ik = $method == 'cas' ? 1 : 0; |
| 1375 |
$ig = $ik+(($gnd || $multi) ? 1 : 2); |
| 1376 |
$ie = $ig+(str_contains($method, '_salted') ? 1 : 0); |
| 1377 |
|
| 1378 |
if(count($args) >= $ig){ |
| 1379 |
$cb = 'wp_cache_'.$method; |
| 1380 |
$key = $args[$ik]; |
| 1381 |
|
| 1382 |
if($prefix = $this->prefix){ |
| 1383 |
$args[$ik] = $multi ? ($gnd ? 'wpjam_map' : 'wpjam_array')($key, fn($k)=> $prefix.':'.$k) : $prefix.':'.$key; |
| 1384 |
} |
| 1385 |
|
| 1386 |
if(!$gnd){ |
| 1387 |
$args[$ie] ??= $this->time ?: DAY_IN_SECONDS; |
| 1388 |
} |
| 1389 |
|
| 1390 |
if($method == 'get_with_cas'){ |
| 1391 |
$value = $cb($args[$ik], $this->group, $cas); |
| 1392 |
|
| 1393 |
return $value === false ? false : ['value'=>$value, 'cas'=>$cas]; |
| 1394 |
} |
| 1395 |
|
| 1396 |
$result = $cb(...wpjam_add_at($args, $ig, $this->group)); |
| 1397 |
|
| 1398 |
if($result && $method == 'get_multiple'){ |
| 1399 |
return wpjam_array($key, fn($i, $k) => (($v = $result[$args[0][$i]]) !== false) ? [$k, $v] : null); |
| 1400 |
} |
| 1401 |
|
| 1402 |
return $result; |
| 1403 |
} |
| 1404 |
} |
| 1405 |
|
| 1406 |
public function is_over($key, $max, $time){ |
| 1407 |
$times = $this->get($key) ?: 0; |
| 1408 |
|
| 1409 |
return $times > $max || ($this->set($key, $times+1, ($max == $times && $time > 60) ? $time : 60) && false); |
| 1410 |
} |
| 1411 |
|
| 1412 |
public static function create($group, $args=[]){ |
| 1413 |
if(is_object($group)){ |
| 1414 |
[$object, $group, $global] = [$group, ...((array)$group->cache_group+['', ''])]; |
| 1415 |
|
| 1416 |
$args = compact('group', 'global')+['prefix'=>$object->cache_prefix, 'time'=>$object->cache_time]; |
| 1417 |
}else{ |
| 1418 |
$args = is_array($group) ? $group : ['group'=>$group]+$args; |
| 1419 |
} |
| 1420 |
|
| 1421 |
if(!empty($args['group'])){ |
| 1422 |
if(wpjam_pull($args, 'global')){ |
| 1423 |
wp_cache_add_global_groups($args['group']); |
| 1424 |
} |
| 1425 |
|
| 1426 |
if(empty($args['time']) && !empty($args['cache_time'])){ |
| 1427 |
$args['time'] = $args['cache_time']; |
| 1428 |
} |
| 1429 |
|
| 1430 |
$name = wpjam_join(':', $args['group'], $args['prefix'] ?? ''); |
| 1431 |
|
| 1432 |
return wpjam_get_registered(static::class, $name) ?: wpjam_register(static::class, $name, $args); |
| 1433 |
} |
| 1434 |
} |
| 1435 |
|
| 1436 |
public static function remember($key, $group, $cb=null, $args=[]){ |
| 1437 |
$fix = is_bool($group) ? ($group ? 'site_' : '').'transient' : ''; |
| 1438 |
|
| 1439 |
if($cb === false){ |
| 1440 |
return $fix ? ('delete_'.$fix)($key) : wp_cache_delete($key, $group); |
| 1441 |
} |
| 1442 |
|
| 1443 |
$args = is_numeric($args) ? ['expire'=>$args] : $args; |
| 1444 |
$expire = ($args['expire'] ?? '') ?: 86400; |
| 1445 |
$force = $args['force'] ?? false; |
| 1446 |
$value = $fix ? ('get_'.$fix)($key) : wp_cache_get($key, $group, ($force === 'get' || $force === true)); |
| 1447 |
|
| 1448 |
return ($cb && ($value === false || $force === 'set' || $force === true)) ? wpjam_tap( |
| 1449 |
$cb($value, $key, ...($fix ? [] : [$group ?: 'default'])), |
| 1450 |
fn($value)=> $value !== false && ($fix ? ('set_'.$fix)($key, $value, $expire) : wp_cache_set($key, $value, $group, $expire)) |
| 1451 |
) : $value; |
| 1452 |
} |
| 1453 |
|
| 1454 |
public static function table($name, $key, ...$args){ |
| 1455 |
$clean = $args && $args[0]; |
| 1456 |
$table = in_array($name, ['post', 'term', 'user']) ? $GLOBALS['wpdb']->{$name.'s'} : $name; |
| 1457 |
$status = static::remember($table.':status', false, $clean ? false : fn()=> $GLOBALS['wpdb']->get_row("SHOW TABLE STATUS LIKE '{$table}'")); |
| 1458 |
|
| 1459 |
return !$clean && $key == 'max_id' ? ($status ? $status->Auto_increment : null) : $status; |
| 1460 |
} |
| 1461 |
|
| 1462 |
public static function deleted_ids($name, ...$args){ |
| 1463 |
$key = 'deleted_ids:'.$name; |
| 1464 |
$items = get_transient($key) ?: []; |
| 1465 |
|
| 1466 |
if($args && $args[0]){ |
| 1467 |
if(is_string($args[0]) && try_suffix($args[0], '-', '-')){ |
| 1468 |
static::table($name, 'max_id', true); |
| 1469 |
|
| 1470 |
$id = (int)$args[0]; |
| 1471 |
$update = in_array($id, $items) ? array_diff($items, [$id]) : null; |
| 1472 |
}else{ |
| 1473 |
$ids = array_diff((array)$args[0], $items); |
| 1474 |
$max = static::table($name, 'max_id'); |
| 1475 |
$ids = $ids && $max ? array_filter($ids, fn($id)=> $id < $max) : $ids; |
| 1476 |
$update = $ids ? array_merge($items, $ids) : null; |
| 1477 |
} |
| 1478 |
|
| 1479 |
if(isset($update)){ |
| 1480 |
$items = array_values($update); |
| 1481 |
|
| 1482 |
set_transient($key, $items, 30); |
| 1483 |
} |
| 1484 |
} |
| 1485 |
|
| 1486 |
return $items; |
| 1487 |
} |
| 1488 |
|
| 1489 |
public static function verification($group, $key, ...$args){ |
| 1490 |
$group = (is_array($group) ? $group : ['group'=>$group])+['max_attempts'=>5, 'interval'=>1, 'expire'=>30, 'global'=>true]; |
| 1491 |
$cache = self::create(['group'=>'verification_code', 'prefix'=>$group['group'] ?: 'default']+$group); |
| 1492 |
|
| 1493 |
if($max = $group['max_attempts']){ |
| 1494 |
$times = (int)$cache->get($key.':failed_times'); |
| 1495 |
($times > $max) && wpjam_throw('too_many_attempts', sprintf(__('Failed attempts exceeded, Please try again in %d minutes.', 'wpjam'), $group['expire']/2)); |
| 1496 |
} |
| 1497 |
|
| 1498 |
if($args){ |
| 1499 |
if(!$args[0] || (int)$args[0] !== (int)$cache->get($key.':code')){ |
| 1500 |
$max && $cache->set($key.':failed_times', $times+1, $group['expire']*30); |
| 1501 |
|
| 1502 |
wpjam_throw('invalid_code'); |
| 1503 |
} |
| 1504 |
|
| 1505 |
return true; |
| 1506 |
} |
| 1507 |
|
| 1508 |
if($group['interval']){ |
| 1509 |
$cache->get($key.':time') && wpjam_throw('error', sprintf(__('A verification code was sent %d minutes ago.', 'wpjam'), $group['interval'])); |
| 1510 |
|
| 1511 |
$cache->set($key.':time', time(), $group['interval']*60); |
| 1512 |
} |
| 1513 |
|
| 1514 |
$code = rand(100000, 999999); |
| 1515 |
|
| 1516 |
$cache->set($key.':code', $code, $group['expire']*60); |
| 1517 |
|
| 1518 |
return $code; |
| 1519 |
} |
| 1520 |
} |