'POST','permission_callback'=>'__return_true','callback'=>[$this,'request']]);} public static function visible(array $style,int $context=0): bool { return DisplayRules::visible($style,$context); } public static function definition(int $id,string $node,array $types,array $chain=[]) { if(!$id||!PostTypes::public_source($id,$chain)){return null;} $doc=Document::clean(json_decode(get_post_meta($id,'_digibl_published',true),true));if(is_wp_error($doc)){return null;} $scan=function($nodes)use(&$scan,$node,$types,$id){foreach($nodes as $n){if(!self::visible($n['style'],$id)){continue;}if($n['id']===$node&&in_array($n['type'],$types,true)){return $n;}if(in_array($n['type'],['loop','reusable','component','data-repeater','mega-menu'],true)){continue;}$found=$scan($n['children']);if($found){return $found;}}return null;};return $scan($doc['nodes']); } public static function ids($value): array {return array_slice(array_values(array_unique(array_filter(array_map('absint',explode(',',(string)$value))))),0,100);} public static function taxonomy(array $p): string { $name=sanitize_key($p['taxonomy']??'category');$type=sanitize_key($p['postType']??'post');$tax=get_taxonomy($name); return $tax&&$tax->public&&in_array($type,$tax->object_type,true)?$name:''; } public static function query(array $p,int $context=0): \WP_Query { $type=sanitize_key($p['postType']??'post');$object=get_post_type_object($type); $count=max(1,min(24,(int)$p['count']));$page=max(1,min(250,(int)($p['_query_page']??1))); $args=['post_type'=>$type,'post_status'=>'publish','has_password'=>false,'posts_per_page'=>$count,'ignore_sticky_posts'=>true,'no_found_rows'=>false,'order'=>($p['order']??'newest')==='oldest'?'ASC':'DESC','orderby'=>Document::choice($p['orderby']??'date',['date','title','modified','menu_order','comment_count'],'date'),'offset'=>min(10000,(int)($p['offset']??0))+($page-1)*$count]; // Internal/private post types cannot be queried even through imported documents. if(!$object||!$object->public||!is_post_type_viewable($object)){$args['post__in']=[0];} elseif($include=self::ids($p['include']??'')){$args['post__in']=$include;} $exclude=self::ids($p['exclude']??'');if($context){$exclude[]=$context;}$args['post__not_in']=$exclude; // WordPress ignores post__not_in when post__in is non-empty. Apply the // intersection here and keep an empty result as [0], never unrestricted. if(isset($args['post__in'])){$args['post__in']=array_values(array_diff($args['post__in'],$exclude))?:[0];} if($authors=self::ids($p['authors']??'')){$args['author__in']=$authors;} if(!empty($p['category'])&&$type==='post'){$args['cat']=(int)$p['category'];} $taxonomy=self::taxonomy($p);$terms=self::ids($p['terms']??'');$term=absint($p['_query_term']??0); if($taxonomy){$clauses=[];if($terms){$clauses[]=['taxonomy'=>$taxonomy,'field'=>'term_id','terms'=>$terms];}if($term){$clauses[]=['taxonomy'=>$taxonomy,'field'=>'term_id','terms'=>[$term]];}if($clauses){$args['tax_query']=array_merge(['relation'=>'AND'],$clauses);}} QueryFields::apply($args,$p); return new \WP_Query($args); } public static function controls(array $p): string { $taxonomy=self::taxonomy($p);if(($p['filters']??'no')!=='yes'||!$taxonomy){return '';} $terms=get_terms(['taxonomy'=>$taxonomy,'hide_empty'=>true,'number'=>100]);if(is_wp_error($terms)){return '';} $h='
'; foreach($terms as $term){$h.='';} return $h.'
'; } public static function has_more(array $p,\WP_Query $query): bool {return (int)($p['offset']??0)+max(1,(int)($p['_query_page']??1))*(int)$p['count']<(int)$query->found_posts;} public static function attributes(array $p,int $source,string $node,bool $preview,bool $more,array $chain=[]): string { if(($p['source']??'custom')!=='custom'||(($p['pagination']??'none')==='none'&&($p['filters']??'no')!=='yes')){return '';} return ' data-source-chain="'.esc_attr(wp_json_encode($chain)).'" data-dbs-query data-post="'.$source.'" data-node="'.esc_attr($node).'" data-endpoint="'.esc_url(rest_url('digiter-builder/v1/content/query')).'" data-nonce="'.esc_attr(is_user_logged_in()?wp_create_nonce('wp_rest'):'').'" data-preview="'.($preview?'1':'0').'" data-query-more="'.($more?'1':'0').'" data-query-mode="'.esc_attr($p['pagination']??'none').'"'; } public static function tail(array $p,bool $more): string { return '

'.(($p['pagination']??'none')!=='none'?'':''); } public function request(\WP_REST_Request $r) { if(strlen($r->get_body())>4096){return new \WP_Error('digibl_query_size','Request is too large.',['status'=>413]);} $node=$r->get_param('node');$id=absint($r->get_param('post'));$page=filter_var($r->get_param('page'),FILTER_VALIDATE_INT);$term=absint($r->get_param('term')); if(!is_string($node)||!preg_match('/^[a-zA-Z0-9_-]{1,80}$/D',$node)||$page<1||$page>250){return new \WP_Error('digibl_query_input','Invalid query request.',['status'=>400]);} $chain=PostTypes::source_chain($r->get_param('sourceChain')); $n=$chain===null?null:self::definition($id,$node,['posts','loop'],$chain); if(!$n||($n['props']['source']??'custom')!=='custom'){return new \WP_Error('digibl_query_unavailable','This content is unavailable.',['status'=>404]);} $p=$n['props'];if(($p['pagination']??'none')==='none'&&$page>1){return new \WP_Error('digibl_query_page','Pagination is not enabled.',['status'=>400]);} if($term&&(($p['filters']??'no')!=='yes'||!self::taxonomy($p)||!term_exists($term,self::taxonomy($p)))){return new \WP_Error('digibl_query_term','This filter is unavailable.',['status'=>400]);} $n['props']['_query_page']=$page;$n['props']['_query_term']=$term; $html=(new Renderer(false,$id,false,$id,[],true,$chain))->query_fragment($n); $response=new \WP_REST_Response(['html'=>$html,'page'=>$page]);$response->header('Cache-Control','private, no-store');return $response; } }