empty('Add items in the Content panel');}
switch ($n['type']) {
case 'mega-menu':
$h='';
case 'woo-mini-cart':
if(!class_exists('WooCommerce')){return $this->empty('Activate WooCommerce to use this module');}if($this->fallback){return '';}
if(!$this->preview){wp_enqueue_script('wc-cart-fragments');wp_enqueue_script('wc-add-to-cart');}
return '
';
case 'data-repeater':
$ref=(int)$p['template'];if(!$ref||$ref===$this->source_id||isset(self::$embedding[$ref])||count(self::$embedding)>=3||get_post_status($ref)!=='publish'||post_password_required($ref)){return $this->empty('Choose a published card template');}
$rows=DynamicData::public_meta($this->post_id,$p['metaKey'],true);if(!is_array($rows)){return $this->empty('The public repeater is empty or unavailable');}$doc=Document::clean(json_decode(get_post_meta($ref,'_digibl_published',true),true));if(is_wp_error($doc)){return '';}
self::$embedding[$ref]=true;try{foreach(array_slice($rows,0,(int)$p['count']) as $i=>$row){if(!is_array($row)){continue;}$copy=$doc;$prefix='d'.substr(hash('sha256',$n['id'].'-'.$i),0,12);$copy['nodes']=Document::scope_nodes($copy['nodes'],$prefix);$h.=''.(new self($this->preview,$this->post_id,$this->fallback,$ref,$row,false))->render($copy).'';}}finally{unset(self::$embedding[$ref]);}return $this->card_grid($h,$p['columns']);
case 'builder-template':
$ref=(int)$p['template'];$tag=$p['provider']==='beaver'?'fl_builder_insert_layout':'elementor-template';
if($this->preview){return '
'.(shortcode_exists($tag)?'Source builder available. Public rendering uses its original modules and assets.':'Activate the source builder and its required add-ons.').'
';
case 'lottie':
if($this->fallback){return '';}
if(!$p['animation']){return $this->empty('Paste valid vector Lottie JSON without expressions or external assets');}
return '
';
case 'site-logo':
$logo=(int)get_theme_mod('custom_logo');$h=$logo?wp_get_attachment_image($logo,'full',false,['class'=>'dbs-site-logo']):$this->text(get_bloginfo('name'));
return ''.$h.'';
case 'post-author':
$post=get_post($this->post_id);if(!$post){return $this->empty('Author of the current post');}$author=(int)$post->post_author;
return '';
case 'post-comments':
if($this->preview||$this->fallback){return $this->empty('WordPress comments appear on the published page');}
if(!$this->post_id||post_password_required($this->post_id)){return '';}
$comments=get_comments(['post_id'=>$this->post_id,'status'=>'approve','number'=>(int)$p['count'],'order'=>'ASC','type'=>'comment']);
foreach($comments as $comment){$h.='
';}
$h=''.$h.'';
if('yes'===$p['form']&&comments_open($this->post_id)){ob_start();comment_form([],$this->post_id);$h.=ob_get_clean();}return $h.'';
case 'sidebar':
if($this->preview){return $this->empty('Widget area: '.$p['sidebar']);}if($this->fallback||!is_active_sidebar($p['sidebar'])){return '';}
ob_start();dynamic_sidebar($p['sidebar']);return '';
case 'reusable-block':
if($this->preview){return $this->empty('WordPress synced pattern #'.(int)$p['block']);}if($this->fallback){return '';}
$block=get_post((int)$p['block']);if(!$block||$block->post_type!=='wp_block'||$block->post_status!=='publish'||post_password_required($block)){return '';}
return do_blocks($block->post_content);
case 'registration':
if(!get_option('users_can_register')){return $this->empty('Enable WordPress membership registration to display this link');}
return $this->button($p['label'],wp_registration_url());
case 'payment':
if('stripe-link'===$p['provider']){$parts=wp_parse_url($p['url']);$url=($parts['scheme']??'')==='https'&&($parts['host']??'')==='buy.stripe.com'&&!isset($parts['user'])?$p['url']:'';}
else{$url=is_email($p['merchant'])&&$p['amount']>0?'https://www.paypal.com/cgi-bin/webscr?'.http_build_query(['cmd'=>'_xclick','business'=>$p['merchant'],'item_name'=>$p['item'],'amount'=>number_format($p['amount'],2,'.',''),'currency_code'=>$p['currency'],'no_shipping'=>1]):'';}
return $url?$this->button($p['label'],$url):$this->empty('Configure a payment recipient and amount, or a Stripe payment link');
case 'woo-product-field':return $this->product_component($p);
case 'table-search':
$headers=array_slice($this->lines($p['headers']),0,12);$h='
'.$this->text($p['caption']).'
';
foreach($headers as $i=>$label){$h.='
';}$h.='
';
foreach(array_slice($this->lines($p['rows']),0,100) as $row){$cells=explode('|',$row);$h.='
';foreach($headers as $i=>$unused){$h.='
'.$this->text(trim($cells[$i]??'')).'
';}$h.='
';}
return $h.'
';
case 'post-content':
if($this->source_id===$this->post_id||$this->fallback){return $this->empty('Original page content appears when this single template is assigned');}
$post=get_post($this->post_id);if(!$post){return $this->empty('Choose a single post preview');}
if(post_password_required($post)){return get_the_password_form($post);}
$managed=Document::clean(json_decode(get_post_meta($post->ID,'_digibl_published',true),true));
if(!is_wp_error($managed)){return (new self($this->preview,$post->ID,$this->fallback,$post->ID,[],$this->interaction_context))->render($managed);}
$saved=$GLOBALS['post']??null;$GLOBALS['post']=$post;
try{$h=apply_filters('the_content',$post->post_content);}finally{$GLOBALS['post']=$saved;}
return '
'.$h.'
';
case 'featured-image':
$h=get_the_post_thumbnail($this->post_id,$p['size'],['loading'=>'lazy','class'=>'dbs-img']);
return $h?('yes'===$p['link']?''.$h.'':$h):$this->empty('Featured image of the current post');
case 'archive-info':
$title=is_search()?'Search results: '.get_search_query():(is_home()?get_the_title(get_option('page_for_posts')):get_the_archive_title());
return '
'.('yes'===$p['description']?wp_kses_post(get_the_archive_description()):'').'';
case 'pagination':
if($this->preview){return '';}
return get_the_posts_pagination(['mid_size'=>2,'prev_text'=>'←','next_text'=>'→']);
case 'loop':
$inline=!empty($n['children']);$ref=(int)$p['template'];
if($inline){$doc=Document::blank();$doc['settings']=array_merge($doc['settings'],$this->layout_settings);$doc['settings']['component']=null;$doc['nodes']=$n['children'];}
else{
if(!$ref||$ref===$this->source_id||isset(self::$embedding[$ref])||count(self::$embedding)>=3||get_post_status($ref)!=='publish'||get_post_type($ref)!=='digibl_template'){return $this->empty('Choose a published loop template or add an inline card');}
$doc=Document::clean(json_decode(get_post_meta($ref,'_digibl_published',true),true));if(is_wp_error($doc)){return '';}
}
$query=$p['source']==='current-query'&&!$this->preview?$GLOBALS['wp_query']:ContentQuery::query($p,$this->post_id);
if(!$inline){self::$embedding[$ref]=true;}
try{
foreach($query->posts as $i=>$post){
if($post->post_status!=='publish'||post_password_required($post)){continue;}
$copy=$doc;$editable=$this->preview&&$inline&&$i===0;
if(!$editable){$copy['nodes']=Document::scope_nodes($copy['nodes'],'l'.substr(hash('sha256',$n['id'].'-'.$post->ID),0,8));}
$card=(new self($this->preview,$post->ID,$this->fallback,$inline?$this->source_id:$ref,[],false))->render($copy);
if($this->preview&&!$editable){$card=preg_replace('/ data-dbs-(?:node|row|column|image-drop)="[^"]*"/','',$card);}
$h.=''.$card.'';
}
if(!$h&&$this->preview&&$inline){$h=''.(new self(true,$this->post_id,false,$this->source_id,[],false))->render($doc).'';}
}finally{if(!$inline){unset(self::$embedding[$ref]);}}
$interactive=$p['source']==='custom'&&!$this->fallback&&$this->interaction_allowed();$more=$interactive&&ContentQuery::has_more($p,$query);
return 'source_id,$n['_sourceId']??$n['id'],$this->preview,$more,$this->source_chain):'').'>'.($interactive?ContentQuery::controls($p):'').'
'.($h?:'
No matching content.
').'
'.($interactive?ContentQuery::tail($p,$more):'').'';
case 'button-group':
foreach($p['items'] as $r){$h.=$this->button($r['label'],$r['url'],$r['variant'],['allowEmptyUrl'=>true,'icon'=>$r['icon']??'none','iconPosition'=>$r['iconPosition']??'after','iconColor'=>$r['iconColor']??'','iconSize'=>$r['iconSize']??0,'audio'=>$audioStyle,'audioEnabled'=>$r['audioEnabled']??'no','audioEffect'=>$r['audioEffect']??'none','audioVolume'=>$r['audioVolume']??55,'audioCooldown'=>$r['audioCooldown']??180,'audioRepeat'=>$r['audioRepeat']??'always']);}
return '
'.$h.'
';
case 'icon':
$h=''.Icons::symbol($p['symbol']).'';
return $p['url']?''.$h.'':$h;
case 'icon-list':
foreach($p['items'] as $r){$h.='
';
case 'callout':
return '';
case 'dual-heading':
return '<'.$p['level'].' class="dbs-heading">'.$this->text($p['before']).''.$this->text($p['accent']).''.$this->text($p['after']).''.$p['level'].'>';
case 'animated-heading':
$words=array_slice($this->lines($p['words']),0,30);
return '
';}
return $this->carousel($h,$id,$p['autoplay'],(int)$p['columns']);
case 'filter-gallery':
$cats=[];foreach($p['items'] as $index=>$r){if($r['category']!==''){$cats[$r['category']][]=$index;}$h.=''.$this->image($r['image']).'';}
$buttons='';foreach($cats as $cat=>$indices){$buttons.='';}
return '
'.$buttons.'
'.$this->card_grid($h,$p['columns']).'
';
case 'image-compare':
if(!$p['before']['url']||!$p['after']['url']){return $this->empty('Choose both comparison images');}
$p['before']['ratio']='16/9';$p['after']['ratio']='16/9';
return '
';
case 'hotspot':
foreach($p['items'] as $i=>$r){$h.=''.($i+1).'
'.$this->text($r['title']).'
'.$this->rich($r['content']).'
';}
return '
'.$this->image($p['image']).$h.'
';
case 'flipbox':
return ''.($p['image']['url']?$this->image($p['image']):'').''.$this->text($p['title']).''.$this->text(wp_strip_all_tags($p['front'])).''.$this->text($p['toggle']).'
'.$this->rich($p['back']).'
'.$this->button($p['label'],$p['url']).'
';
case 'image-panels':
foreach($p['items'] as $r){$h.=''.$this->image($r['image']).'
';
case 'coupon':
return '';
case 'download':
return $this->button($p['label'],$p['url']).($p['description']?'
'.$this->text($p['description']).'
':'');
case 'search':
return '';
case 'nav-menu':
if(!$p['menu']){return $this->empty('Choose a WordPress menu ID');}
$menu=wp_nav_menu(['menu'=>(int)$p['menu'],'echo'=>false,'fallback_cb'=>false,'container'=>false,'menu_class'=>'dbs-menu-list','menu_id'=>$id.'-list','depth'=>4]);
if(!$menu){return $this->empty('The selected menu is unavailable');}
return '';
case 'breadcrumbs':
$parts=[''.$this->text($p['home']).''];
foreach(array_reverse(get_post_ancestors($this->post_id)) as $parent){if(get_post_status($parent)==='publish'&&!post_password_required($parent)){$parts[]=''.$this->text(get_the_title($parent)).'';}}
if($this->post_id){$parts[]=''.$this->text(get_the_title($this->post_id)).'';}
return '';
case 'sitemap':
$pages=get_posts(['post_type'=>'page','post_status'=>'publish','has_password'=>false,'posts_per_page'=>(int)$p['count'],'orderby'=>$p['order'],'order'=>'ASC','no_found_rows'=>true]);
foreach($pages as $page){$h.='
';}
return '';
case 'categories':
$terms=get_terms(['taxonomy'=>'category','hide_empty'=>true,'number'=>(int)$p['count']]);
if(!is_wp_error($terms)){foreach($terms as $term){$url=get_term_link($term);if(!is_wp_error($url)){$h.=''.$this->text($term->name).('yes'===$p['showCount']?''.(int)$term->count.'':'').'';}}}
return ($p['title']?'
'.$this->text($p['title']).'
':'').$this->card_grid($h,$p['columns']);
case 'post-info':
$post=get_post($this->post_id);if(!$post){return $this->empty('Post information appears on the published page');}$parts=[];
if('yes'===$p['author']){$parts[]=$this->text(get_the_author_meta('display_name',$post->post_author));}
if('yes'===$p['date']){$parts[]='';}
if('yes'===$p['terms']){$parts[]=get_the_category_list(', ','',$post->ID);}
return '
'.implode(' · ',array_filter($parts)).'
';
case 'post-navigation':
if(!$this->post_id){return $this->empty('Adjacent post links appear on the published page');}
$current=get_post($this->post_id);if(!$current){return '';}
foreach(['previous'=>['<','DESC'],'next'=>['>','ASC']] as $key=>$query){$q=new \WP_Query(['post_type'=>$current->post_type,'post_status'=>'publish','has_password'=>false,'posts_per_page'=>1,'no_found_rows'=>true,'date_query'=>[['column'=>'post_date','compare'=>$query[0],($key==='previous'?'before':'after')=>$current->post_date]],'order'=>$query[1],'orderby'=>'date']);if($q->posts){$post=$q->posts[0];$h.=''.$this->text($p[$key]).''.$this->text(get_the_title($post)).'';}}
return '';
case 'dynamic-field':
$value=$this->dynamic_value($p);$text=''.$this->text($p['before']).''.$this->text($value!==''?$value:$p['fallback']).''.$this->text($p['after']).'';return ''.('yes'===($p['link']??'no')&&$this->post_id?''.$text.'':$text).'';
case 'integration':
return $this->form_integration($p);
case 'content-toggle':
return '
';
case 'popup': case 'offcanvas':
$trigger=esc_attr($p['trigger']??'click');$opener='';
if('offcanvas'===$n['type']||$trigger==='click'){$opener='';}
return '
'.$opener.'
';
case 'notice':
return '';
case 'counter':
return '
';
case 'video-gallery':
foreach($p['items'] as $r){$h.=''.$this->video($r['url']).''.$this->text($r['title']).'';}
return $this->card_grid($h,$p['columns']);
case 'reading-progress':
return '';
case 'share':
$link=$this->post_id?get_permalink($this->post_id):home_url('/');$title=$this->post_id?get_the_title($this->post_id):get_bloginfo('name');$encoded=rawurlencode($link);
$links=['facebook'=>['Facebook','https://www.facebook.com/sharer/sharer.php?u='.$encoded],'linkedin'=>['LinkedIn','https://www.linkedin.com/sharing/share-offsite/?url='.$encoded],'whatsapp'=>['WhatsApp','https://wa.me/?text='.rawurlencode($title.' '.$link)],'email'=>['Email','mailto:?subject='.rawurlencode($title).'&body='.$encoded]];
foreach($links as $key=>$pair){if('yes'===$p[$key]){$h.=''.$this->text($pair[0]).'';}}
if('yes'===$p['copy']){$h.='';}
return '
';
case 'anchor':
return $p['name']?'':$this->empty('Set an anchor name; link to #dbs-anchor-name');
case 'map':
$dx=180/pow(2,max(1,(int)$p['zoom']-1));$dy=$dx/2;$lat=(float)$p['lat'];$lng=(float)$p['lng'];
$map='https://www.openstreetmap.org/export/embed.html?'.http_build_query(['bbox'=>implode(',',[max(-180,$lng-$dx),max(-85,$lat-$dy),min(180,$lng+$dx),min(85,$lat+$dy)]),'layer'=>'mapnik','marker'=>$lat.','.$lng]);
$iframe='';
if('yes'===$p['consent']){$iframe='';}
return '
';
case 'form':
return $this->interaction_allowed()?$this->contact_form($p,$id,$n['_sourceId']??$n['id']):$this->empty('Forms require a direct public page or an assigned site template.');
case 'login':
$login=wp_login_url($p['redirect']?:get_permalink($this->post_id));
// Native login preserves 2FA, passkeys and rate limiting supplied by authentication plugins.
return '
';
case 'reusable':
$ref=(int)$p['postId'];if(!$ref||$ref===$this->post_id||isset(self::$embedding[$ref])||count(self::$embedding)>=3){return $this->empty('Choose a different published Digiter page; recursive sections are blocked');}
if(get_post_status($ref)!=='publish'||post_password_required($ref)){return $this->empty('Reusable source is unavailable');}
$doc=Document::clean(json_decode(get_post_meta($ref,'_digibl_published',true),true));if(is_wp_error($doc)){return $this->empty('Choose a published Digiter page');}
self::$embedding[$ref]=true;
$prefix='r'.substr(hash('sha256',$n['id'].'-'.$ref),0,10).'_';$doc['nodes']=Document::scope_nodes($doc['nodes'],$prefix);
try{$h=(new self($this->preview,$ref,$this->fallback,$ref,[],$this->interaction_context,array_merge($this->source_chain,[['post'=>$this->source_id,'node'=>$n['_sourceId']??$n['id']]])))->render($doc);}finally{unset(self::$embedding[$ref]);}return $h;
case 'woo-products': case 'woo-categories': case 'woo-add-cart': case 'woo-pages': case 'woo-cart-link':
return $this->commerce($n['type'],$p);
case 'device-frame':
return '
'.$this->image($p['image']).'
';
case 'image-scroll':
$p['image']['ratio']='auto';return '
'.$this->image($p['image']).'
';
case 'content-ticker':
foreach($p['items'] as $r){$h.='
';}
return $this->carousel($h,$id,$p['autoplay']);
}
return '';
}
private function product_component(array $p): string {
if(!class_exists('WooCommerce')){return $this->empty('Activate WooCommerce to use product components');}
if($this->preview){return $this->empty('WooCommerce product component: '.$p['component']);}if($this->fallback){return '';}
$product=wc_get_product((int)($p['product']?:$this->post_id));if(!$product||$product->get_status()!=='publish'||post_password_required($product->get_id())||!$product->is_visible()){return '';}
$oldProduct=$GLOBALS['product']??null;$oldPost=$GLOBALS['post']??null;$GLOBALS['product']=$product;$GLOBALS['post']=get_post($product->get_id());
ob_start();try{switch($p['component']){
case 'title':woocommerce_template_single_title();break;
case 'price':woocommerce_template_single_price();break;
case 'image':echo wp_kses_post($product->get_image('large'));break;
case 'rating':woocommerce_template_single_rating();break;
case 'short-description':woocommerce_template_single_excerpt();break;
case 'description':echo wp_kses_post(wpautop($product->get_description()));break;
case 'meta':woocommerce_template_single_meta();break;
case 'tabs':woocommerce_output_product_data_tabs();break;
case 'add-to-cart':woocommerce_template_single_add_to_cart();break;
case 'related':woocommerce_output_related_products();break;
case 'upsells':woocommerce_upsell_display();break;
case 'breadcrumb':woocommerce_breadcrumb();break;
}$html=ob_get_clean();}catch(\Throwable $error){ob_end_clean();throw $error;}finally{$GLOBALS['product']=$oldProduct;$GLOBALS['post']=$oldPost;}
return '
'.$html.'
';
}
private function card_grid(string $html,$columns): string {
return '
';
}
private function dynamic_value(array $p): string {
$post=get_post($this->post_id);
if($p['source']==='row-field'){return (string)DynamicData::value($p,$this->post_id,$this->row_values);}
switch($p['source']){
case 'post-url':return $post?get_permalink($post):'';
case 'post-id':return $post?(string)$post->ID:'';
case 'modified-date':return $post?get_the_modified_date('',$post):'';
case 'site-title':return get_bloginfo('name');
case 'site-description':return get_bloginfo('description');
case 'post-title':return $post?get_the_title($post):'';
case 'post-excerpt':return $post?$this->post_excerpt($post):'';
case 'post-date':return $post?get_the_date('',$post):'';
case 'author-name':return $post?(string)get_the_author_meta('display_name',$post->post_author):'';
case 'custom-field':
return (string)DynamicData::public_meta($this->post_id,$p['metaKey']);
}
return '';
}
private function form_integration(array $p): string {
$tags=['contact-form-7'=>['contact-form-7','id'],'wpforms'=>['wpforms','id'],'gravityforms'=>['gravityform','id'],'fluentform'=>['fluentform','id'],'formidable'=>['formidable','id'],'ninja_forms'=>['ninja_form','id']];
[$tag,$attribute]=$tags[$p['provider']];
if($this->preview){return '
'.$this->text($p['provider']).' · Form '.(int)$p['formId'].'
'.(shortcode_exists($tag)?'Integration available. Open the public page to test the form.':'Activate the required form plugin.').'
';
}
private function commerce(string $type,array $p): string {
if(!class_exists('WooCommerce')){return $this->empty('Activate WooCommerce to use this module');}
if($this->preview){return '
WooCommerce · '.$this->text($type).'
Open the public page to use the store.
';}
if($this->fallback){return '';}
switch($type){
case 'woo-products':$category=preg_replace('/[^a-zA-Z0-9_,\-]/','',$p['category']);$shortcode='[products limit="'.(int)$p['count'].'" columns="'.(int)$p['columns'].'" orderby="'.$p['order'].'" category="'.$category.'"'.('yes'===$p['sale']?' on_sale="true"':'').']';break;
case 'woo-categories':$shortcode='[product_categories number="'.(int)$p['count'].'" columns="'.(int)$p['columns'].'"]';break;
case 'woo-add-cart':
$product=wc_get_product((int)$p['product']);if(!$product||$product->get_status()!=='publish'||!$product->is_visible()){return '';}
$shortcode='[add_to_cart id="'.(int)$p['product'].'" show_price="'.('yes'===$p['price']?'true':'false').'"]';break;
case 'woo-pages':$shortcode='['.(['cart'=>'woocommerce_cart','checkout'=>'woocommerce_checkout','my-account'=>'woocommerce_my_account'][$p['page']]).']';break;
case 'woo-cart-link':return $this->button($p['label'],wc_get_cart_url());
default:return '';
}
return '
'.do_shortcode($shortcode).'
';
}
private function contact_form(array $p,string $id,string $node): string {
if(!$p['fields']){return $this->empty('Add form fields');}
if($this->fallback){return '';}
$h='';
}
}
'.$h.'
'; if('yes'===$p['form']&&comments_open($this->post_id)){ob_start();comment_form([],$this->post_id);$h.=ob_get_clean();}return $h.'