This website uses cookies to ensure you get the best possible experience. See our Cookies Policy.

PMG Digital Made for Humans

Flexible WordPress Shortcodes with Locate Template

4 MINUTE READ | March 2, 2016

Flexible WordPress Shortcodes with Locate Template

Underlying the entire WordPress template hierarchy is the locate_template function. locate_template pretty easy one to understand: it checks the child theme for the template, then the parent theme and either returns the template path it found or loads it for you.

locate_template.php

<?phpfunction locate_template($template_names, $load = false, $require_once = true ) {	$located = '';	foreach ( (array) $template_names as $template_name ) {		if ( !$template_name )			continue;		if ( file_exists(STYLESHEETPATH . '/' . $template_name)) {			$located = STYLESHEETPATH . '/' . $template_name;			break;		} elseif ( file_exists(TEMPLATEPATH . '/' . $template_name) ) {			$located = TEMPLATEPATH . '/' . $template_name;			break;		}	}	if ( $load && '' != $located )		load_template( $located, $require_once );	return $located;}
The idea is you pass it an array of template names with the more specific template names at the beginning. For instance, WordPress creates the array ["single-{$post->post_type}.php", "single.php"] when it loads single templates.

What’s a Shortcode Anyway?

Shortcodes are a way to pull blocks of content (or whatever you want) into post content. We’ll make an imaginary shortcode that renders pulls in a little feature box for another post. You might use it like

[pmg_post_snippet id="123"]

and get something back like…

Screen Shot 2016-02-25 at 12.51.55 PM

The code for this is pretty simple. We’ll wrap it up in a class to keep the hooks into init and callbacks for add_shortcode close by.

SnippetShortcode.php

<?phpclass SnippetShortcode{    public function connect()    {        add_action('init', [$this, 'addShortcode']);    }    public function disconnect()    {        remove_action('init', [$this, 'addShortcode']);    }    public function addShortcode()    {        add_shortcode('pmg_post_snippet', [$this, 'showSnippet']);    }    public function showSnippet($atts)    {        global $post;        $atts = shortcode_atts([            'id' => null,        ], $atts, 'pmg_post_snippet');        if (!$atts['id']) {            return;        }        $_post = get_post($atts['id']);        if (!$_post || 'publish' !== $_post->post_status) {            return;        }        $post = $_post;        setup_postdata($post);        ob_start();        ?>        <a class="pmg-post-snippet" style="display: block">            <h2><?php the_title(); ?></h2>            <?php the_excerpt(); ?>        </a>        <?php        $out = ob_get_clean();        wp_reset_postdata();        return $out;    }}

This small example exposes a big weakness to this approach: it’s nearly impossible for an end user to modify the content of the shortcode. Even if that end user will only ever be you, some flexibility is a good thing to build.

A way around this would be to add a filter that lets a user short circuit the display part of the shortcode callback.

SnippetShortcodeFilter.php

<?phpclass SnippetShortcode{    public function showSnippet($atts)    {        // shortcode_atts, get_post, etc all up here        if (false !== $out = apply_filters('pmg_post_snippet_content', false, $_post)) {            return $out;        }                // render as normal    }}

This isn’t bad, but I think there’s a better approach that makes it easy for theme developers to integrate with third party shortcodes and make them look good.

Using Locate Template

Rather than hard coding the HTML into the shortcode, use locate_template to look for a template in the theme to render the shortcode content. Fall back to your own template if one isn’t found in the themes.

SnippetShortcodeLocate.php

<?phpclass SnippetShortcode{    public function showSnippet($atts)    {        global $post;                // shortcode_atts, get_post, etc all up here        $post = $_post;        setup_postdata($post);        $template = locate_template('pmg-post-snippet.php') ?: __DIR__.'/view.php';        ob_start();        self::safeInclude($template, $post);        $out = ob_get_clean();        wp_reset_postdata();        return $out;    }    private static function safeInclude($template, $post)    {        require $template;    }}

Now all a theme developer has to do is drop a pmg-post-snippet.php template in their theme to customize the output. As a nice side effect, it’s a great way to separate the view part of your shortcode from the logic aspect(s) — like checking to see if a post is published, etc.

The only thing of note in the example above is the safeInclude static method that’s used to avoid giving the included template access to the objects $this variable or any of the other variables in the showSnippet method.

Why Not get_template_part?

Because we want to control the context passed to the included template. In the example above, I wanted to give the template access only to the $post variable, not the usual set of the global variables that load_template sets up.

get_template_part also doesn’t allow additional context (other variables) to be passed in. This isn’t a big deal in this example, but becomes more important with complicated shortcodes.

Other Applications

This same pattern works great for hiding re-usable theme components behind a function and when get_template_part won’t suffice for the reasons outlined above.

For example, we wrote a small abstractions around loading a template to render a grid of posts this week for a client.

example.php

Insights meet inbox

Sign up for weekly articles & resources.

<?phpfunction clientname_theme_postgrid(\WP_Query $query, callable $termsCb){    $template = locate_template('parts/frontpage-postgrid.php');    if ($template) {        require $template;    }}
The custom query object meant get_template_part was not going to work without messing with the global $wp_query variable.


Posted by Christopher Davis

Related Content

thumbnail image

Get Informed

PMG Innovation Challenge Inspires New Alli Technology Solutions

4 MINUTES READ | November 2, 2021

Get Informed

Applying Function Options to Domain Entities in Go

11 MINUTES READ | October 21, 2019

thumbnail image

Get Informed

My Experience Teaching Through Jupyter Notebooks

4 MINUTES READ | September 21, 2019

Get Informed

Trading Symfony’s Form Component for Data Transfer Objects

8 MINUTES READ | September 3, 2019

Get Inspired

Working with an Automation Mindset

5 MINUTES READ | August 22, 2019

Get Informed

Parsing Redshift Logs to Understand Data Usage

7 MINUTES READ | May 6, 2019

Get Inspired

3 Tips for Showing Value in the Tech You Build

5 MINUTES READ | April 24, 2019

thumbnail image

Get Informed

Testing React

13 MINUTES READ | March 12, 2019

Get Inspired

Tips for Designing & Testing Software Without a UX Specialist

4 MINUTES READ | March 6, 2019

Get Informed

A Beginner’s Experience with Terraform

4 MINUTES READ | December 20, 2018

BACK TO ALL POST