*
Section 1 Title
*
Section 1 content goes here...
* *
*
Section 2 Title
*
Section 2 content goes here...
*
* [/mobile_accordion] * * NOTES: * - First section opens by default * - Supports nested shortcodes via do_shortcode() */ add_shortcode( 'mobile_accordion', function ( $atts, $content = null ) { static $instance = 0; ++$instance; // This line alone ensures all [gfsearch] and nested shortcodes run $content = do_shortcode( $content ); $prev = libxml_use_internal_errors( true ); $doc = new DOMDocument(); $doc->loadHTML( '
' . $content . '
' ); $errors = libxml_get_errors(); libxml_clear_errors(); libxml_use_internal_errors( $prev ); // Return early if HTML structure is critically malformed if ( ! empty( $errors ) ) { foreach ( $errors as $error ) { if ( LIBXML_ERR_ERROR === $error->level || LIBXML_ERR_FATAL === $error->level ) { return ''; } } } $xpath = new DOMXPath( $doc ); $containers = $xpath->query( '//*[@id="accordion-wrapper"]/div' ); $accordion_items = []; foreach ( $containers as $container ) { $children = []; foreach ( $container->childNodes as $child ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase if ( XML_ELEMENT_NODE === $child->nodeType && 'div' === $child->tagName ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $children[] = $child; } } if ( count( $children ) < 2 ) { continue; } $label = trim( $children[0]->textContent ); $content_html = ''; foreach ( $children[1]->childNodes as $node ) { $content_html .= $doc->saveHTML( $node ); } $accordion_items[] = [ 'label' => esc_html( $label ), 'content' => wp_kses_post( $content_html ), ]; } if ( empty( $accordion_items ) ) { return ''; } $id = 'mobile-accordion-' . $instance; $output = '
'; foreach ( $accordion_items as $i => $item ) { $btn_id = $id . '-btn-' . $i; $panel_id = $id . '-panel-' . $i; $is_open = ( 0 === $i ); $output .= '
' . $item['content'] . '
'; } $output .= '
'; ob_start(); ?>