Template files are very standard for themes and heavily used in child themes. For example, if a user wants to modify the layout of their site pages, they can simply copy page.php from their parent theme to a child theme, modify the HTML structure, and the changes will be reflected on the site. While it’s well known that template files like page.php can also be built for plugins, I feel most developers tend to avoid building them due to the complexity of building a template loader in a plugin. First I’d like to walk you through the logic of how a template file loader works; second I am going to show you a real example; and third I’m going to show you just how easy building a template loader into your plugin is now.
The concept of a template loader is pretty simple:
- Determine the name of the file to be loaded
- Determine the locations to look for the file and the order in which each should be searched
- Search each location for the file
- Load the file and stop searching as soon as it is found
- Fall back to some default template if the requested file is never found
When building a template loader in a plugin, the developer will usually create one main templates folder (the name can vary) that exists inside the plugin and it will hold all of the available template files. These files can then be copied to a specifically-named folder in the currently active theme in order to be modified. If a file from the plugin’s templates folder exists inside of the theme, it gets loaded instead of the default version from the plugin.
Template file loaders like this are used in a lot of large-scale plugins in order to provide greater flexibility and better control for advanced users that want to tailor a plugin’s output more to their specific needs. A few examples of plugins that use template file loaders are:
- Restrict Content Pro
- Easy Digital Downloads
- WP e-Commerce
- WooCommerce
- JigoShop
- Shopp
- WP Jobs Manager
- bbPress
- buddyPress
Let’s look at a quick example of what a template file loader looks like in a plugin. We will use Restrict Content Pro (RCP) for this example.
RCP has several template files for the registration form, the login form, and the user’s profile editor form. The login form template, as an example, looks like this:
<?php global $rcp_login_form_args; ?> <?php if( ! is_user_logged_in() ) : ?> <?php rcp_show_error_messages( 'login' ); ?> <form id="rcp_login_form" class="rcp_form" method="POST" action="<?php echo esc_url( rcp_get_current_url() ); ?>"> <fieldset class="rcp_login_data"> <p> <label for="rcp_user_Login"><?php _e( 'Username', 'rcp' ); ?></label> <input name="rcp_user_login" id="rcp_user_login" class="required" type="text"/> </p> <p> <label for="rcp_user_pass"><?php _e( 'Password', 'rcp' ); ?></label> <input name="rcp_user_pass" id="rcp_user_pass" class="required" type="password"/> </p> <p> <label for="rcp_user_remember"><?php _e( 'Remember', 'rcp' ); ?></label> <input type="checkbox" name="rcp_user_remember" id="rcp_user_remember" value="1"/> </p> <p class="rcp_lost_password"><a href="<?php echo esc_url( wp_lostpassword_url( rcp_get_current_url() ) ); ?>"><?php _e( 'Lost your password?', 'rcp' ); ?></a></p> <p> <input type="hidden" name="rcp_action" value="login"/> <input type="hidden" name="rcp_redirect" value="<?php echo esc_url( $rcp_login_form_args['redirect'] ); ?>"/> <input type="hidden" name="rcp_login_nonce" value="<?php echo wp_create_nonce( 'rcp-login-nonce' ); ?>"/> <input id="rcp_login_submit" type="submit" value="Login"/> </p> </fieldset> </form> <?php else : ?> <div class="rcp_logged_in"><?php _e( 'You are logged in.', 'rcp' ); ?> <a href="<?php echo wp_logout_url( home_url() ); ?>"><?php _e( 'Logout', 'rcp' ); ?></a></div> <?php endif; ?> |
This file is mostly straight HTML with a little bit of PHP. A pretty standard template file.
Restrict Content Pro loads this file through a short code, and when that happens, it looks like this:
rcp_get_template_part( 'login' ); |
The rcp_get_template_part() takes care of searching each of the possible locations for a file called login.php and then loading the file if it exists.
The rcp_get_template_part() function looks like this:
/** * Retrieves a template part * * @since v1.5 * * Taken from bbPress * * @param string $slug * @param string $name Optional. Default null * * @uses rcp_locate_template() * @uses load_template() * @uses get_template_part() */ function rcp_get_template_part( $slug, $name = null, $load = true ) { // Execute code for this part do_action( 'get_template_part_' . $slug, $slug, $name ); // Setup possible parts $templates = array(); if ( isset( $name ) ) $templates[] = $slug . '-' . $name . '.php'; $templates[] = $slug . '.php'; // Allow template parts to be filtered $templates = apply_filters( 'rcp_get_template_part', $templates, $slug, $name ); // Return the part that is found return rcp_locate_template( $templates, $load, false ); } |
This function does four things:
- Fires a do_action() call so that other plugins can hook into the load process of specific template files.
- Determines the names of the template files to look for.
- Passes the template file names through a filter so other plugins can force it to look for additional or different template files.
- Fires rcp_locate_template(), which performs the actual file lookups.
Before we have a final picture of how this works, we need to look at rcp_locate_template():
/** * Retrieve the name of the highest priority template file that exists. * * Searches in the STYLESHEETPATH before TEMPLATEPATH so that themes which * inherit from a parent theme can just overload one file. If the template is * not found in either of those, it looks in the theme-compat folder last. * * Taken from bbPress * * @since v1.5 * * @param string|array $template_names Template file(s) to search for, in order. * @param bool $load If true the template file will be loaded if it is found. * @param bool $require_once Whether to require_once or require. Default true. * Has no effect if $load is false. * @return string The template filename if one is located. */ function rcp_locate_template( $template_names, $load = false, $require_once = true ) { // No file found yet $located = false; // Try to find a template file foreach ( (array) $template_names as $template_name ) { // Continue if template is empty if ( empty( $template_name ) ) continue; // Trim off any slashes from the template name $template_name = ltrim( $template_name, '/' ); // Check child theme first if ( file_exists( trailingslashit( get_stylesheet_directory() ) . 'rcp/' . $template_name ) ) { $located = trailingslashit( get_stylesheet_directory() ) . 'rcp/' . $template_name; break; // Check parent theme next } elseif ( file_exists( trailingslashit( get_template_directory() ) . 'rcp/' . $template_name ) ) { $located = trailingslashit( get_template_directory() ) . 'rcp/' . $template_name; break; // Check theme compatibility last } elseif ( file_exists( trailingslashit( rcp_get_templates_dir() ) . $template_name ) ) { $located = trailingslashit( rcp_get_templates_dir() ) . $template_name; break; } } if ( ( true == $load ) && ! empty( $located ) ) load_template( $located, $require_once ); return $located; } |
This function takes an array of template file names, such as array( ‘login.php’, ‘login-form.php’ ), loops through the list and searches in each of the possible locations for the files. As soon as it finds a matching file, it does one of two things:
- If the third parameter, $load, is true, the file is loaded with the core WordPress function load_template()
- If $load is false, it returns the absolute path to the file
For Restrict Content Pro, the locations rcp_locate_template() looks are (in this order):
- wp-content/themes/CHILD_THEME/rcp/{filename}
- wp-content/themes/PARENT_THEME/rcp/{filename}
- wp-content/plugins/restrict-content-pro/templates/{filename}
That’s the entire process. While it is not a ton of code, it is something that can be intimidating to write from scratch (note, I took most of my code straight from bbPress, thanks JJJ!). If you are not writing a large plugin, it can be difficult to justify spending a lot of time on a system like this, which leads me into the next part of this tutorial.
Gary Jones recently released a Template Loader class that does most of the heavy lifting for you. The class is based on the template loader built into Easy Digital Downloads (which was based off of the one in bbPress), so it works nearly identically to the methods described above for Restrict Content Pro, except it takes an OOP approach.
I’m not going to walk you through the class, but I am going to show you a quick example of how to use it so that you can easily build template file loaders into your own plugins.
First, you will copy the main Gamajo_Template_Loader class into a new file in your plugin.
Second, you will write a new class that extends Gamajo_Template_Loader, like this:
<?php /** * Template loader for PW Sample Plugin. * * Only need to specify class properties here. * */ class PW_Template_Loader extends Gamajo_Template_Loader { /** * Prefix for filter names. * * @since 1.0.0 * @type string */ protected $filter_prefix = 'pw'; /** * Directory name where custom templates for this plugin should be found in the theme. * * @since 1.0.0 * @type string */ protected $theme_template_directory = 'pw-templates'; /** * Reference to the root directory path of this plugin. * * @since 1.0.0 * @type string */ protected $plugin_directory = PW_SAMPLE_PLUGIN_DIR; } |
This class simply defines the prefix for filters, the name of the directory that files will be searched for in from the current theme, and also the plugin’s own directory.
Third, you include both of these class files into your plugin and instantiate the sub class:
<?php define( 'PW_SAMPLE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); require PW_SAMPLE_PLUGIN_DIR . 'class-gamajo-template-loader.php'; require PW_SAMPLE_PLUGIN_DIR . 'class-pw-template-loader.php'; function pw_sample_shortcode() { $templates = new PW_Template_Loader; // Templates will be loaded here } add_shortcode( 'pw_sample', 'pw_sample_shortcode' ); |
Note, I’m using a simple short code here just to help illustrate how this works. It does not have to be in a short code.
Fourth, create a folder called templates in your plugin’s directory and add your template files to that directory.
Now you can load template files like this:
$templates->get_template_part( $slug, $name ); |
When put in our short code, it looks like this:
function pw_sample_shortcode() { $templates = new PW_Template_Loader; ob_start(); $templates->get_template_part( 'content', 'header' ); $templates->get_template_part( 'content', 'middle' ); $templates->get_template_part( 'content', 'footer' ); return ob_get_clean(); } add_shortcode( 'pw_sample', 'pw_sample_shortcode' ); |
Note that an output buffer is used because short codes, in order to work correctly, must always return their content, and the template loader includes the files directly.
To help demonstrate exactly how to use this class, I’ve written a sample plugin that can be viewed and downloaded from Github.
Having template files available for advanced users of your plugin can dramatically improve the flexibility of your plugin. I would absolutely recommend implementing them in any and all decently large plugins that include output on the front end of the site.
Fantastic tutorial
Thanks for sharing your expertise pal
Great to see there’s an easy class to extend for this, so hats off to Gary Jones.
One thing I like to do as well is make the theme template directory a filterable value. This allows themers to organize all their plugin template overrides in a single, logical place. For example, if your theme is providing support both for WooCommerce and BBPress, you can organize the templates for both inside a templates directory. IE: templates/woocommerce and templates/bbpress
Cheers,
Eric
Eric,
That’s already possible, as part of the existing library. You can filter the array in {filter_prefix}_template_paths to include your own prioritised list of directories to search.
Let’s say you’re using the sample plugin outlines in this tutorial. The plugin author has decided that it should look in:
* /themes/child-theme/pw-templates (priority 1)
* /themes/parent-theme/pw-templates (priority 10)
* /plugins/pw-sample/templates (priority 100).
Since you can filter that array, you could add extra paths in, or even unset the existing ones, so you can define a single place to look e.g. /themes/child-theme/templates/pw-sample.
Thanks for the heads-up on Gary’s class. It’s nice to see a common, shared solution emerge.
One thing I’ve struggled with is how to best expose a plugin’s custom template structure to the end user – particularly the novice dabbler who may not know where to look to find and copy the template files from the plugin. Some large plugins automatically copy the template files from the plugin folder to the active theme folder. Personally, I dislike a plugin tampering with my theme folder, so I would avoid this approach. Has anyone else come up with a good solution to this problem?
I don’t see any reason why you could have the user put the files in a folder in wp-content/. All you’d have to do is adjust the locations the template loader looks in.
I guess my question was more about how to make the user aware of the files so that they can locate them to copy and edit in their own theme. I guess that’s what the Plugins > Editor window is for. I’ve never used the editor panels myself, but maybe the novice users I am worried about make greater use of them.
I think that’s just a matter of documentation.
You wouldn’t want a theme customiser to be using the plugin editor on your plugin. That’s not going to help anyone (it’s evil anyway).
I agree that it’s a matter of documentation. How would you know that WooCommerce or EDD supports templates in a theme? Because their installation / developer documentation covers how to do it.
Sure, documentation is important. I was just wondering if there was a good way to push novice-level information like this into the native WordPress admin experience. But I guess you can only hold their hands so much. A help section under the plugin options is probably the best we can do.
Nice write up! Good to know about the class to. I’ll check that out.
I’m curious about one thing. In `rcp_locate_template()` why not just use `locate_template()` which runs those same checks in that order in one function call? I can’t think of a situation when a plugin developer would need to know if a template override is coming from a child theme or a not-child theme.
By using my own function I can add additional locations for it to look in, as well as add in extra hooks specific to my plugin.
Mark,
Whilst doing my class, I did look at whether some of the methods could better re-use core functions by calling them, instead of duplicating them. I tried (and failed to get working) something along the lines of: “Did locate_template() return anything? If so, use it. If not, try looking in the fallback directory of the plugin instead.”. With more time and eyes, maybe it would work, but duplicating the approach of core functions works for now, instead of coming up with wrapper code.
Forgot to add, that if
locate_template()
had a contextual filter inside of it, so we could directly change where WP looked for template files, then a couple of the methods of my class could be removed in favour of a simple array filter, making the class mostly redundant.Thanks for the replies. All helpful.
FWIW, here’s a bit of generalized code I’m using in one plugin of mine with `locate_template` to allow templating of a widget’s output:
`$template = locate_template( ‘my_plugin_template_folder/layout.php’, false, true );
// Allow themes to override template
if( $template ) {
require( $template );
} else {
require( plugin_dir_path( dirname(__FILE__) ) . ‘my_plugin_template_folder/layout..php’ );
}`
As far as I know, it’s worked for those who need it, but you’re both right that it’s not filterable.
I have done something similar in a plugin I have recently released called the Pico Plugin Framework. I do not like the way that HTML is so often mixed in with other logic in many plugins I see on WordPress.org. In the Pico Plugin framework I have called the ‘templates’ views but the concept is exactly the same really. Separating the presentation code from the rest of the logic in the plugin.
Do you have a link to your Pico Plugin Framework Chris?
It’s in the WordPress plugin repository http://wordpress.org/plugins/fofo-pico-plugin-framework/ or you can download it from my site http://foxdellfolio.com/downloads/. The plugin doesn’t have the coalesce type functionality described above but I’m thinking that could be a nice feature to kind of weave in.
HI All,
I’ve recently bee updating a plugin to use this same template technique too. Wonderful to see it getting more coverage.
What are your thoughts on also providing content available to the templates via an extract() wrapper? (Ie: https://github.com/agentk/issuem/blob/master/issuem-templates.php : issuem_process_render(); )
It then allows clean abstraction using a view class structure, (Ie: https://github.com/agentk/issuem/blob/master/view-classes/issuem-class-article.php)
No more HTML in the code.
Great tutorial
Greta tutorial thanks
I’m trying to load templates for custom post types from a plugin. That’s not what you’re covering here is it? If I understand correctly this is more for templates used to format output from shortcodes and widgets. I see how you use it to do that. But it’s the login page example that’s confusing me. I have for example archive-type.php and single-type.php I want to include the templates from my plugin if they don’t already exist in the theme. Can I do that with this system?
Correct, this is not for post types.
Post type template files are supported by WordPress core out of the box. You can use the template_include hook to load custom template files: http://markjaquith.wordpress.com/2014/02/19/template_redirect-is-not-for-loading-templates/
I think there is a serious problem with this tutorial and it has to do with the Gamajo_Template_Loader class. Your tutorials are very popular and so people are doing what you suggest above and extending that class in their plugin projects. The problem is that it is causing namespace clashes with other plugins that are extending that class.
As a new plugin developer, I learned that hard way when creating my plugin.
https://wordpress.org/plugins/gps-tracker/
What I had to do was to rename that class and then everything worked fine. I don’t know if that’s the best way to solve to problem but please mention this in your tutorial. It caused quite a bit of gnashing of teeth while I was trying to troubleshoot this problem.
thanks
Nick
Hey Nick!
To prevent that kind of conflict, you just need to include a class_exists() check in your plugin prior to loading the template file class. That way if another plugin has already loaded it, you don’t attempt to load it again.
Thank you, Pippin! Everything works great. As usual, you are most helpful.
Just a little detail please on if I wanted to use this just for my plugin WITHOUT using a short code?
So maybe I am missing the point and since this may just be a hole in my understanding, say I just want to include a template part from my plugin/templates directory inside a page template, also loaded from my plugin/templates folder
something like:
Thanks,
-John
It would be done exactly the same way. Nothing in this restricts it to shortcodes.
“It would be done exactly the same way. Nothing in this restricts it to short codes.”
Yes I realize it’s not restricted as you mentioned in the article.
Well I would expect not “exactly” the same as first of all, you are not using short codes 🙂
I was wondering what an example not using them might look like.
Thanks and regards,
-John
Anything that is displayed on the frontend can benefit from having a template file that is loaded through a loader.
“Anything that is displayed on the frontend can benefit from having a template file that is loaded through a loader.”
I agree and I would love to do it that way.
Example please?
Regards,
-John
Comment forms, widgets, data added to the bottom of posts, signup forms, ecommerce checkout screens.
I’m not sure if those are the kind of examples you’re looking for. If not, could you provide me something more to go off of so I can try and answer your question better?
Sorry for not replying sooner as I have been traveling.
What I am looking for is a specific example using code, of including a template part but not using a shortcode, in a template file.
Maybe a category template for example?
Thanks,
-John
Here’s an example: https://github.com/pippinsplugins/Restrict-Content-Pro/blob/master/includes/member-forms.php#L23
It really isn’t any different at all. The template file is loaded the same way.
Hi Pippin,
From the example and what I saw in the Restrict Content Pro (thanks so much for posting it on GitHub btw) is that you are only parts of content. Can you load the full template like you would in a theme with get_header() and get_footer() using Gary’s solution? I’m thinking like they do in woocommerce archive-product.php where they have get_header( ‘shop’ );
I would like my plugin to do that, like have custom get_header and footer in the template file. I know you can’t call them in the plugin and has to be in the theme but that’s ok.
Hi again,
I’ve been mucking around now with the template loader class and seems to work with what I want… I’m not using shortcode, instead loading the template via the template_include filter for that specific page. I’m guessing this is the way to go about it.
Nooo, actually not working… getting the themes headers/footers 🙁
You could use it for complete template files as well, though that’s a bit trickier to do from a plugin. You will need to overwrite the theme calls to get_header(), get_footer(), etc.
I would recommend looking at how WooCommerce does it if you want to do that.
Hi Pippin,
I had been working on it, but couldn’t manage.
So decided to do this to load the dashboard template:
public function load_dashboard_template($template) {
// Get page id of the dashboard page from the values stored in the dashboard options
$optionValue = get_option(‘tdto_dashboard_page_id’);
global $wp;
// Check if on dashboard page first
if(is_page($optionValue)){
// See if user is both logged in and can create tasks
if(is_user_logged_in() && current_user_can( ‘edit_task’ )){
$template_name = ‘dashboard.php’;
// Check if template is in the themes folder, if not, load the one in plugin
if (file_exists(TEMPLATEPATH . ‘/tdto/’ . $template_name)) {
return TEMPLATEPATH . ‘/tdto/’ . $template_name;
} else {
return dirname( __FILE__ ) . ‘/templates/’ .$template_name;
}
}else{
// Please get the hell away then
wp_redirect(home_url());
exit;
}
}
return $template;
}
Then I have a bunch of smaller sections that I can insert using your method…as template parts.
I think woocommerce doesn’t load get_header(shop). I think they just did that so you can use it in theme, coz I didn’t find any shop header files anywhere… :/
Is there any way to pass shortcode attributes or other information to the template files?
I need to access some of the attribbutes in the layout but can’t see if that’s possible by extending the class?
Not directly, but you can add the attributes to a global variable and then access it that way. We do this in EDD:
https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/includes/shortcodes.php#L707
https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/templates/shortcode-receipt.php#L5
Awesome, thanks!
It is completely useless and written by a person who does not even know what he is talking about. More than a tutorial is an article that advertises paid plugins.
There is no need to read it and it is not worth the time to waste.
What are you talking about? The only links I see to paid plugins are the links at the start of the article which mentions plugins that use template loaders. Did you read any further than that?
The code is in the article and also on github and it works perfectly fine.
I’ve been using a similar technique over years. But it’s time to use a well-maintained class for this purpose. The Gamajo Template Loader is recently updated with ability to load custom data for template files, which is a great feature for developers.
” The production of capital goods, intermediate goods and consumer goods all showed decreases. It helps take away some of the financialresponsibility from the patient. Most of them are known with writer block and this could be upsetting to folks writing the dissertation. Have a look at many clearance items for these are a lot better deals as compared to the regular ones. The report then estimates 2018-2023 market development trends of Vacation Rental Software industry. Being a one stop solution for research requirements, our Market Intelligence reports help our clients to keep abreast of thousands of industries all-round the globe. 17 in the three cities in Hebei, which neighbors Beijing. These kiln controllers are certainly suited for commercial purposes. Through the statistical analysis, the report depicts the global total market of Vacation Rental Software industry including capacity, production, production value, cost/profit, supply/demand. Just before you knew it, merchandises that you have ordered are already at your doorstep just awaiting you to collect it.Water pipe blades, the most effective creations for birdwatcher pipe cutting inside small places without having to use a hacksaw.A person going to get their teeth clean and has alot of tarter buildup understands the discomfort that it causes.”