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 '

'.$this->text($p['title']).'

'.$this->text($p['label']).'
'; 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 '
'.$this->text($p['provider']).' · Template #'.$ref.'

'.(shortcode_exists($tag)?'Source builder available. Public rendering uses its original modules and assets.':'Activate the source builder and its required add-ons.').'

';} if($this->fallback||!$ref||$ref===$this->post_id||isset(self::$embedding[$ref])||count(self::$embedding)>=3||get_post_status($ref)!=='publish'||post_password_required($ref)||!shortcode_exists($tag)){return '';} if($p['provider']==='beaver'&&!get_post_meta($ref,'_fl_builder_enabled',true)){return '';} if($p['provider']==='elementor'&&get_post_type($ref)!=='elementor_library'){return '';} self::$embedding[$ref]=true;try{$h=do_shortcode('['.$tag.' id="'.$ref.'"]');}finally{unset(self::$embedding[$ref]);}return '
'.$h.'
'; 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.='
  • '.$this->text($comment->comment_author).'
    '.wpautop(wp_kses_post($comment->comment_content)).'
  • ';} $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='
    '; 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.='';}$h.='';} return $h.'
    '.$this->text($p['caption']).'
    '.$this->text(trim($cells[$i]??'')).'
    '; 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 '

    '.$this->text(wp_strip_all_tags($title?:'Posts')).'

    '.('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=''; return $p['url']?''.$h.'':$h; case 'icon-list': foreach($p['items'] as $r){$h.='
  • '.''.($r['url']?''.$this->text($r['text']).'':$this->text($r['text'])).''.'
  • ';} return ''; case 'callout': return ''; case 'dual-heading': return '<'.$p['level'].' class="dbs-heading">'.$this->text($p['before']).' '.$this->text($p['accent']).' '.$this->text($p['after']).''; case 'animated-heading': $words=array_slice($this->lines($p['words']),0,30); return '

    '.$this->text($p['before']).' '.$this->text(implode(', ',$words)).' '.$this->text($p['after']).'

    '; case 'testimonials': foreach($p['items'] as $r){$h.='
    '.$this->text($r['quote']).'
    '.($r['image']['url']?$this->image($r['image']):'').''.$this->text($r['name']).''.$this->text($r['role']).''.($r['rating']?''.str_repeat('★',(int)round($r['rating'])).'':'').'
    ';} return ($p['title']?'

    '.$this->text($p['title']).'

    ':'').$this->card_grid($h,$p['columns']); case 'team': foreach($p['items'] as $r){$h.='
    '.$this->image($r['image']).'

    '.($r['url']?''.$this->text($r['name']).'':$this->text($r['name'])).'

    '.$this->text($r['role']).'

    '.$this->text($r['bio']).'

    ';} return $this->card_grid($h,$p['columns']); case 'logo-grid': foreach($p['items'] as $r){$image=$r['image'];$image['fit']='contain';$h.='';} return '
    '.$this->card_grid($h,$p['columns']).'
    '; case 'slider': foreach($p['items'] as $r){$r['image']['ratio']=$p['height'];$h.='
    '.$this->image($r['image']).'

    '.$this->text($r['title']).'

    '.$this->rich($r['content']).'
    '.$this->button($r['label'],$r['url']).'
    ';} return $this->carousel($h,$id,$p['autoplay']); case 'image-carousel': foreach($p['images'] as $img){$h.='
    '.$this->image($img).'
    ';} 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 '
    '.$this->image($p['after']).'
    '.$this->image($p['before']).'
    '.$this->text($p['beforeLabel']).''.$this->text($p['afterLabel']).'
    '; 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']).'

    '.($r['url']?''.$this->text($r['title']).'':$this->text($r['title'])).'

    '.$this->text($r['text']).'

    ';} return $this->card_grid($h,$p['columns']); case 'business-hours': foreach($p['items'] as $r){$h.='
    '.$this->text($r['day']).'
    '.$this->text($r['hours']).'
    ';} return '

    '.$this->text($p['title']).'

    '.$h.'

    '.$this->text($p['note']).'

    '; case 'price-list': foreach($p['items'] as $r){$h.='
  • '.($r['image']['url']?$this->image($r['image']):'').'

    '.$this->text($r['title']).'

    '.$this->text($r['description']).'

    '.$this->text($r['price']).'
  • ';} return '

    '.$this->text($p['title']).'

    '; 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.='
  • '.$this->text(get_the_title($page)).'
  • ';} 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 '
    '.$this->rich($p['first']).'
    '.$this->rich($p['second']).'
    '; case 'popup': case 'offcanvas': $trigger=esc_attr($p['trigger']??'click');$opener=''; if('offcanvas'===$n['type']||$trigger==='click'){$opener='';} return '
    '.$opener.'

    '.$this->text($p['title']).'

    '.$this->rich($p['content']).'
    '; case 'notice': return ''; case 'counter': return '
    '.$this->text($p['prefix']).''.$this->text($p['value']).''.$this->text($p['suffix']).''.$this->text($p['label']).'
    '; 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 '
    '.$h.'
    '; case 'social-links': foreach($p['items'] as $index=>$r){if($r['url']){$h.=''.$this->text($r['label']).'';}} 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 '
    '.$iframe.'© OpenStreetMap contributors
    '; 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 '
    '.$this->button($p['label'],$login).'

    Lost password?

    '; 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.='
    '.($r['url']?''.$this->text($r['title']).'':$this->text($r['title'])).'
    ';} 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 '
    '.$html.'
    '; } private function carousel(string $html,string $id,$autoplay,int $columns=1): string { if(!$html){return $this->empty('Add carousel items');} 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.').'

    ';} if($this->fallback||!$p['formId']||!shortcode_exists($tag)){return '';} return '
    '.do_shortcode('['.$tag.' '.$attribute.'="'.(int)$p['formId'].'"]').'
    '; } 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='

    '.$this->text($p['title']).'

    '; $h.=FormFields::markup($p,$id).'
    '; if($p['consent']){$h.='';} if($p['privacy']){$h.='Privacy policy';} return $h.'

    '; } }