$content not null in shortcode, even though it is self closing

I haven’t tested this but based on memory, you are misunderstanding how shortcodes work. The “content” isn’t passed through the callback the way you seem to expect it to be. The content is extracted from the post body via a preg_* function. That, if my memory serves, is going to give you “content” as a string no matter what you try to pass into it.


As noted below, the Codex has a line stating that is_null() can be used to determine that the shortcode is closed. As far as I can tell, that does not work.

function baztag_func( $atts, $content = null) {
  echo gettype($content);
  echo '<br>';
  var_dump($content);
  echo '<br>';
  return "content = $content";
}
add_shortcode( 'baztag', 'baztag_func' );
do_shortcode('[baztag]');

do_shortcode('[baztag]abc[/baztag]');
die;

In all cases, the shorcode content comes out as a string. If you look at how shortcodes work, you can see why:

$pattern = get_shortcode_regex();
preg_match_all("/$pattern/s",'[baztag]abc[/baztag]',$matches);
var_dump($matches);

preg_match_all("/$pattern/s",'[baztag]',$matches);
var_dump($matches);
die;

Make note of the 5th item in the resulting array. That item is a string in both cases. That string is what is going to hit your callback as $content.

Now, Core uses preg_replace_callback() with the do_shortcode_tag() callback. That callback makes it look like null might be passed through if content isn’t set. To do that it checks the 5th element of that $matches array with isset(). If isset() returns false code should push null through, as I read it. But isset() on empty content…

$content="";
var_dump(isset($content));

Or an array…

$content = array();
var_dump(isset($content));

Is true. Even if this line: if ( isset( $m[5] ) ) { in do_shortcode_tag() is changed to if ( empty( $m[5] ) ) { you still get a string:

function do_shortcode_tag_v2( $m ) {
  global $shortcode_tags;

  // allow [[foo]] syntax for escaping a tag
  if ( $m[1] == '[' && $m[6] == ']' ) {
    return substr($m[0], 1, -1);
  }

  $tag = $m[2];
  $attr = shortcode_parse_atts( $m[3] );

  if ( empty( $m[5] ) ) {
    // enclosing tag - extra parameter
    return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, $m[5], $tag ) . $m[6];
  } else {
    // self-closing tag
    return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, null,  $tag ) . $m[6];
  }
}
var_dump(preg_replace_callback( "/$pattern/s", 'do_shortcode_tag_v2', '[baztag]' ));

Everything I can think of doing suggests that the information in the Codex is wrong on this one. That is simply not how it works.