*
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();
?>