Does WordPress support a shortcode calling itself from within a shortcode call?

I’ve built a super-crazy WordPress framework that has a million and one shortcodes, one of those is a columns shortcode which supports a count parameter that allows you to define a column and the appropriate class will be added.

My shortcode is as follows:

Read More
add_shortcode('column', 'column_shortcode');
function column_shortcode($atts, $content = '')
{
    extract(shortcode_atts(array(
       'count' => 12
    ), $atts));

    if (stripos($count, '_') === FALSE)
    {
        if ((int) $count > 12)
        {
            $count = 12;
        }
    }

    $html = '<div class="column-'.$count.' column">';
    $html .= do_shortcode($content);
    $html .= '</div>';

    return $html;
}

There appears to be an issue however when the column shortcode is used within a column, is this a known issue or does WordPress not support shortcodes of the same name being nested within one another?

What essentially happens is the column inside is prematurely closed off in the outputted HTML and I see [/column] wrapped in paragraph tags being shown on the front-end. However if I create another shortcode and call it child, using the exact same code as above only the child shrotcode is just [child] instead of [column] everything works as expected.

Here is the code from the WYSIWYG editor in my WordPress installation. Exhibit A is the ideal scenario, I don’t want to have to use two different shortcodes that do the same thing. Exhibit B is what works by simply duplicating the shortcode.

Exhibit A – does not work

[column count="9"]

[column count="8_9"]

Welcome to the site.

Who's brave enough to fly into something we all keep calling a death sphere? Well, then good news! It's a suppository. A true inspiration for the children. Ah, computer dating. It's like pimping, but you rarely have to use the phrase "upside your head."

[/column]

[/column]

Exhibit B – works

[column count="9"]

[child count="8_9"]

Welcome to the site.

Who's brave enough to fly into something we all keep calling a death sphere? Well, then good news! It's a suppository. A true inspiration for the children. Ah, computer dating. It's like pimping, but you rarely have to use the phrase "upside your head."

[/child]

[/column]

Related posts

Leave a Reply

3 comments

  1. This is a known issue. If you have one shortcode inside another which has the same name, the wordpress parser will not be able to handle them correctly. This is also mentioned in the codex page for shortcode API under limitations.

    From the linked page
    However the parser will fail if a shortcode macro is used to enclose another macro of the same name

    The solution i suggest is to add extra parameters to the main shortcode call & in your shortcode function, recursively call itself based on if those extra parameters are present

    EXAMPLE

    [column count="9" child="8_9" 8_9_text="
    
    Welcome to the site.
    
    Who's brave enough to fly into something we all keep calling a death sphere? Well, then good news! It's a suppository. A true inspiration for the children. Ah, computer dating. It's like pimping, but you rarely have to use the phrase "upside your head."
    " /]
    

    Then in the code

    function shortcode_handler($atts, $content, $tag) {
        if(isset($atts['child']))
            $output = shortcode_handler(array('count'=>$atts['child']), $atts[$atts['child'].'_text'])
    
        // Do something else with the output
    
    }
    

    I know it doesn’t look good but maybe you can work something out in the context of your application.

  2. I wrote a little function that will enable shortcodes within themselves. It should work perfectly for your problem. It’s been super helpful for me.

    Open up the file ‘wp-includes/shortcodes.php’ and change the do_shortcode() function (on line 201) to the following

    function do_shortcode($content) {
        global $shortcode_tags;
    
        if (empty($shortcode_tags) || !is_array($shortcode_tags))
            return $content;
    
        $content = enable_recursive_shortcodes($content);
    
        $pattern = get_shortcode_regex();
        return preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content );
    }
    

    All that was changed is the the addition of *$content = enable_recursive_shortcodes($content);*

    That function goes through the $content passed to it and adds a suffix to the shortcode names of shortcodes within themselves. Then only the outer shortcodes are processed, and when their content is processed, the suffix is removed allowing them to be processed. There is no limit to how many levels deep it can go. (example: [box ][box ][box ][box ]Content[/box][/box][/box][/box] )

    Add the following function above the do_shortcode() function

    /**
     * Add shortcode suffix enabling recursive shortchodes.
     *
     * This function goes inside the set of $recursive_tags and adds a suffix to
     * the shortcode name so it will allow a shortcode to call itself in its content
     *
     * @since 2.5
     * @return string Content with recursive shortcodes modified out.
     */
    function enable_recursive_shortcodes($content) {
    
        $recursive_tags = array( 'column', 'div', 'span', 'table' ); // define recursive shortcodes, these must have closing tags 
        $suffix = '__recursive'; // suffix is added to the tag of recursive shortcodes
    
        $content = str_replace( $suffix, '', $content ); // remove old suffix on shortcodes to start process over
    
        foreach( $recursive_tags as $recursive_tag ) {
    
            $open_tag = '[' . $recursive_tag . ' ';
            $close_tag = '[/'. $recursive_tag . ']';
    
            $open_tags = 0;
    
            $open_pos = stripos( $content, $open_tag ); // find first opening tag
    
            $offset = $open_pos + strlen( $open_tag ); // set offset for first closing tag
    
            while( $open_pos !== false ) {
    
                $close_pos = stripos( $content, $close_tag, $offset ); // find first closing tag
    
                if(++$open_tags > 1) { // if we are inside an open tag
    
                    // modify open tag from original shortcode name
                    $content = substr( $content, 0, $open_pos ) . 
                                '[' .$recursive_tag . $suffix . ' ' . 
                                  substr( $content, $offset ); 
                    // modify closing tag from original shortcode name
                    $content = substr( $content, 0, $close_pos + strlen($suffix) ) . 
                                '[/'.$recursive_tag.'__recursive]' . 
                                substr( $content, $close_pos + strlen( $close_tag ) + strlen($suffix) ); 
                    $open_tags--;
                }
    
                $open_pos = stripos( $content, $open_tag, $offset ); // find next opening tag
    
                if( $close_pos < $open_pos ) $open_tags--; // if closing tag comes before next opening tag, lower open tag count
    
                $offset = $open_pos + strlen($open_tag); // set new offset for next open tag search
    
            }
    
        }
    
        return $content;
    
    }
    

    Be sure to update the $recursive_tags variable as you need. Only those tags will be checked for same-name children. Those tags must have closing tags [column ][/column] because [column /] will not work. They also must have a space after the shortcode name in the opening tag and no spaces in the closing tag because of how the function searches through tags. If anyone updates the code to allow [column] or [column /] style shortcodes to work, please share.

  3. The simplest — though maybe not the best — solution I’ve found for this is to add the tag multiple times under slight variations. For example, in the questioner’s case, it could be:

    function render_column( $atts, $content ) {
        // etc.
    
        return apply_filters( 'the_content', $content );
    }
    
    add_shortcode( 'column', 'render_column' );
    add_shortcode( 'column-level-2', 'render_column' );
    add_shortcode( 'column-level-3', 'render_column' );
    

    This requires a little extra documentation and some users might not grasp it immediately, but I feel it’s a little more intuitive than asking users to put the shortcode’s content in an attribute. And of course that solution introduces the separate problem of handling quotation marks in a shortcode attribute.