Upgrading older plugins that bundle custom templates for BP 1.7
This article examines what you can do as a plugin developer to maintain backwards-compatibility template support, while utilizing the newer template-loading approach as featured in BuddyPress 1.7.
We will walk through a real-life case study involving the BP Follow plugin and what was done to update the plugin for BuddyPress 1.7.
Intro
If you created a BuddyPress plugin that bundles custom template files, chances are you might have followed the bp_example_load_template_filter()
function approach as recommended by the BP Skeleton Component:
https://github.com/r-a-y/buddypress-skeleton-component/blob/master/includes/bp-example-functions.php#L10
If you didn’t follow this approach, you are lucky! You get to stop reading the rest of this article! 🙂
For the rest of you, this approach was fine for versions of BuddyPress previous to 1.7 as BP was only specific to the bp-default theme.
However, if we don’t alter the bp_example_load_template_filter()
function, what it actually does is bypass the universal theme compatibility code, which is the main feature in BuddyPress 1.7.
Obviously, we do not want to do that!
Words can only explain so much, so here are some screenshots of the BP Follow plugin before we dive into the tutorial:
Screenshot of the BP Follow plugin running on BP 1.7 before updating the code on the TwentyTen theme:
Screenshot of the BP Follow plugin running on BP 1.7 after updating the code on the TwentyTen theme:
What we’re going to do is make the BP Follow plugin look like the second screenshot so it will be ready for BuddyPress 1.7.
Let’s get started!
(1) Include some 1.7 template-compatibility code with your plugin
BuddyPress 1.7 comes with some awesome new template-loading functions like bp_get_template_part()
.
However, not everyone will be upgrading to BuddyPress 1.7 immediately, so we need to include some 1.7-compatible code with our plugin to ensure that this new code will work on older BP installs.
A version of this code can be found here:
https://gist.github.com/r-a-y/5119066
Make sure to add it to your plugin’s includes section like so:
if ( ! class_exists( 'BP_Theme_Compat' ) ) { require( 'THE FILE FROM THE LINK ABOVE AND WHATEVER YOU NAMED IT TO' ); }
For reference, BP Follow does this here:
https://github.com/r-a-y/buddypress-followers/blob/1.2.x/bp-follow-core.php#L54
We will utilize some of these functions a little bit later, but let’s get back to that first screenshot from the Intro section.
(2) A closer look at the ‘bp_located_template’ filter
So what is happening in the first screenshot?
If we analyze the bp_follow_load_template_filter() function, we can see that it is similar to the BP Skeleton Component as referenced in the Intro.
What’s happening is BP Follow will always serve its own template:
https://github.com/r-a-y/buddypress-followers/blob/1.1.x/_inc/templates/members/single/following.php
Which is a full page template (using get_header() and get_footer()) and not a template part.
The bp_follow_load_template_filter()
function runs on the 'bp_located_template'
filter, so let’s examine that for a second:
https://buddypress.trac.wordpress.org/browser/tags/1.7-rc1/bp-core/bp-core-catchuri.php#L378
Since our function always returns a template location, we will bypass the universal theme compatibility code that runs on lines 397-411 and our full page template will be loaded.
We don’t want to do this.
So the first thing we do is alter the function to this:
function bp_follow_load_template_filter( $found_template, $templates ) { global $bp; // Only filter the template location when we're on the follow component pages. if ( ! bp_is_current_component( $bp->follow->followers->slug ) && !bp_is_current_component( $bp->follow->following->slug ) ) return $found_template; // $found_template is not empty when the older template files are found in the // parent and child theme // // When the older template files are not found, we use our new template method, // which will act more like a template part. if ( empty( $found_template ) ) { // we will add some code here to add our new templates in the next step } return apply_filters( 'bp_follow_load_template_filter', $found_template ); } add_filter( 'bp_located_template', 'bp_follow_load_template_filter', 10, 2 );
What we’re doing here is checking if $found_template
is empty.
The passed $found_template
variable already tries to find if our full templates:
- /members/single/following.php
- /members/single/followers.php
exist in the parent or child themes because locate_template() is used.
This is great for themes that might have overriden the old templates that came with BP Follow and handles our backwards-compatibility requirement.
If $found_template
is empty, we can start to add some code to add our new template parts!
(3) Get acquainted with the BP Template Stack
The BP Template Stack is a new approach in BP 1.7 to locate templates residing in different directories.
BP handles template stack registration here:
https://buddypress.trac.wordpress.org/browser/tags/1.7-rc1/bp-loader.php#L536
Let’s look at the following lines specifically:
bp_register_template_stack( 'get_stylesheet_directory', 10 ); bp_register_template_stack( 'get_template_directory', 12 );
bp_register_template_stack()
is a function that allows us to register a template directory where BP will attempt to find templates in.
The first parameter is a function callback returning a directory that houses templates; the second parameter tells BuddyPress when to look at this directory.
If we look at the above example, what this means is BuddyPress will look in the stylesheet directory (or child theme’s directory) for templates first since it has a priority of 10. Next, BuddyPress will look for templates in the template directory (or parent theme’s directory) if it could not find it in the child theme since it has a priority of 12.
We can also register our own directory to the stack; this is especially beneficial for 3rd-party components and is exactly what we’re doing in BP Follow:
function bp_follow_load_template_filter( $found_template, $templates ) { global $bp; // Only filter the template location when we're on the follow component pages. if ( ! bp_is_current_component( $bp->follow->followers->slug ) && !bp_is_current_component( $bp->follow->following->slug ) ) return $found_template; // $found_template is not empty when the older template files are found in the // parent and child theme // // When the older template files are not found, we use our new template method, // which will act more like a template part. if ( empty( $found_template ) ) { // register our theme compat directory // // this tells BP to look for templates in our plugin directory last // when the template isn't found in the parent / child theme bp_register_template_stack( 'bp_follow_get_template_directory', 14 ); // we're not done yet! read on to the next step! } return apply_filters( 'bp_follow_load_template_filter', $found_template ); }
Note that 'bp_follow_get_template_directory'
is a real function returning the location where our custom templates are housed:
https://github.com/r-a-y/buddypress-followers/blob/1.2.x/_inc/bp-follow-screens.php#L138
We’re giving it a priority of 14 so it is checked after the stylesheet and template directories.
(4) Setup bp-default compatibility
Now, we have to make sure that we still support the bp-default theme, since a lot of people will still be using that theme.
Don’t forget that bp-default has its own page template for plugins where we can inject our plugin content into:
- /members/single/plugins.php – members component
- /groups/single/plugins.php – groups component
In the BP Follow plugin, since we add new pages to member profiles, we will alter the bp_follow_load_template_filter()
function to look for the 'members/single/plugins.php'
template:
function bp_follow_load_template_filter( $found_template, $templates ) { global $bp; // Only filter the template location when we're on the follow component pages. if ( ! bp_is_current_component( $bp->follow->followers->slug ) && !bp_is_current_component( $bp->follow->following->slug ) ) return $found_template; // $found_template is not empty when the older template files are found in the // parent and child theme // // When the older template files are not found, we use our new template method, // which will act more like a template part. if ( empty( $found_template ) ) { // register our theme compat directory // // this tells BP to look for templates in our plugin directory last // when the template isn't found in the parent / child theme bp_register_template_stack( 'bp_follow_get_template_directory', 14 ); // plugins.php is the preferred template to use, since all we'd need to do is // inject our content into BP // // note: this is only really relevant for bp-default themes as theme compat // will kick in on its own when this template isn't found $found_template = locate_template( 'members/single/plugins.php', false, false ); // we're almost there! let's move on to the next step! } return apply_filters( 'bp_follow_load_template_filter', $found_template ); }
So what did we just do?
We are redeclaring the passed $found_template
variable to look for 'members/single/plugins.php'
. locate_template()
will attempt to find this template in the child and parent theme and will return the location of this template if found.
Since we’re thinking of bp-default users, this template will always be found since that template is bundled with bp-default. Therefore, this template will be loaded by BuddyPress.
If we’re using any other WordPress theme, BuddyPress’ theme compatibility will kick in later since this template will not be found.
Lastly, we need to setup our new template parts so they will be loaded by BuddyPress.
(5) Setup the new template parts
We’re going to inject our new template parts using the "bp_template_content"
hook, which is located in both the bp-default theme and the theme compatibility templates:
- https://buddypress.trac.wordpress.org/browser/tags/1.7-rc1/bp-themes/bp-default/members/single/plugins.php#L57
- https://buddypress.trac.wordpress.org/browser/tags/1.7-rc1/bp-templates/bp-legacy/buddypress/members/single/plugins.php#L29
Our updated bp_follow_load_template_filter()
function looks like this:
https://github.com/r-a-y/buddypress-followers/blob/1.2.x/_inc/bp-follow-screens.php#L60
function bp_follow_load_template_filter( $found_template, $templates ) { global $bp; // Only filter the template location when we're on the follow component pages. if ( ! bp_is_current_component( $bp->follow->followers->slug ) && !bp_is_current_component( $bp->follow->following->slug ) ) return $found_template; // $found_template is not empty when the older template files are found in the // parent and child theme // // When the older template files are not found, we use our new template method, // which will act more like a template part. if ( empty( $found_template ) ) { // register our theme compat directory // // this tells BP to look for templates in our plugin directory last // when the template isn't found in the parent / child theme bp_register_template_stack( 'bp_follow_get_template_directory', 14 ); // plugins.php is the preferred template to use, since all we'd need to do is // inject our content into BP // // note: this is only really relevant for bp-default themes as theme compat // will kick in on its own when this template isn't found $found_template = locate_template( 'members/single/plugins.php', false, false ); // add our hook to inject content into BP // // note the new template name for our template part add_action( 'bp_template_content', create_function( '', " bp_get_template_part( 'members/single/follow' ); " ) ); } return apply_filters( 'bp_follow_load_template_filter', $found_template ); }
So what is happening here?
// add our hook to inject content into BP // // note the new template name for our template part add_action( 'bp_template_content', create_function( '', " bp_get_template_part( 'members/single/follow' ); " ) );
We are adding a hook to the "bp_template_content"
action to load our new template part with the bp_get_template_part()
function that we added from our 1.7 compatibility code in step (1).
—
If you’re wondering what create_function()
is for, it’s just a way to create an anonymous function without having to create a real function. You can still create a real function if you wanted to.
For example, you could do this instead:
add_action( 'bp_template_content', 'my_new_template_part_function' );
And somewhere else, you could have my_new_template_part_function() defined like:
function my_new_template_part_function() { bp_get_template_part( 'members/single/follow' ); );
And the same result would occur. Anyway, back to our regular, scheduled programming!
—
The new template part resides at:
/members/single/follow.php
If you recall, the old templates in BP Follow reside here:
- members/single/following
- members/single/followers
For the new template parts, we cannot use the same location and filename due to backwards-compatibility issues.
Hence, for BP Follow, we simply went with /members/single/follow
since the “Following” and “Followers” screens use the same information.
Notice that /members/single/follow
is a template part and that the templates reside in a subfolder called ‘buddypress’.
The 'buddypress'
subfolder is just another one of those cool BP 1.7 features to help separate custom templates in your theme directory just a little bit better.
For example, if you wanted to override the new template part in your Twenty Twelve theme, you could copy 'buddypress/members/single/follow.php'
to your theme:
/wp-content/themes/twentytwelve/buddypress/members/single/follow.php
Make a few changes and they would override the bundled template part from BP Follow. To find out a little more about this feature, read this codex article.
If your own BuddyPress plugin uses more than one template, you will have to introduce a bit more logic.
Check out the changes to BP-Album, a plugin that recently implemented the same approach listed above:
https://github.com/BP-Media/bp-album/pull/4
Pay special attention to lines 404-417:
https://github.com/BP-Album/bp-album/blob/master/includes/bpa.core.php#L372
Summary
The bp_follow_load_template_filter()
function is now complete! And our second screenshot from step (1) should now show up.
So to summarize, what did we just do?
We:
- Added BP 1.7 theme backpat functions so we can use them in our plugin
- Allow themes that are already using the older, full page templates to continue working as-is
- Change our plugin to use
/members/single/plugins.php
as the template so we can inject template parts into BP without changing the surrounding markup - Re-route older template locations to use our new template parts to avoid conflicts.
The beauty of this entire approach is older versions of BuddyPress (tested down to v1.5) will inherit some of 1.7’s template-loading features as well, so we get to be both backwards-and-future-compatible with the way we utilize templates in our BuddyPress plugin.