WordPress.org

Make WordPress Core

Opened 18 months ago

Last modified 12 days 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 (4)

22355.patch (3.9 KB) - added by johnjamesjacoby 18 months ago.
22355.2.patch (4.0 KB) - added by johnjamesjacoby 18 months ago.
Reverse lookup (name to stack) and break out of both loops
22355.3.patch (4.1 KB) - added by meloniq 13 months 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 6 weeks ago.
Patch from #27322

Download all attachments as: .zip

Change History (52)

johnjamesjacoby18 months ago

comment:1 obenland18 months ago

  • Cc obenland added

comment:2 JustinSainton18 months ago

  • Cc justinsainton@… added

comment:4 curtismchale18 months ago

  • Cc curtis@… added

comment:5 mordauk18 months ago

  • Cc pippin@… added

comment:6 MikeSchinkel18 months ago

  • Cc mike@… added

comment:7 johnjamesjacoby18 months ago

  • Description modified (diff)

comment:8 stephenh198818 months ago

  • Cc contact@… added

comment:9 mercime18 months ago

  • Cc mercijavier@… added

comment:10 follow-up: scribu18 months ago

A usage example from start to finish would be helpful.

johnjamesjacoby18 months ago

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

comment:11 in reply to: ↑ 10 johnjamesjacoby18 months 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.

comment:12 follow-up: scribu18 months 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 18 months ago by scribu (previous) (diff)

comment:13 wpmuguru18 months ago

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

comment:14 mordauk18 months ago

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

comment:15 ryanduff18 months ago

  • Cc ryan@… added

comment:16 in reply to: ↑ 12 johnjamesjacoby18 months 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.

comment:18 mzaweb18 months ago

  • Cc mzaweb added

comment:19 alexvorn218 months ago

  • Cc alexvornoffice@… added

comment:20 DrewAPicture18 months ago

  • Cc xoodrew@… added

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

comment:21 follow-ups: johnbillion18 months 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.

comment:22 maor18 months ago

  • Cc maorhaz@… added

comment:23 in reply to: ↑ 21 johnjamesjacoby18 months 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.

comment:24 jondavis18 months ago

  • Cc jondavis added

comment:25 sabreuse18 months ago

  • Cc sabreuse added

comment:26 DrewAPicture15 months ago

#23180 was marked as a duplicate.

comment:27 kovshenin15 months ago

  • Cc kovshenin added

comment:28 tarasm15 months ago

  • Cc tarasm@… added

comment:29 meloniq15 months ago

  • Cc meloniq@… added

comment:30 in reply to: ↑ 21 chacha10214 months 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.

comment:31 chacha10214 months ago

  • Cc chacha102 added

comment:32 pauldewouters14 months ago

  • Cc pauldewouters@… added

meloniq13 months 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

comment:33 meloniq13 months 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.

comment:34 mindctrl13 months ago

  • Cc dailyrants@… added

comment:35 iandunn12 months ago

  • Cc ian.dunn@… added

comment:36 alex-ye12 months ago

  • Cc nashwan.doaqan@… added

comment:37 chipbennett12 months ago

  • Cc chip@… added

comment:38 emzo11 months ago

  • Cc wordpress@… added

comment:39 sumobi10 months ago

  • Cc sumobi added

comment:40 fjarrett9 months ago

  • Cc fjarrett@… added

comment:41 westonruter9 months ago

  • Cc weston@… added

comment:42 talbet8 months ago

  • Cc talbet.fulthorpe@… added

comment:43 atimmer6 months ago

  • Cc atimmermans@… added

comment:45 nacin6 weeks ago

#27322 was marked as a duplicate.

rmccue6 weeks ago

Patch from #27322

comment:46 rmccue6 weeks 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.

comment:47 casben796 weeks ago

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

comment:48 rmccue12 days 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? :)

Note: See TracTickets for help on using tickets.