Custom Element : Unwrapped Loop

Hi,

According to the “isssue” described here, I’m trying to create Custom Element named Unwrapped Loop.
Goal is to remove loop wrapper created by bricks on each ACF Flexible Content or Repeater field loop iteration :fire:.

Looks like the better way is :

  • Clone original includes/elements/container.php element to elements/unwrapped-loop.php in my child theme
  • Edit it renaming some stuff (className, $name, $category, label(), etc)
  • Remove unnecessary controls (except loop)
  • Remove loop controls condition based on element name (only div, container, block and section are allowed by default)
  • Add custom notice for experimental disclaimer purpose
  • in render() function, comment div wrapping :rainbow:
  • Call unwrapped-loop.php in child theme using function.php (doc)

Looks like it works like a charm with flexible content, still testing with differents context to improve it.

Here’s the temporary code. I’ll update it during my research. If you are interested by helping me,
you are welcome :slight_smile:

<?php

use Bricks\Breakpoints;
use Bricks\Capabilities;
use Bricks\Database;
use Bricks\Frontend;
use Bricks\Helpers;

if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

class Element_Unwrapped_Loop extends \Bricks\Element {
  /** 
   * How to create custom elements in Bricks
   * 
   * https://academy.bricksbuilder.io/article/create-your-own-elements
   */
    public $category = 'custom';
    public $name     = 'unwrapped-loop';
    public $icon     = 'fas fa-infinity'; // FontAwesome 5 icon in builder (https://fontawesome.com/icons)
    public $tag      = 'no-tag';
    public $vue_component = 'bricks-nestable';
    public $nestable = true;

    public function get_label() {
        return esc_html__( 'Unwrapped loop', 'bricks' );
    }

    public function get_keywords() {
        return ['loop', 'query'];
    }

    public function set_controls() {
        $this->controls['infoNoAccess'] = [
            'type'       => 'info',
            'content'    => esc_html__( 'This experimental Unwrapped Loop allows you to virtually wrap loop avoiding Bricks creates DOM element arround each loop iteration. Code based on original container.php element.', 'bricks' ),
        ];

        $this->controls['loopSeparator'] = [
            'type' => 'separator',
        ];

        /**
         * Loop Builder
         *
         * Enable for elements: Container, Block, Div and Section (@since 1.8)
         */
        $this->controls = array_replace_recursive( $this->controls, $this->get_loop_builder_controls() );



    }

    /**
     * Return shape divider HTML
     */
    public static function get_shape_divider_html( $settings = [] ) {
        $shape_dividers = ! empty( $settings['_shapeDividers'] ) && is_array( $settings['_shapeDividers'] ) ? $settings['_shapeDividers'] : [];
        $output         = '';

        foreach ( $shape_dividers as $shape ) {
            $shape_name = ! empty( $shape['shape'] ) ? $shape['shape'] : false;

            // Skip: No shape set
            if ( ! $shape_name ) {
                continue;
            }

            $svg = '';

            // Custom shape from attachment ID (@since 1.8.6)
            if ( $shape_name === 'custom' ) {
                $svg_path = ! empty( $shape['shapeCustom']['id'] ) ? get_attached_file( $shape['shapeCustom']['id'] ) : false;
                $svg      = $svg_path ? Helpers::file_get_contents( $svg_path ) : false;
            }

            // Shape from file
            else {
                $svg = Helpers::file_get_contents( BRICKS_PATH_ASSETS . "svg/shapes/{$shape_name}.svg" );
            }

            // Skip: SVG file doesn't exist
            if ( ! $svg ) {
                continue;
            }

            $shape_classes = [ 'bricks-shape-divider' ];
            $shape_styles  = [];

            // Shape classes
            if ( isset( $shape['front'] ) ) {
                $shape_classes[] = 'front';
            }

            if ( isset( $shape['flipHorizontal'] ) ) {
                $shape_classes[] = 'flip-horizontal';
            }

            if ( isset( $shape['flipVertical'] ) ) {
                $shape_classes[] = 'flip-vertical';
            }

            if ( isset( $shape['overflow'] ) ) {
                $shape_classes[] = 'overflow';
            }

            // Shape styles
            if ( isset( $shape['horizontalAlign'] ) ) {
                $shape_styles[] = "justify-content: {$shape['horizontalAlign']}";
            }

            if ( isset( $shape['verticalAlign'] ) ) {
                $shape_styles[] = "align-items: {$shape['verticalAlign']}";
            }

            // Shape inner styles
            $shape_inner_styles   = [];
            $shape_css_properties = [
                'height',
                'width',
                'top',
                'right',
                'bottom',
                'left',
            ];

            foreach ( $shape_css_properties as $property ) {
                $value = isset( $shape[ $property ] ) ? $shape[ $property ] : null;

                if ( $value !== null ) {
                    // Append default unit
                    if ( is_numeric( $value ) ) {
                        $value .= 'px';
                    }

                    $shape_inner_styles[] = "{$property}: {$value}";
                }
            }

            if ( isset( $shape['rotate'] ) ) {
                $rotate               = intval( $shape['rotate'] );
                $shape_inner_styles[] = "transform: rotate({$rotate}deg)";
            }

            $output .= '<div class="' . join( ' ', $shape_classes ) . '" style="' . join( '; ', $shape_styles ) . '">';
            $output .= '<div class="bricks-shape-divider-inner" style="' . join( '; ', $shape_inner_styles ) . '">';

            $dom = new \DOMDocument();
            libxml_use_internal_errors( true );
            $dom->loadXML( $svg );
            libxml_clear_errors();

            // SVG styles
            $svg_styles = [];

            if ( isset( $shape['fill']['raw'] ) ) {
                $svg_styles[] = "fill: {$shape['fill']['raw']}";
            } elseif ( isset( $shape['fill']['rgb'] ) ) {
                $svg_styles[] = "fill: {$shape['fill']['rgb']}";
            } elseif ( isset( $shape['fill']['hex'] ) ) {
                $svg_styles[] = "fill: {$shape['fill']['hex']}";
            }

            foreach ( $dom->getElementsByTagName( 'svg' ) as $element ) {
                $element->setAttribute( 'style', join( '; ', $svg_styles ) );
            }

            $svg = $dom->saveXML();

            $output .= str_replace( '<?xml version="1.0"?>', '', $svg );

            $output .= '</div>';
            $output .= '</div>';
        }

        return $output;
    }

    /**
     * Return background video HTML
     */
    public function get_background_video_html( $settings ) {
        // Loop over all breakpoints
        foreach ( Breakpoints::$breakpoints as $breakpoint ) {
            $setting_key      = $breakpoint['key'] === 'desktop' ? '_background' : "_background:{$breakpoint['key']}";
            $background       = ! empty( $settings[ $setting_key ] ) ? $settings[ $setting_key ] : false;
            $video_url        = ! empty( $background['videoUrl'] ) ? $background['videoUrl'] : false;
            $video_attributes = [];

            if ( strpos( $video_url, '{' ) !== false ) {
                $video_url = bricks_render_dynamic_data( $video_url, $this->post_id, 'link' );
            }

            if ( $video_url ) {
                $attributes[] = 'class="bricks-background-video-wrapper bricks-lazy-video"';
                $attributes[] = 'data-background-video-url="' . esc_url( $video_url ) . '"';

                if ( ! empty( $background['videoScale'] ) ) {
                    $attributes[] = 'data-background-video-scale="' . $background['videoScale'] . '"';
                }

                if ( ! empty( $background['videoAspectRatio'] ) ) {
                    $attributes[] = 'data-background-video-ratio="' . $background['videoAspectRatio'] . '"';
                }

                if ( ! empty( $background['videoStartTime'] ) ) {
                    $attributes[] = 'data-background-video-start="' . $background['videoStartTime'] . '"';
                }

                if ( ! empty( $background['videoEndTime'] ) ) {
                    $attributes[] = 'data-background-video-end="' . $background['videoEndTime'] . '"';
                }

                if ( empty( $background['videoPlayOnce'] ) ) {
                    $attributes[] = 'data-background-video-loop="1"';
                }

                if ( ! empty( $background['videoShowAtBreakpoint'] ) ) {
                    $breakpoint = Breakpoints::get_breakpoint_by( 'key', $background['videoShowAtBreakpoint'] );
                    $width      = isset( $breakpoint['width'] ) ? $breakpoint['width'] : null;

                    // Is base breakpoint
                    if ( isset( $breakpoint['base'] ) ) {
                        $breakpoints = Breakpoints::$breakpoints;

                        foreach ( $breakpoints as $index => $bp ) {
                            // Is first breakpoint
                            if ( $bp['key'] === $breakpoint['key'] && $index === 0 ) {
                                // Get 'width' of next breakpoint
                                $next_breakpoint = isset( $breakpoints[ $index + 1 ] ) ? $breakpoints[ $index + 1 ] : null;

                                if ( $next_breakpoint ) {
                                    $width = Breakpoints::$is_mobile_first ? 0 : $next_breakpoint['width'] + 1;
                                }
                            }
                        }
                    }

                    if ( $width ) {
                        $attributes[] = 'data-background-video-show-at-breakpoint="' . $width . '"';
                    }
                }

                // Video poster (@since 1.11)
                if ( ! empty( $background['videoPoster'] ) ) {
                    $video_attributes[] = 'poster="' . $background['videoPoster']['url'] . '"';
                    $attributes[]       = 'data-background-video-poster="' . $background['videoPoster']['url'] . '"';
                }
                // YouTube video poster (@since 1.11)
                if ( ! empty( $background['videoPosterYouTube'] ) ) {
                    $youtube_poster_size = $background['videoPosterYouTubeSize'] ?? 'maxresdefault';
                    $attributes[]        = 'data-background-video-poster-yt-size="' . $youtube_poster_size . '"';
                }

                $attributes       = join( ' ', $attributes );
                $video_attributes = join( ' ', $video_attributes );

                // @since 1.4: Chrome doesn't play the .mp4 background video if the <video> tag is injected programmatically using JavaScript
                return "<div $attributes><video autoplay loop playsinline muted $video_attributes></video></div>";
            }
        }
    }

    public function render() {
        $element  = $this->element;
        $settings = $this->settings ?? [];
        $output   = '';

        // Bricks Query Loop
        if ( isset( $settings['hasLoop'] ) ) {
            // Hold the component to first unset 'hasLoop' and then add back 'hasLoop' after the query->render (@since 1.12)
            $original_component = Helpers::get_component( $element );

            // Hold the global element to first unset 'hasLoop' and then add back 'hasLoop' after the query->render
            $global_element = Helpers::get_global_element( $element );

            // STEP: Query
            add_filter( 'bricks/posts/query_vars', [ $this, 'maybe_set_preview_query' ], 10, 3 );

            // Is component: Generate random ID for component instance (@since 1.12)
            if ( ! empty( $element['instanceId'] ) && ! empty( $element['parentComponent'] ) ) {
                $element['id'] .= ':' . $element['instanceId'];
            }

            $query = new \Bricks\Query( $element );

            remove_filter( 'bricks/posts/query_vars', [ $this, 'maybe_set_preview_query' ], 10, 3 );

            // Prevent endless loop
            unset( $element['settings']['hasLoop'] );

            // Prevent endless loop for component (@since 1.12)
            if ( $original_component ) {
                // Find all component element and unset 'hasLoop'
                Database::$global_data['components'] = array_map(
                    function( $component ) use ( $element ) {
                        if ( ! empty( $element['cid'] ) && $element['cid'] === $component['id'] ) {
                            foreach ( $component['elements'] as $index => $component_element ) {
                                if ( isset( $component['elements'][ $index ]['settings']['hasLoop'] ) ) {
                                    unset( $component['elements'][ $index ]['settings']['hasLoop'] );
                                }
                            }
                        }
                        return $component;
                    },
                    Database::$global_data['components']
                );
            }

            // Prevent endless loop for global element
            if ( ! empty( $global_element['global'] ) ) {
                // Find the global element and unset 'hasLoop'
                Database::$global_data['elements'] = array_map(
                    function( $global_element ) use ( $element ) {
                        if ( ! empty( $element['global'] ) && $element['global'] === $global_element['global'] ) {
                            unset( $global_element['settings']['hasLoop'] );
                        }
                        return $global_element;
                    },
                    Database::$global_data['elements']
                );
            }

            // STEP: Render loop
            $output = $query->render( 'Bricks\Frontend::render_element', compact( 'element' ) );

            echo $output;

            // Prevent endless loop for component (@since 1.12)
            if ( $original_component ) {
                // Restore orignal component with 'hasLoop' setting after execute render_element
                Database::$global_data['components'] = array_map(
                    function( $component ) use ( $element, $original_component ) {
                        if ( ! empty( $element['cid'] ) && $element['cid'] === $component['id'] ) {
                            $component = $original_component;
                        }
                        return $component;
                    },
                    Database::$global_data['components']
                );
            }

            // Prevent endless loop for global element
            if ( ! empty( $global_element['global'] ) ) {
                // Add back global element 'hasLoop' setting after execute render_element
                Database::$global_data['elements'] = array_map(
                    function( $global_element ) use ( $element ) {
                        if ( ! empty( $element['global'] ) && $element['global'] === $global_element['global'] ) {
                            $global_element['settings']['hasLoop'] = true;
                        }
                        return $global_element;
                    },
                    Database::$global_data['elements']
                );
            }

            // STEP: Infinite scroll
            $this->render_query_loop_trail( $query );

            // Destroy Query to explicitly remove it from global store
            $query->destroy();

            unset( $query );

            return;
        }

        // Render the video wrapper first so we know it before adding the has-bg-video class
        $video_wrapper_html = $this->get_background_video_html( $settings );

        // No background video set on element ID: Loop over element global classes
        if ( ! $video_wrapper_html ) {
            $elements_class_ids = ! empty( $settings['_cssGlobalClasses'] ) ? $settings['_cssGlobalClasses'] : [];

            if ( count( $elements_class_ids ) ) {
                $global_classes = Database::$global_data['globalClasses'];

                foreach ( $global_classes as $global_class ) {
                    $global_class_id = ! empty( $global_class['id'] ) ? $global_class['id'] : '';

                    if ( ! $video_wrapper_html && in_array( $global_class_id, $elements_class_ids ) ) {
                        if ( ! empty( $global_class['settings'] ) ) {
                            $video_wrapper_html = $this->get_background_video_html( $global_class['settings'] );
                        }
                    }
                }
            }
        }

        // Add .has-bg-video to set z-index: 1 (#2g9ge90)
        if ( ! empty( $video_wrapper_html ) ) {
            $this->set_attribute( '_root', 'class', 'has-bg-video' );
        }

        // Add .has-shape to set position: relative (#2t7w2bq)
        if ( ! empty( $settings['_shapeDividers'] ) ) {
            $this->set_attribute( '_root', 'class', 'has-shape' );
        }

        // Non-megamenu dropdown content: Set tag to 'ul'
        $parent_id      = ! empty( $element['parent'] ) ? $element['parent'] : false;
        $parent_element = ! empty( Frontend::$elements[ $parent_id ] ) ? Frontend::$elements[ $parent_id ] : false;

        if ( $parent_element && $parent_element['name'] === 'dropdown' && ! isset( $parent_element['settings']['megaMenu'] ) ) {
            $this->tag = 'ul';
        }

        /**
         * Live search wrapper
         *
         * Add 'data-brx-ls-wrapper' to hide live search wrapper on page load.
         *
         * @since 1.9.6
         */
        if ( count( Frontend::$live_search_wrapper_selectors ) ) {
            foreach ( Frontend::$live_search_wrapper_selectors as $live_search_query_id => $live_search_wrapper_selector ) {
                /**
                 * 1. Last six-characters of live search results selector match element.id
                 * 2. Live search results selector matches custom element ID
                 */
                $match_default_id = "#brxe-{$element['id']}" === $live_search_wrapper_selector;
                $match_custom_id  = ! empty( $element['settings']['_cssId'] ) && "#{$element['settings']['_cssId']}" === $live_search_wrapper_selector;

                if ( $match_default_id || $match_custom_id ) {
                    unset( Frontend::$live_search_wrapper_selectors[ $live_search_query_id ] );

                    $this->set_attribute( '_root', 'data-brx-ls-wrapper', $live_search_query_id );

                    // Ensure setting element 'id' to target the live search wrapper with CSS. Could be omittied, if the elment doesn't has_css_settings.
                    if ( empty( $this->attributes['_root']['id'] ) ) {
                        $this->set_attribute( '_root', 'id', $this->get_element_attribute_id() );
                    }
                }
            }
        }

        // Default: Non-query loop
        //$output .= "<{$this->tag} {$this->render_attributes( '_root' )}>";

        $output .= self::get_shape_divider_html( $settings );

        $output .= $video_wrapper_html;

        // Render element children
        if ( ! empty( $element['children'] ) && is_array( $element['children'] ) ) {
            foreach ( $element['children'] as $child_id ) {
                $child_element = Frontend::$elements[ $child_id ] ?? false;

                /**
                 * Skip element: Component with this 'cid' doesn't exist in database
                 *
                 * @since 1.12
                 */
                if ( ! empty( $child_element['cid'] ) && ! Helpers::get_component_by_cid( $child_element['cid'] ) ) {
                    continue;
                }

                $child_html = $child_element ? Frontend::render_element( $child_element ) : false; // Recursive

                if ( $child_element && $child_html ) {
                    // Nav items is parent element: Wrap this nav link in <li> (@since 1.8)
                    $parent_id               = $child_element['parent'];
                    $parent_element          = ! empty( Frontend::$elements[ $parent_id ] ) ? Frontend::$elements[ $parent_id ] : false;
                    $inside_nav_items        = ! empty( $parent_element['settings']['_hidden']['_cssClasses'] ) ? $parent_element['settings']['_hidden']['_cssClasses'] === 'brx-nav-nested-items' : false;
                    $inside_dropdown_content = ! empty( $parent_element['settings']['_hidden']['_cssClasses'] ) ? $parent_element['settings']['_hidden']['_cssClasses'] === 'brx-dropdown-content' : false;

                    // Wrap in <li> if child HTML does not start with an 'li' tag (e.g. non-megamenu dropdown)
                    if (
                        ( $inside_nav_items || $inside_dropdown_content ) &&
                        ( strpos( $child_html, '<li' ) === false || strpos( $child_html, '<li' ) !== 0 )
                    ) {
                        $dropdown_id      = $parent_element['parent'];
                        $dropdown_element = ! empty( Frontend::$elements[ $dropdown_id ] ) ? Frontend::$elements[ $dropdown_id ] : false;

                        // Megamenu: Don't wrap dropdown item in <li>
                        if ( isset( $dropdown_element['settings']['megaMenu'] ) ) {
                            $output .= $child_html;
                        }

                        // Default: Wrap menu item in <li>
                        else {
                            if ( isset( $child_element['settings']['hasLoop'] ) ) {
                                // Get first HTML tag
                                preg_match( '/<([a-zA-Z]+)([^>]*)>/', $child_html, $matches );
                                $html_tag = $matches[1] ?? 'div';

                                // Wrap each loop node in <li>
                                $output .= preg_replace( '/(<' . $html_tag . '.*?>.*?<\/' . $html_tag . '>)/', '<li class="menu-item">$1</li>', $child_html );
                            } else {
                                $output .= '<li class="menu-item">';
                                $output .= $child_html;
                                $output .= '</li>';
                            }
                        }
                    }

                    // Default: Render child element HTML
                    else {
                        $output .= $child_html;
                    }
                }
            }
        }

        /**
         * STEP: Add masonry trail nodes
         *
         * Suppose add these nodes inside base.php but no perfect hook yet.
         * Any custom element has to run this method manually in the render method.
         *
         * @since 1.11.1
         */
        $output .= $this->maybe_masonry_trail_nodes();

        //$output .= "</{$this->tag}>";

        echo $output;
    }
}
1 Like

Hi, still testing and cleaning the code. But would be a temporary solution because of cloning container.php code is not the best idea since the php loop query is not a separated core function. Container is a specific component that handle various situation.

Here’s the new code at this time. Tested w/ flexible (multiples sections inside) and nested repeater (basic fields inside, like links) :

<?php

use Bricks\Database;
use Bricks\Frontend;
use Bricks\Helpers;

if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

class Element_Unwrapped_Loop extends \Bricks\Element {
  /** 
   * How to create custom elements in Bricks
   * 
   * https://academy.bricksbuilder.io/article/create-your-own-elements
   */
    public $category = 'custom';
    public $name     = 'unwrapped-loop';
    public $icon     = 'fas fa-infinity'; // FontAwesome 5 icon in builder (https://fontawesome.com/icons)
    public $tag      = 'no-tag';
    public $vue_component = 'bricks-nestable';
    public $nestable = true;

    public function get_label() {
        return esc_html__( 'Unwrapped loop', 'bricks' );
    }

    public function get_keywords() {
        return ['loop', 'query'];
    }

    public function set_controls() {
        $this->controls['infoNoAccess'] = [
            'type'       => 'info',
            'content'    => esc_html__( 'This experimental Unwrapped Loop allows you to virtually wrap loop avoiding Bricks creates DOM element arround each loop iteration. Code based on original container.php element.', 'bricks' ),
        ];

        $this->controls['loopSeparator'] = [
            'type' => 'separator',
        ];

        /**
         * Loop Builder
         *
         * Enable for elements: Container, Block, Div and Section (@since 1.8)
         */
        $this->controls = array_replace_recursive( $this->controls, $this->get_loop_builder_controls() );

    }

    public function render() {
        $element  = $this->element;
        $settings = $this->settings ?? [];
        $output   = '';

        // Bricks Query Loop
        if ( isset( $settings['hasLoop'] ) ) {
            // Hold the component to first unset 'hasLoop' and then add back 'hasLoop' after the query->render (@since 1.12)
            $original_component = Helpers::get_component( $element );

            // Hold the global element to first unset 'hasLoop' and then add back 'hasLoop' after the query->render
            $global_element = Helpers::get_global_element( $element );

            // STEP: Query
            add_filter( 'bricks/posts/query_vars', [ $this, 'maybe_set_preview_query' ], 10, 3 );

            // Is component: Generate random ID for component instance (@since 1.12)
            if ( ! empty( $element['instanceId'] ) && ! empty( $element['parentComponent'] ) ) {
                $element['id'] .= ':' . $element['instanceId'];
            }

            $query = new \Bricks\Query( $element );

            remove_filter( 'bricks/posts/query_vars', [ $this, 'maybe_set_preview_query' ], 10, 3 );

            // Prevent endless loop
            unset( $element['settings']['hasLoop'] );

            // Prevent endless loop for component (@since 1.12)
            if ( $original_component ) {
                // Find all component element and unset 'hasLoop'
                Database::$global_data['components'] = array_map(
                    function( $component ) use ( $element ) {
                        if ( ! empty( $element['cid'] ) && $element['cid'] === $component['id'] ) {
                            foreach ( $component['elements'] as $index => $component_element ) {
                                if ( isset( $component['elements'][ $index ]['settings']['hasLoop'] ) ) {
                                    unset( $component['elements'][ $index ]['settings']['hasLoop'] );
                                }
                            }
                        }
                        return $component;
                    },
                    Database::$global_data['components']
                );
            }

            // Prevent endless loop for global element
            if ( ! empty( $global_element['global'] ) ) {
                // Find the global element and unset 'hasLoop'
                Database::$global_data['elements'] = array_map(
                    function( $global_element ) use ( $element ) {
                        if ( ! empty( $element['global'] ) && $element['global'] === $global_element['global'] ) {
                            unset( $global_element['settings']['hasLoop'] );
                        }
                        return $global_element;
                    },
                    Database::$global_data['elements']
                );
            }

            // STEP: Render loop
            $output = $query->render( 'Bricks\Frontend::render_element', compact( 'element' ) );

            echo $output;

            // Prevent endless loop for component (@since 1.12)
            if ( $original_component ) {
                // Restore orignal component with 'hasLoop' setting after execute render_element
                Database::$global_data['components'] = array_map(
                    function( $component ) use ( $element, $original_component ) {
                        if ( ! empty( $element['cid'] ) && $element['cid'] === $component['id'] ) {
                            $component = $original_component;
                        }
                        return $component;
                    },
                    Database::$global_data['components']
                );
            }

            // Prevent endless loop for global element
            if ( ! empty( $global_element['global'] ) ) {
                // Add back global element 'hasLoop' setting after execute render_element
                Database::$global_data['elements'] = array_map(
                    function( $global_element ) use ( $element ) {
                        if ( ! empty( $element['global'] ) && $element['global'] === $global_element['global'] ) {
                            $global_element['settings']['hasLoop'] = true;
                        }
                        return $global_element;
                    },
                    Database::$global_data['elements']
                );
            }

            // STEP: Infinite scroll
            $this->render_query_loop_trail( $query );

            // Destroy Query to explicitly remove it from global store
            $query->destroy();

            unset( $query );

            return;
        }

        // Render element children
        if ( ! empty( $element['children'] ) && is_array( $element['children'] ) ) {
            foreach ( $element['children'] as $child_id ) {
                $child_element = Frontend::$elements[ $child_id ] ?? false;

                /**
                 * Skip element: Component with this 'cid' doesn't exist in database
                 *
                 * @since 1.12
                 */
                if ( ! empty( $child_element['cid'] ) && ! Helpers::get_component_by_cid( $child_element['cid'] ) ) {
                    continue;
                }

                $output .= $child_element ? Frontend::render_element( $child_element ) : false; // Recursive

            }
        }

        echo $output;
    }
}
1 Like