WordPress.org

Make WordPress Core

Opened 3 years ago

Last modified 6 months ago

#22355 new enhancement

Template stack - Beyond parent/child theme relationships

Reported by: johnjamesjacoby Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Themes Keywords: has-patch
Focuses: Cc:

Description (last modified by johnjamesjacoby)

Problem

Robust plugins (BuddyPress, bbPress, et all) are unable to break out of the parent/child restrictions that WordPress imposes on template output. For these plugins to include their own template parts, elaborate code must be written to hook into several different execution points, requiring an intimate knowledge of the inner workings of WordPress's template loading system.


Solution

Create a stack of template locations, and allow WordPress to transverse this array following the same STYLESHEETPATH/TEMPLATEPATH order it always has, while also enabling additional paths to be added with a priority (similar to the filters API.)


Details

The attached patch includes two new functions in wp-includes/template.php:

  • register_template_stack()
  • get_template_stack()

Register template stack is a wrapper for the 'template_stack' filter. get_template_stack() is a variation of apply_filters() that returns the array of filtered template locations.

A modification to wp-settings.php calls register_template_stack() two times, passing get_stylesheet_directory() and get_template_directory() as callbacks, to initialize the core parent/child relationship, ensuring complete backwards compatibility.


Result

This allows for plugins to register additional paths in the template loader hierarchy, and enables plugins that may come with their own default template parts the option of registering a fallback template location.

This works with both locate_template() and get_template_part(), and has the added benefit removing duplicate items inside of get_template_stack(), resulting in avoiding an additional file system check should the parent and child themes be the same.

Attachments (6)

22355.patch (3.9 KB) - added by johnjamesjacoby 3 years ago.
22355.2.patch (4.0 KB) - added by johnjamesjacoby 3 years ago.
Reverse lookup (name to stack) and break out of both loops
22355.3.patch (4.1 KB) - added by meloniq 3 years ago.
Patch according to @scribu and @chacha102 suggestions: uses separate global instead of filters api, pass path instead of callback, added 'remove' and 'has' functions
27322.diff (2.3 KB) - added by rmccue 2 years ago.
Patch from #27322
22355.4.patch (3.4 KB) - added by obenland 19 months ago.
22355.5.patch (1.2 KB) - added by jfarthing84 6 months ago.
Introduce 'template_locations' filter.

Download all attachments as: .zip

Change History (70)

#1 @obenland
3 years ago

  • Cc obenland added

#2 @JustinSainton
3 years ago

  • Cc justinsainton@… added

#4 @curtismchale
3 years ago

  • Cc curtis@… added

#5 @mordauk
3 years ago

  • Cc pippin@… added

#6 @MikeSchinkel
3 years ago

  • Cc mike@… added

#7 @johnjamesjacoby
3 years ago

  • Description modified (diff)

#8 @stephenh1988
3 years ago

  • Cc contact@… added

#9 @mercime
3 years ago

  • Cc mercijavier@… added

#10 follow-up: @scribu
3 years ago

A usage example from start to finish would be helpful.

@johnjamesjacoby
3 years ago

Reverse lookup (name to stack) and break out of both loops

#11 in reply to: ↑ 10 @johnjamesjacoby
3 years ago

Replying to scribu:

A usage example from start to finish would be helpful.

Imagine a plugin registers a custom post type 'foo' and wants to bundle templates for twentytwelve support. This plugin would:

  • Call: register_theme_stack( 'my_plugin_twentytwelve_template_path', 10 );
  • Now, /plugins/my-foo/twentytwelve/single-foo.php is in the stack.
  • locate_template() finds a match in the plugin directory.
  • Profit.

#12 follow-up: @scribu
3 years ago

And you could control the order (parent theme, child theme, plugin) via the priority arg. Got it.

Instead of:

register_template_stack( 'get_stylesheet_directory', 10 );

why couldn't you do:

register_template_stack( get_stylesheet_directory(), 10 );

since the callback just returns a path, which needs to be computed only once.

Last edited 3 years ago by scribu (previous) (diff)

#13 @wpmuguru
3 years ago

+1 - I've used templates located im plugins several times for a variety of reasons.

#14 @mordauk
3 years ago

+1 as well. I've built templating systems into plugins as well and this would dramatically cut down the work load.

#15 @ryanduff
3 years ago

  • Cc ryan@… added

#16 in reply to: ↑ 12 @johnjamesjacoby
3 years ago

Replying to scribu:

why couldn't you do (...) since the callback just returns a path, which needs to be computed only once.

I went the callback route because it came with the filters API; either approach would work.

#18 @mzaweb
3 years ago

  • Cc mzaweb added

#19 @alexvorn2
3 years ago

  • Cc alexvornoffice@… added

#20 @DrewAPicture
3 years ago

  • Cc xoodrew@… added

Pretty neat, and we drop STYLESHEETPATH and TEMPLATEPATH in the process. +1

#21 follow-ups: @johnbillion
3 years ago

I like this idea.

Is there really a need for the register_template_stack() wrapper function? I'd prefer to stick with add_filter(), which is self explanatory.

#22 @maor
3 years ago

  • Cc maorhaz@… added

#23 in reply to: ↑ 21 @johnjamesjacoby
3 years ago

Replying to johnbillion:

I like this idea.

Is there really a need for the register_template_stack() wrapper function? I'd prefer to stick with add_filter(), which is self explanatory.

add_filter() alone would work. The reason to use a dedicated function is to make it API agnostic. Should we want to move it off of the filters API later, we can do so without changing the name of the function or it's parameters.

#24 @jondavis
3 years ago

  • Cc jondavis added

#25 @sabreuse
3 years ago

  • Cc sabreuse added

#26 @DrewAPicture
3 years ago

#23180 was marked as a duplicate.

#27 @kovshenin
3 years ago

  • Cc kovshenin added

#28 @tarasm
3 years ago

  • Cc tarasm@… added

#29 @meloniq
3 years ago

  • Cc meloniq@… added

#30 in reply to: ↑ 21 @chacha102
3 years ago

Replying to johnbillion:

Is there really a need for the register_template_stack() wrapper function? I'd prefer to stick with add_filter(), which is self explanatory.

But it isn't a filter. The values of previous function aren't passed to the next function. If I understand correctly, I don't think anything is passed to the function at all.

Right now the filter API is simply a storage mechanism, which doesn't seem right.

#31 @chacha102
3 years ago

  • Cc chacha102 added

#32 @pauldewouters
3 years ago

  • Cc pauldewouters@… added

@meloniq
3 years ago

Patch according to @scribu and @chacha102 suggestions: uses separate global instead of filters api, pass path instead of callback, added 'remove' and 'has' functions

#33 @meloniq
3 years ago

Above patch uses way of registering locations suggested by @scribu : add_template_stack( get_template_directory(), 12 ); , stop to use filters api as suggested @chacha102 - uses $wp_template_stack global, and adds functions to remove and check for existence of template location.

#34 @mindctrl
3 years ago

  • Cc dailyrants@… added

#35 @iandunn
3 years ago

  • Cc ian.dunn@… added

#36 @alex-ye
3 years ago

  • Cc nashwan.doaqan@… added

#37 @chipbennett
3 years ago

  • Cc chip@… added

#38 @emzo
3 years ago

  • Cc wordpress@… added

#39 @sumobi
3 years ago

  • Cc sumobi added

#40 @fjarrett
3 years ago

  • Cc fjarrett@… added

#41 @westonruter
3 years ago

  • Cc weston@… added

#42 @talbet
3 years ago

  • Cc talbet.fulthorpe@… added

#43 @atimmer
3 years ago

  • Cc atimmermans@… added

#45 @nacin
2 years ago

#27322 was marked as a duplicate.

@rmccue
2 years ago

Patch from #27322

#46 follow-up: @rmccue
2 years ago

Added a simpler patch that handles this without adding a multitude of extra functions. It also adds a theme_url function as per #18302, since it makes a lot of sense to introduce that at the same time.

IMO, there no reason we should store this in a global. Doing it there creates a lot of extra code over the simplicity of just filtering an array.

#47 @casben79
2 years ago

+1 for the patch by rmccue looks like a very neat solution to this and #13239

#48 @rmccue
2 years ago

I'm going to assume we're too late for 3.9 here, but does anyone want to weigh in on the above patch? I'd like to aim for 4.0-early if we're +1 here. nacin seems to like the idea so far, anyone else have thoughts? :)

This ticket was mentioned in IRC in #wordpress-dev by rmccue. View the logs.


19 months ago

@obenland
19 months ago

#50 @obenland
19 months ago

  • Milestone changed from Awaiting Review to 4.1

I really like the idea.

Added an updated patch, where theme_url() lives with other *_url() template tags, wp_template_directories() is get_template_directories() to reflect the nomenclature in template.php, and some added docs.

I wonder if we should restrict get_template_directories() to only allow directories to be added after stylesheet and template directories, not before.

#51 follow-up: @georgestephanis
18 months ago

Related: https://core.trac.wordpress.org/attachment/ticket/18302/18302.14.diff -- there's some discussion on that ticket about the desire for the normal inheriting function, but also one where you can specify you want the parent theme url and it to not be overrideable.

#52 in reply to: ↑ 51 @rmccue
18 months ago

Replying to georgestephanis:

Related: https://core.trac.wordpress.org/attachment/ticket/18302/18302.14.diff -- there's some discussion on that ticket about the desire for the normal inheriting function, but also one where you can specify you want the parent theme url and it to not be overrideable.

I kind of disagree with not having it overridable, but we've also noticed the need to "skip" to the parent. We could possibly add a skip_levels argument, default to 0, where parent theme is $skip_levels = 1

#53 in reply to: ↑ 46 @johnjamesjacoby
18 months ago

Replying to rmccue:

Added a simpler patch that handles this without adding a multitude of extra functions. It also adds a theme_url function as per #18302, since it makes a lot of sense to introduce that at the same time.

IMO, there no reason we should store this in a global. Doing it there creates a lot of extra code over the simplicity of just filtering an array.

If all we want is the ability to short-circuit the template loading process with a filter, there are easier ways to do that than this. The extra functions are important for encouraging plugin and theme libraries to announce to the application that they are introducing a new template directory location, to allow other libraries to interact with it accordingly.


Example: both BuddyPress & bbPress are active, and both introduce their own default template locations. A new theme is activated that wants to unhook the default BuddyPress & bbPress default template locations, and completely own the output experience. If BuddyPress & bbPress are forced to register their locations as callbacks, they are much easier to manipulate. Without registering them, plugin and theme authors will need to guess at what each template location is for, and guess at how to interact with it.

Example: both BuddyPress & bbPress are active, again both introducing default template locations. I write a plugin that includes a brand new set of template parts for handling what BuddyPress Member Profiles look like, with an emphasis on forum topics. In my plugin, I should be able to announce a new template location ahead of BuddyPress & bbPress, so my template parts are located first.

In theory, each template and part could be registered (to avoid a whole mess of file_exists() checks) but that's a lot of additional work for not much gain.


We should try to shy away from function names like get_template_directories(). The theme/template/stylesheet nomenclature is already messy, and it starts to collide with existing function names like register_theme_directory(), search_theme_directories(), etc...

The revised approach in the most recent patches is better than what's in core today, but provides less structure than I originally proposed. I think the _stack naming conventions proposed in my original patch are more clear, and more accurately represent the complex relationships that plugins have with themes than yet-another-filter at the end of the template funnel.

This ticket was mentioned in Slack in #core by georgestephanis. View the logs.


18 months ago

This ticket was mentioned in Slack in #core by georgestephanis. View the logs.


18 months ago

This ticket was mentioned in Slack in #core by jjj. View the logs.


18 months ago

#57 @johnbillion
18 months ago

#30288 was marked as a duplicate.

#58 @johnbillion
18 months ago

  • Milestone changed from 4.1 to Awaiting Review

I don't think this was ever on the cards for 4.1.

We need to consider this along with other tickets such as #14310 and #12877.

This ticket was mentioned in Slack in #buddypress by boone. View the logs.


7 months ago

#60 follow-up: @jfarthing84
6 months ago

I'd be happy with a simple filter on the template locations, myself. Patch incoming.

@jfarthing84
6 months ago

Introduce 'template_locations' filter.

#61 in reply to: ↑ 60 ; follow-up: @johnbillion
6 months ago

Replying to jfarthing84:

I'd be happy with a simple filter on the template locations, myself.

#21062

#62 in reply to: ↑ 61 ; follow-up: @jfarthing84
6 months ago

Replying to johnbillion:

#21062

Not the same. That filters the found template, leaving you to still have to search for yours. My proposal placed a filter on the actual locations that are searched, so that you may add locations, similarly to what's proposed in this issue.

#63 in reply to: ↑ 62 ; follow-up: @johnbillion
6 months ago

Replying to jfarthing84:

Not the same. That filters the found template, leaving you to still have to search for yours. My proposal placed a filter on the actual locations that are searched, so that you may add locations, similarly to what's proposed in this issue.

Sorry, wrong ticket number. Should have been #13239.

#64 in reply to: ↑ 63 @jfarthing84
6 months ago

Replying to johnbillion:

Sorry, wrong ticket number. Should have been #13239.

Eh, still not quite the same. This issue, along with my patch, focus on filtering the locations that are searched for a template, not the actual template name, like the other issues you've mentioned.

Last edited 6 months ago by jfarthing84 (previous) (diff)
Note: See TracTickets for help on using tickets.