2

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:

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]
2
  • 1
    Polluting the world, one shortcode at a time ;) A million and one is madness, and same name shortcodes nested within one another rips hole in space time continuum, but, while on the topic of columns have you looked at: Grid Columns - the principle is one column shortcode to rule them all. I wonder if that meets your needs, whether you can borrow from it or implement it directly into your framework?
    – Adam
    Oct 24, 2012 at 10:59
  • Userabuser, that Github link you supplied might actually be of some use, it even accounts for adding first and last classes as well all while using one shortcode. Thanks for that, I'll definitely get some inspiration from it or perhaps just use it. Oct 24, 2012 at 11:15

3 Answers 3

2

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.

3
  • Thanks for responding! Haha, I don't think I've ever read the documentation for shortcodes before. I've just known how to do them so never had to look, rookie mistake right there not consulting the documentation. When you say recursively call the function, would you mind sharing a code example? I'm having a hard time doing the code in my head as to how I would recursively call the shortcode function to parse out those additional column shortcodes. Oct 24, 2012 at 11:17
  • edited the answer Oct 24, 2012 at 11:40
  • Perfect. I get you now, wasn't entirely sure if you meant calling the same shortcode function from within itself, or if you meant something else. Thanks for clarifying that, the solution works well. Oct 24, 2012 at 23:59
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.

0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.