schedule();}}); add_action('admin_init',[$this,'schedule']); add_action('digibl_query_index',[$this,'reindex']); } public static function catalog(string $type): array { $out=[]; foreach (array_merge(get_registered_meta_keys('post'),get_registered_meta_keys('post',$type)) as $key=>$field) { $rest=$field['show_in_rest']??false;$contexts=is_array($rest)?($rest['schema']['context']??['view']):['view']; if (!$rest||!in_array('view',$contexts,true)||empty($field['single'])||!in_array($field['type']??'', ['string','number','integer','boolean'],true)||!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]{0,79}$/D',$key)||is_protected_meta($key,'post')) { continue; } $out[$key]=['key'=>$key,'label'=>$key,'type'=>$field['type'],'storage'=>$key]; } foreach (NativeFields::definitions($type) as $field) { if (!$field['public']||in_array($field['type'],['image','repeater'],true)) { continue; } $out[$field['key']]=['key'=>$field['key'],'label'=>$field['label'],'type'=>$field['type']==='number'?'number':'string','storage'=>self::PREFIX.$field['key']]; } return $out; } public static function clean($raw): array { if ($raw===null || $raw===[]) { return []; } if (!is_array($raw)||!array_is_list($raw)||count($raw)>8) { throw new \InvalidArgumentException('Use at most eight field filters.'); } $out=[]; foreach ($raw as $row) { if (!is_array($row)||!is_string($row['key']??null)||!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]{0,79}$/D',$row['key'])||!in_array($row['operator']??'', ['eq','ne','contains','gt','gte','lt','lte','empty','not-empty'],true)||!is_scalar($row['value']??'')) { throw new \InvalidArgumentException('Invalid public field filter.'); } $out[]=['key'=>$row['key'],'operator'=>$row['operator'],'value'=>Document::text((string)($row['value']??''),300)]; } return $out; } public static function apply(array &$args,array $props): void { $catalog=self::catalog($args['post_type']);$filters=$props['queryFilters']??[]; $meta=['relation'=>($props['filterRelation']??'AND')==='OR'?'OR':'AND']; foreach ($filters as $row) { $field=$catalog[$row['key']]??null; if (!$field) { $args['post__in']=[0];return; } $clause=['key'=>$field['storage'],'value'=>$row['value'],'type'=>in_array($field['type'],['number','integer'],true)?'DECIMAL(20,6)':'CHAR']; $op=$row['operator']; if (in_array($op,['gt','gte','lt','lte'],true)&&!is_numeric($row['value'])) { $args['post__in']=[0];return; } $clause['compare']=['eq'=>'=','ne'=>'!=','contains'=>'LIKE','gt'=>'>','gte'=>'>=','lt'=>'<','lte'=>'<=','empty'=>'=','not-empty'=>'!='][$op]; if (in_array($op,['empty','not-empty'],true)) { $clause['value']='';$clause['type']='CHAR'; } if ($op==='empty') { $clause=['relation'=>'OR',$clause,['key'=>$field['storage'],'compare'=>'NOT EXISTS']]; } $meta[]=$clause; } if (count($meta)>1) { $args['meta_query']=$meta; } if (($props['orderby']??'')==='field') { $field=$catalog[$props['sortField']??'']??null; if (!$field) { $args['post__in']=[0];return; } $args['meta_key']=$field['storage'];$args['orderby']=in_array($field['type'],['number','integer'],true)?'meta_value_num':'meta_value'; } } public function changed($meta_id,$id,$key,$value): void { if($key===NativeFields::META){self::index((int)$id);} } public static function index(int $id): void { $known=[]; foreach (self::catalog((string)get_post_type($id)) as $field) { if (!str_starts_with($field['storage'],self::PREFIX)) { continue; } $known[]=$field['storage'];$value=NativeFields::public_value($id,$field['key']); update_post_meta($id,$field['storage'],is_bool($value)?($value?'1':'0'):$value); } foreach (array_keys(get_post_meta($id)) as $key) { if(str_starts_with($key,self::PREFIX)&&!in_array($key,$known,true)){delete_post_meta($id,$key);} } } public function schedule(): void { if(get_option('digibl_query_index_version','')!=='1'&&!wp_next_scheduled('digibl_query_index')){wp_schedule_single_event(time()+5,'digibl_query_index');} } public function reindex(int $after=0): array { global $wpdb; $ids=$wpdb->get_col($wpdb->prepare("SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE post_id>%d AND (meta_key=%s OR meta_key LIKE %s) ORDER BY post_id ASC LIMIT 100",$after,NativeFields::META,$wpdb->esc_like(self::PREFIX).'%')); foreach($ids as $id){self::index((int)$id);} $next=count($ids)===100?(int)end($ids):0; if($next){wp_schedule_single_event(time()+5,'digibl_query_index',[$next]);}else{update_option('digibl_query_index_version','1',false);} return ['count'=>count($ids),'next'=>$next]; } }