Make WordPress Core

Opened 14 years ago

Closed 8 years ago

Last modified 6 years ago

#14310 closed enhancement (fixed)

Make template hierarchy filterable

Reported by: scribu's profile scribu Owned by: johnbillion's profile johnbillion
Milestone: 4.7 Priority: normal
Severity: normal Version:
Component: Themes Keywords: needs-patch
Focuses: template Cc:

Description (last modified by scribu)

Currently, we have filters for each template type: home_template, author_template etc.

The trouble is that these filters are applied on the final template path, after the template hierarchy has been traversed.

It would be useful if there was another filter applied to the actual template hierarchy array, before it was sent to locate_template().

Example

Take the author template hierarchy:

author-{nicename}.php > author-{id}.php > author.php

Say I want to add author-{role}.php before author.php.

Sure, I could use the 'author_template' filter:

function author_role_template( $old_template ) {
  // get current author's role

  $new_template = locate_template( array( "author-$role.php" ) );

  if( $new_template && 'author.php' == $old_template )
    return $new_template;

  return $old_template;
}
add_filter('author_template', 'author_role_template');

With an 'author_template_candidates' hook, I could manipulate the actual hierarchy:

function author_role_template( $templates ) {
  // get current author's role

  $new_template = array( "author-$role.php" );

  $templates = array_merge( 
    array_slice( $templates, 0, -1 ), // before
    $new_template,                    // inserted
    array_slice( $templates, -1 )     // after
  );

  return $templates;
}
add_filter('author_template_hierarchy', 'author_role_template');

This would allow me to remove author-{id}.php if I wanted, etc.

Attachments (8)

template_candidates.diff (4.4 KB) - added by scribu 14 years ago.
template_hierarchy.diff (4.4 KB) - added by scribu 14 years ago.
Name the filter *_template_hierarchy
template_hierarchy.2.diff (4.4 KB) - added by scribu 14 years ago.
refresh
14310.diff (428 bytes) - added by nacin 13 years ago.
template_hierarchy.3.diff (428 bytes) - added by scribu 13 years ago.
just the filter
template_hierarchy.4.diff (455 bytes) - added by scribu 11 years ago.
refresh for WP 3.6-alpha
template_hierarchy.5.diff (792 bytes) - added by stevegrunwell 9 years ago.
Refreshed for WordPress 4.3 + includes inline documentation for the new filter.
template_hierarchy.6.diff (1.4 KB) - added by johnbillion 8 years ago.

Download all attachments as: .zip

Change History (93)

#1 @scribu
14 years ago

template_candidates.diff makes all get_*_template() functions call get_query_template(), which consistently applies the appropriate filters.

#3 @scribu
14 years ago

  • Summary changed from Template hierarchy filter to Make template hierarchy filterable

#4 @F J Kaiser
14 years ago

  • Cc 24-7@… added

@scribu
14 years ago

Name the filter *_template_hierarchy

#5 @scribu
14 years ago

  • Description modified (diff)

template_hierarchy.diff just renames the filter *_template_hierarchy, which seems more appropriate.

@scribu
14 years ago

refresh

#6 @scribu
14 years ago

  • Resolution set to fixed
  • Status changed from new to closed

(In [15611]) Apply filters consistently in get_*_template() functions. Fixes #14310

#7 @azizur
13 years ago

  • Cc prodevstudio+wordpress@… added

#8 @scribu
13 years ago

Marked #15141 as dup.

#9 @axwax
13 years ago

  • Cc axwax added

#10 @markjaquith
13 years ago

Nacin and Koopersmith expressed concerns about this. Not necessarily about the idea, but that it didn't get any peer review or iteration. Sort of landed under the radar. get_template_part() went through a lot of revisions, for example. I'd like to give this a chance for more discussion and development before it lands in core.

#11 @markjaquith
13 years ago

(In [17214]) Revert [15611] for 3.1. Needs more time for peer review and iteration. see #14310

#12 @markjaquith
13 years ago

  • Milestone changed from 3.1 to Future Release
  • Resolution fixed deleted
  • Status changed from closed to reopened

#13 @ryan
13 years ago

[17214] broke custom post type templates on wp.com.

#14 @coffee2code
13 years ago

[15611] has been in trunk for four months, which is quite a while. Pulling it during/after RC 2 stage is rather last minute, particularly since it's being reverted more out of principle than due to bugs or lack of merit.

#15 @automattor
13 years ago

(In [17220]) I bungled [17214]. Reverting the revert, so it can be reverted properly! see #14310

#16 @markjaquith
13 years ago

  • Milestone changed from Future Release to 3.1
  • Resolution set to fixed
  • Status changed from reopened to closed

I must have blindly hit "tc" or "mc" in that original revert attempt. It doesn't revert cleanly.

And on second thought, lack of review or not, coffee2code and ryan made a good point: it's been in trunk for a while. Taking it out has already caused problems for people running RC code who wanted to make use of the new filters. Leaving it in. Final answer, Regis.

Sorry for the disturbance on this one. Mea culpa.

#17 @nacin
13 years ago

  • Resolution fixed deleted
  • Status changed from closed to reopened

@nacin
13 years ago

#18 @nacin
13 years ago

Per discussion with Mark and westi, while a full revert is difficult due to code churn, this filter should never have been added to core. Re-opening for consideration.

#19 @westi
13 years ago

I'm not convinced about the suitability of the new hook that was added here and I don't think that this has had enough discussion for us to want to keep this and preserve backwards compatibility in the future.

I would rather that we review the need for this new hook in future and discuss it more openly rather than sneak it in as part of a re-organisation of this code.

#20 @westi
13 years ago

(In [17316]) Remove this new filter as it didn't get enough discussion prior to addition.
Revisit later. See #14310.

#21 @westi
13 years ago

  • Milestone 3.1 deleted
  • Resolution set to fixed
  • Status changed from reopened to closed

We remembered this again whilest discussing #12877 and if we are going to try and give access to the template hierarchy then we need to have a big discussion as to why we want to change these.

#22 @nacin
13 years ago

  • Milestone set to 3.1

#23 @scribu
13 years ago

Fine with me.

#24 @scribu
13 years ago

  • Milestone changed from 3.1 to Future Release
  • Resolution fixed deleted
  • Status changed from closed to reopened

Duplicate: #16994

#25 @scribu
13 years ago

Re-opened because the original goal of this ticket wasn't achieved.

#26 @johnbillion
13 years ago

  • Cc johnbillion@… added

@scribu
13 years ago

just the filter

#27 follow-up: @scribu
13 years ago

  • Keywords dev-feedback added

Could someone explain to me again what's wrong with the "{$type}_template_hierarchy" filter?

Do we plan to drop the hierarchy approach altogether?

Otherwise, I don't see what the big deal is, considering we already have a "{$type}_template" filter on the next line.

#28 in reply to: ↑ 27 @johnbillion
13 years ago

Replying to scribu:

Could someone explain to me again what's wrong with the "{$type}_template_hierarchy" filter?

Anyone? I'd really love to get a filter added to the template hierarchy. Oh, the things I could do with it!

#29 @gruvii
13 years ago

  • Cc gruvii added

#30 @scribu
13 years ago

A similar suggestion: #17788

#31 @scribu
13 years ago

Related: #13239.

#32 @ciobi
12 years ago

  • Cc alex.ciobica@… added

#33 @navjotjsingh
12 years ago

  • Cc navjotjsingh@… added

#34 @eddiemoya
12 years ago

  • Cc eddie.moya+wptrac@… added

#35 @ocean90
12 years ago

  • Cc ocean90 added

Would be useful to extend the single hierarchy, see #18859.

#36 @DrewAPicture
12 years ago

  • Cc xoodrew@… added

#37 @Bueltge
12 years ago

  • Cc frank@… added

#38 @divinethemes
12 years ago

  • Cc divinethemes added

#39 @bainternet
12 years ago

  • Cc admin@… added

#40 @iandunn
12 years ago

  • Cc ian_dunn@… added

#41 @raulillana
12 years ago

  • Cc raulillana added

@scribu
11 years ago

refresh for WP 3.6-alpha

#42 @sc0ttkclark
11 years ago

  • Cc lol@… added

#43 @retlehs
11 years ago

  • Cc retlehs added

#44 @louisremi
11 years ago

I'm trying to recreate the template hierarchy logic on the client side with AngularJS. I want client-side templates to be picked by the Angular router based on current path and naming convention using the same rules that are used by WordPress on the server side.

The easiest way to achieve this is to get the computed array of $templates from the server side whenever the location path changes on the client side, then check which client-side template fits.

This implies that a generic filter inside get_category_template can provide the $templates array corresponding to any $type.
Unfortunately I see no alternative to this filter, and if it doesn't make it in 3.6, I will have to write a plugin that overwrites the content of wp-includes/template.php. I know this is a bad practice but there is simply no other way that I can think of.

Last edited 11 years ago by louisremi (previous) (diff)

#45 @emzo
11 years ago

  • Cc wordpress@… added

#46 @talbet
11 years ago

  • Cc talbet.fulthorpe@… added

#47 @markoheijnen
11 years ago

Is this something we can add in 3.7?

#48 @simonwheatley
11 years ago

Here's a scenario we could use this in for Babble (multilingual plugin):

We'd like to allow theme developers to provide language specific versions of templates, e.g. single-product-fr-fr.php. We can use the "{$type}_template" filter for this, but we need to replicate a lot of code when hooking it to properly handle template hierarchy.

What do we need in order to get some discussion going again, and hopefully get the "{$type}_template_hierarchy" filter into 3.7?

#49 @christianmagill
10 years ago

I too would like to see this implemented. It would allow for implementation of much more modular templating.

#50 @Nabha
9 years ago

We have a multi-site installation and I'd like to get all the blogs running on a single theme with a modified lookup for theme files. I think this would be needed.

For example, instead of just looking in the root theme folder for author.php, I'd like it to look for /blogs/blog-name/author.php as well. This offers a sort of intuitive way for us to override template parts.

I can kind of sort of get there with the existing filter, but not really, because the full array of searched-for templates is no longer available by the time filters are applied in get_query_template().

Last edited 9 years ago by Nabha (previous) (diff)

#51 @stevegrunwell
9 years ago

  • Focuses template added

Well shoot, I was just starting on a patch for this concept and found the ticket. Instead, I'll just have to contribute another use-case: a theme wanting to cascade a custom category design through its child sub-categories.

Assume a site has the given category hierarchy:

  • Art
    • Paintings
    • Sculptures
  • Music
    • Blues
    • Jazz
    • Rock
  • Recipes
    • Sweet
    • Savory

Art, Music, and Recipes may be three distinct categories of the same blog and the site owner wants a distinct template for each section, so he/she creates category-art.php in the theme, and the Art category looks great, until the user clicks into Art > Painting and the theme has fallen back to category.php as a template.

Using the this new filter, the site owner could implement something like the following to permit "Paintings" and "Sculptures" to use category-art.php without limiting the ability to use category-paintings.php down the road.

/**
 * Cascade category-specific templates to child categories.
 *
 * @param array $templates A list of template candidates, in ascending order of priority.
 * @return The filtered list of template candidates.
 */
function mytheme_cascade_category_templates( $templates ) {
	$term_id          = false;
	$templates_before = array();

	// Determine the category ID.
	foreach ( $templates as $template ) {
		$templates_before[] = $template;

		if ( preg_match( '/^category-(\d+)\.php$/i', $template, $matches ) ) {
			$term_id = absint( $matches['1'] );
			break;
		}
	}

	// Proceed normally if we can't find a term ID
	if ( false === $term_id ) {
		return $templates;
	}

	// Get the category ancestors
	$parent_terms = get_terms( 'category', array(
		'orderby'    => 'none',
		'include'    => get_ancestors( $term_id, 'category' ),
		'hide_empty' => false,
		'fields'     => 'id=>slug',
	) );

	// Inject the ancestors into the template candidate array
	$templates_after    = array_diff( $templates, $templates_before );
	$ancestor_templates = array();

	foreach ( $parent_terms as $parent_term_id => $parent_term_slug ) {
		$ancestor_templates[] = sprintf( 'category-%s.php', $parent_term_slug );
		$ancestor_templates[] = sprintf( 'category-%d.php', $parent_term_id );
	}

	return array_merge( $templates_before, $ancestor_templates, $templates_after );
}

add_filter( 'category_template_hierarchy', 'mytheme_cascade_category_templates' );

This (example) code effectively leverages this new filter to change the list of template candidates from:

  1. category-sculptures.php
  2. category-2.php
  3. category.php

To:

  1. category-sculptures.php
  2. category-2.php
  3. category-art.php
  4. category-1.php
  5. category.php

I don't think it would make sense from a backwards compatibility perspective to ever enforce this sort of cascade as default behavior, but this filter lets site owners who *do* wish to introduce this functionality to do so without affecting those who prefer the default functionality.

@stevegrunwell
9 years ago

Refreshed for WordPress 4.3 + includes inline documentation for the new filter.

#52 @helen
9 years ago

#33911 was marked as a duplicate.

#53 follow-up: @johnbillion
8 years ago

  • Milestone changed from Future Release to 4.4
  • Owner set to johnbillion
  • Status changed from reopened to reviewing

Let's see if we can do this for 4.4.

#54 in reply to: ↑ 53 @influencebydesign
8 years ago

Replying to johnbillion:

Let's see if we can do this for 4.4.

Awesome! Been looking forward to working with this one.

#55 @DrewAPicture
8 years ago

@johnbillion What's the status here? Looks like template_hierarchy.5.diff still applies, just need an @since. Is there a consensus on whether or not to go forward with this? What are the up- and down-sides for future maintenance?

#56 @johnbillion
8 years ago

  • Keywords commit added; dev-feedback removed

Recap of discussion in this ticket and elsewhere:

Advantages of introducing this filter:

  • The $template array can be used in a client-side templating app.
  • The $template array can be used in a debugging plugin.
  • Allows language-specific versions of template files in multilingual plugins.
  • Allows control over cascading and priority of template files.

Potential disadvantages of introducing this filter:

  • Allows abuse of control over the priority of template files.
  • Alows template files to be removed, thus removing consistency.

Bear in mind that the template_include and {type}_template filters negate any arguments against such a filter allowing the template hierarchy to be abused. It already can be.

I'm strongly in favour of adding this filter into 4.4. template_hierarchy.6.diff refreshes the patch.

Related tickets which I've been considering holistically along with this:

Going to give this a final discussion in the dev chat this evening.

Last edited 8 years ago by johnbillion (previous) (diff)

#57 @johnbillion
8 years ago

Example use case of a multilingual plugin that wants to allow template files to be overridden on a per-locale basis:

$types = array(
	'404', 'archive', 'attachment', 'author', 'category', 'date', 'frontpage', 'home',
	'index', 'page', 'paged', 'search', 'single', 'singular', 'tag', 'taxonomy',
);

foreach ( $types as $type ) {
	add_filter( "{$type}_template_hierarchy", function( array $templates ) {
		$new = array();
		foreach ( $templates as $template ) {
			$new[] = get_locale() . '/' . $template;
			$new[] = $template;
		}
		return $new;
	} );
}
Last edited 7 years ago by johnbillion (previous) (diff)

#58 @johnbillion
8 years ago

Example of a naïve JavaScript templating app class which wants to be able to access the page template hierarchy:

function __construct() {
	add_filter( 'page_template_hierarchy', array( $this, 'log_page_template_hierarchy' ) );
	add_action( 'wp_footer',               array( $this, 'output_page_template_hierarchy' ) );
}

public function log_page_template_hierarchy( array $templates ) {
	$this->page_templates = $templates;
	return $templates;
}

public function output_page_template_hierarchy() {
	?>
	<script>
	var page_templates = <?php echo wp_json_encode( $this->page_templates ); ?>;
	</script>
	<?php
}

#59 @wonderboymusic
8 years ago

  • Milestone changed from 4.4 to Future Release

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


8 years ago

#61 @influencebydesign
8 years ago

So this isn't going to be in 4.4?

#62 follow-up: @helen
8 years ago

@johnbillion: You are welcome to rescue this before beta 1, but leaving it to you/a committer who's passionate about this topic because this is the kind of thing that needs a commitment to following through with anything that may come up as a result.

#63 @johnbillion
8 years ago

In 35413:

List the possible values for the dynamic portion of the {type}_template hook.

See #14310, #32246

#64 in reply to: ↑ 62 ; follow-up: @influencebydesign
8 years ago

Replying to helen:

@johnbillion: You are welcome to rescue this before beta 1, but leaving it to you/a committer who's passionate about this topic because this is the kind of thing that needs a commitment to following through with anything that may come up as a result.

So why does this ticket seem to have such a problem getting through the process? The last time the Milestone was changed to Future Release was 5 years ago. I'm not familiar with the ins and outs of WordPress's core, but it seems like the idea behind apply_filters is that by default without hooked functions it wouldn't change anything.

#65 in reply to: ↑ 64 ; follow-up: @DrewAPicture
8 years ago

Replying to influencebydesign:

So why does this ticket seem to have such a problem getting through the process? The last time the Milestone was changed to Future Release was 5 years ago. I'm not familiar with the ins and outs of WordPress's core, but it seems like the idea behind apply_filters is that by default without hooked functions it wouldn't change anything.

The problem is that while on the surface this seems like a simple change, it could have far-reaching unintended consequences. Making the template hierarchy broadly filterable would effectively make it possible to completely override expected behavior for how templates work in WordPress. That's a really big deal.

When used as intended it's not a bad idea. But time and again, core has introduced hooks that have been misused and abused, and if enough people are doing the abusing, that sometimes becomes something core has to support.

Part of what makes WordPress so approachable by developers of all levels is that there are certain constants in how it can be expected to work. Therefore, I don't think it's unreasonable to approach a feature request like this with a high level of caution as has been demonstrated here.

#66 in reply to: ↑ 65 @influencebydesign
8 years ago

Thanks for your response. I can see how some filters being used by plugins without the end user’s knowledge could cause issues. At the same time, one of the things that makes WordPress so great is how you can customize it to your needs and change default behaviour to achieve desired outcomes.

The way I see it, I could already completely change the way the template hierarchy works through the template_include filter. If I really wanted to, I could duplicate the logic to determine the array of templates based on the current template hierarchy. However, this is very inefficient since it has already been determined and would require maintenance to keep it in step with future changes. Thus, the real issue becomes not whether the template hierarchy should be changeable, but whether people should have access to what the default template hierarchy should be. Currently you allow people to change it, but they can not do so in an informed manner which I see as even more of an issue.

Replying to DrewAPicture:

Replying to influencebydesign:

So why does this ticket seem to have such a problem getting through the process? The last time the Milestone was changed to Future Release was 5 years ago. I'm not familiar with the ins and outs of WordPress's core, but it seems like the idea behind apply_filters is that by default without hooked functions it wouldn't change anything.

The problem is that while on the surface this seems like a simple change, it could have far-reaching unintended consequences. Making the template hierarchy broadly filterable would effectively make it possible to completely override expected behavior for how templates work in WordPress. That's a really big deal.

When used as intended it's not a bad idea. But time and again, core has introduced hooks that have been misused and abused, and if enough people are doing the abusing, that sometimes becomes something core has to support.

Part of what makes WordPress so approachable by developers of all levels is that there are certain constants in how it can be expected to work. Therefore, I don't think it's unreasonable to approach a feature request like this with a high level of caution as has been demonstrated here.

#67 follow-up: @johnbillion
8 years ago

  • Keywords 4.5-early added; commit removed
  • Status changed from reviewing to accepted

@influencebydesign Frustrating, isn't it? Be assured that I'll address several related issues as a whole in 4.5. See my comment above for more info.

#68 in reply to: ↑ 67 @influencebydesign
8 years ago

@johnbillion Yes indeed! I appreciate your work on this.

Replying to johnbillion:

@influencebydesign Frustrating, isn't it? Be assured that I'll address several related issues as a whole in 4.5. See my comment above for more info.

#69 @influencebydesign
8 years ago

Any movement on this?

#70 @influencebydesign
8 years ago

@johnbillion I'm guessing this won't be addressed in 4.5. Any hope for 4.6?

#71 @swissspidy
8 years ago

  • Keywords 4.5-early removed

#72 @johnbillion
8 years ago

  • Keywords early added

#73 @ocean90
8 years ago

#37443 was marked as a duplicate.

#74 @ocean90
8 years ago

#37443 was marked as a duplicate.

#75 @johnbillion
8 years ago

  • Keywords early removed
  • Milestone changed from Future Release to 4.7

#76 @johnbillion
8 years ago

  • Resolution set to fixed
  • Status changed from accepted to closed

In 38385:

Themes: Make the template hierarchy for a given template type filterable.

This introduces a {$type}_template_hierarchy filter that allows the hierarchy of candidate template filenames for a given template type to be filtered.

This allows the hierarchy to be added to or altered completely without resorting to re-building the hierarchy from scratch within the template_include filter, which is common and prone to conflicts between plugins and prone to getting out of sync with core's hierarchy.

Fixes #14310

#77 @johnbillion
8 years ago

In 38390:

Themes: Begin introducing unit tests for the expected theme template hierarchy. More to come.

See #14310

#78 @johnbillion
8 years ago

  • Keywords needs-patch added; has-patch removed
  • Resolution fixed deleted
  • Status changed from closed to reopened

Re-opening to deal with the inconsistency of frontpage in the filter name instead of front_page.

#79 @johnbillion
8 years ago

In 38403:

Themes: Remove failing tests introduced in [38390].

See #14310

#80 @johnbillion
8 years ago

In 38418:

Themes: Update filter names in the inline documentation for the get_*_template() functions.

See #14310, #37770

#81 @johnbillion
8 years ago

In 38419:

Themes: Introduce tests for the theme template hierarchy.

See #14310

#82 @johnbillion
8 years ago

  • Resolution set to fixed
  • Status changed from reopened to closed

#83 @johnbillion
8 years ago

In 38428:

Themes: Correct the list of possible values for the dynamic portion of the {$type}_template_hierarchy and {$type}_template filters.

See #14310

#84 @SergeyBiryukov
8 years ago

In 38609:

Docs: Use a third-person singular verb for {$type}_template_hierarchy filter added in [38385].

See #14310.

This ticket was mentioned in Slack in #meta by kjennings. View the logs.


6 years ago

Note: See TracTickets for help on using tickets.