Mauro Bringolf

Currently geeking out as WordPress developer at WebKinder and student of computer science at ETH.

How to output markup from a shortcode and some thoughts on WordPress templating

June 6, 2017
, ,

Separation of concerns currently seems to be all over the place on the web. More and more front-end frameworks move into the direction of separating components instead of technologies. People are experimenting with all kinds of variations of this idea which I think is great. However, working with WordPress also exposes me to an environment with certain constraints. I cannot ignore the given framework when writing a page template within a theme (unless we are talking about decoupled front-end applications, then anything is possible). Templates is usually the first point where things start to get ugly with WordPress. It takes some of discipline to separate the view from the rest and not end up with some data formatting computations right inside the HTML. Nobody forces us to strive for this separation, it is really our own responsibility.

Let’s assume we set up a nice way of passing data into our lean and simple page templates. Great. I currently believe that apart from escaping functions 1, the only PHP inside a template file should be if, foreach and ternary operators. Let’s assume we also have that (or some other convention on templating) in place and are happy with it. How do we do shortcodes now?

Shortcodes are a mess

A shortcode is added by registering a callback function to the corresponding string tag 2:

// '[just-another-shortcode]'
add_shortcode( 'just-another-shortcode', 'just_another_shortcode' );

function just_another_shortcode() {
  return 'Just another shortcode';
}

The reason I am bringing this up is because the callback function should return the shortcode content, but not output any of it. Producing output from the callback function can result in wrong placement and removed filtering of content. But most shortcodes I have implemented so far had to produce some markup for the front-end. Here is how I learned it from tutorials and used to do it for a long time:

// '[all-post-titles]'
add_shortcode( 'all-post-titles', 'all_post_titles' );

function all_post_titles() {

  $posts_query  = new WP_Query(  
    array (  
      'post_type'      => 'post',  
      'posts_per_page' => -1,
      'fields'         => 'ids',
    )
  );

  $html = '<ul>';
  
  foreach( $posts_query->posts as $post_id ) {
    $html .= '<li>' . get_the_title( $post_id ) . '</li>';
  }

  $html .= '</ul>';
  return $html;
}

Shortcodes can have clean templates too

And I am not going to reason why, but passing around HTML markup as a string is not something I think we should be doing. To me, this is just a complete mess and I do not feel comfortable working with this code. After all it is inconsistent, now that we assume we have good templating guidelines in place. Wouldn’t it be nice if we could use exactly the same way of templating for shortcodes as well? It certainly would, and can easily be done. I just wish some of the early WordPress development tutorials I learned from would have told me this.

functions.php

// '[all-post-titles]'
add_shortcode( 'all-post-titles', 'all_post_titles' );

function all_post_titles() {

  $post_ids = (new WP_Query(  
    array (  
      'post_type'      => 'post',  
      'posts_per_page' => -1,
      'fields'         => 'ids',
    )
  ))->posts;

  ob_start();
  include 'all-post-titles.php';
  return ob_get_clean();
}

all-post-titles.php

<ul>
  <?php foreach( $post_ids as $pid) : ?>
    <li><?php echo get_the_title( $pid); ?>
  <?php endforeach; ?>
<ul>

This uses an output buffer which is included in PHP 3. It catches all the output generated in between ob_start() and ob_get_clean(). Working with this kind of templating structure has proven to be way easier for me in the past. I would like to remark that in the example above, all-post-titles.php has a strong dependency on the variable $post_titles variable being present in the current scope. I consider this as bad design and usually wrap the template in a render function where all data has to be explicitly passed in. Using this approach has the additional benefit that template parts can be reused across shortcodes and page templates. I did not realize it until recently, but I think I took this idea from React: Pass in data to a pure render function that returns (outputs) the corresponding UI. This is my current approach to shortcode templates:

// functions.php
add_shortcode( 'some-shortcode', 'some_shortcode' );

function some_shortcode() {
  // prepare and format data
  $word_list = array( 'load', 'from', 'database' );

  include_once 'some-shortcode.php';
  ob_start();
  render_some_shortcode( $word_list );
  return ob_get_clean();
}

// some-shortcode.php
function render_some_shortcode( $word_list ) {
  ?>

  <ul>
    <?php foreach( $word_list as $word ) : ?>
      <li><?php echo esc_html( $word ); ?>
    <?php endforeach; ?>
  </ul>

  <?php
}

References

  1. http://beta.maurobringolf.ch/2017/01/more-secure-wordpress-code-by-validating-sanitizing-and-escaping-data/
  2. https://developer.wordpress.org/reference/functions/add_shortcode/
  3. http://php.net/manual/en/function.ob-start.php