Opened 12 years ago
Closed 9 years ago
#20509 closed feature request (maybelater)
Theme idea - generic.php
Reported by: | johnjamesjacoby | Owned by: | |
---|---|---|---|
Milestone: | Priority: | normal | |
Severity: | normal | Version: | |
Component: | Themes | Keywords: | needs-patch dev-feedback |
Focuses: | Cc: |
Description (last modified by )
Problem: How do plugins that introduce completely new functionality (I.E. BuddyPress/bbPress) interface with themes, without needing to move template files directly into a theme's folder?
Solution: generic.php
generic.php would be a template file that includes skeletal layout of the theme. In place of the content and the comment logic, is an action; for the sake of discussion, let's name this action 'generic_content'
A concept file is attached for twentyeleven.
This type of ability would help prevent a ton of additional processing that plugins currently need to do to hi-jack the_content output and noop the comment stream, in situations where a plugin needs to output HTML into the main content area of a theme.
Rather than guessing at template names, and hoping 'page.php' or 'index.php' will be close enough, a convention of having themes come with a dedicated template for plugin compatibility would be incredibly helpful.
BuddyPress components have a similar convention already, with a plugin.php having only the header, footer, sidebar, and generic action in them.
The use case is currently small, and there's very little (if anything) needed in WordPress core to make this work. Wanted to post the idea here to get some developer talk going, and get opinions on other possible approaches.
Attachments (3)
Change History (58)
#3
@
12 years ago
I think this could work.
I had a more radical idea: make all the template files go through generic.php (I called it wrapper.php):
#4
follow-up:
↓ 6
@
12 years ago
I wonder if we could do a bit more with this. What I'm thinking of:
add_action( 'parse_request', function($wp) { if ( isset( $wp->query_vars['foo'] ) ) { $wp->is_generic_request = true; } }
This would skip running WP_Query and would load generic.php
, making plugin dev's lives even easier.
#5
follow-up:
↓ 7
@
12 years ago
I'm for it!
You can be sure that I will add just such a template to StartBox the moment this is a supported feature.
@scribu: I really liked your wrapper.php concept when I read about it. As a theme dev I'm right there with you in dislike for repeated code.
#6
in reply to:
↑ 4
@
12 years ago
Replying to scribu:
This would skip running WP_Query and would load
generic.php
, making plugin dev's lives even easier.
I think there's a ticket somewhere in the org universe along those lines; maybe in the BuddyPress trac. Years ago we tried nooping the main query loop, and discovered that WordPress hates the idea in several ways.
Would be worth exploring to optionally avoid the added overhead where it might not be needed.
#7
in reply to:
↑ 5
@
12 years ago
Replying to rzen:
As a theme dev I'm right there with you in dislike for repeated code.
Themes have to balance DRY and convenience, and it's not always easy to do (as I'm sure you know.) Genesis is basically built entirely of hooks and is infinitely flexible, but less easy to grok at a glance because of it.
#17
follow-ups:
↓ 20
↓ 21
@
12 years ago
I like this idea.
I wonder what would be the best way to implement it. It might be nice if plugins could register the path to their bundled generic.php as a fallback file, that way they won't need to mess around with hooking into template_include
and checking for the existance of generic.php in the theme.
Example plugin code:
if ( get_query_var( 'my_plugin_query_var' ) ) register_generic_plugin_file( plugin_dir_path( __FILE__ ) . '/generic.php' );
Then we'd have a function similar to all the other get_*_template()
functions used by the template loader which loads the generic template when is_generic_request() is true:
function get_generic_template() { $templates = array(); $templates[] = 'generic.php'; if ( $fallback = get_generic_plugin_file() ) $templates[] = $fallback; return get_query_template( 'generic', $templates ); }
#20
in reply to:
↑ 17
@
12 years ago
Replying to johnbillion:
I like this idea. (...)
That's exactly the kind of supplemental code I was thinking this would need. Thanks for jumping in.
#21
in reply to:
↑ 17
@
12 years ago
Replying to johnbillion:
It might be nice if plugins could register the path to their bundled generic.php as a fallback file, that way they won't need to mess around with hooking into
template_include
and checking for the existance of generic.php in the theme.
Agreed, would be nice. But, what about a series of default fallbacks too?
Something like:
- theme/generic-plugin.php (eg. generic-bbpress.php will tell bbpress to use this special template)
- theme/generic.php (basic generic template for other plugins)
- the-plugin/generic.php (the plugin that is trying to use generic.php. eg. plugins/bbpress/generic.php)
- theme/index.php (If all else fails, let's throw it inside index.php instead of leaving users with nothing)
etc.
#22
follow-up:
↓ 26
@
12 years ago
+1 on falling back to index.php.
-1 to generic-{plugin}.php
. It's not useful, since the plugin has to tell WP when to load a generic template, so might as well pass whatever path they want.
#23
@
12 years ago
There's no point in having a file such as generic-bbpress.php
because that's not generic. In this case, the theme would use whatever templating hierarchy bbPress provides. The idea of the generic.php
is so a theme can provide a generic template for plugins which it doesn't natively support.
+1 on falling back to index.php
. My code above probably doesn't work as expected, it was just to get the idea down.
#24
@
12 years ago
In other words, the idea is for plugins to be able to define a hierarchy from within generic.php.
#25
@
12 years ago
- Keywords needs-patch added
Actually, that's not the main idea, but it's a possibility:
add_action( 'generic_content', function() { echo '<h1>My Plugin</h1>'; if ( get_query_var( 'my_plugin_list' ) ) $specific_template = 'my-plugin-list.php'; else $specific_template = 'my-plugin-table.php'; locate_template( array( $specific_template, 'my-plugin.php' ), true ); }
I think it's time for someone to write a patch, to move the process along.
#26
in reply to:
↑ 22
@
12 years ago
Replying to scribu:
-1 to
generic-{plugin}.php
. It's not useful, since the plugin has to tell WP when to load a generic template, so might as well pass whatever path they want.
While this will probably be completely different, from experience with BuddyPress (as JJJ mentioned, this idea is somewhat inspired by their plugins.php) not all plugin developers will add additional possible template names (i've seen afew BuddyPress plugins that won't check much further than plugins.php and without filtering (or using conditionals) it's impossible to give it a different look)
Using your example they will do:
add_action( 'generic_content', function() { echo '<h1>My Plugin</h1>'; echo 'my content'; }
Which is why i suggested looking for the existance of a generic-{plugin}.php file to fallback on before ending at index.php.
#27
follow-up:
↓ 33
@
12 years ago
generic-{plugin}.php
would be useful if you wanted to control the appearance around a specific plugin. This conflicts with register_generic_plugin_file()
, which would do the same thing, like it or not.
Speaking of which, register_generic_plugin_file()
seems kind of wrong. The whole point is that the plugin can't know what a generic.php
template should look like.
BuddyPress could still do something like this:
add_filter( 'template_include', function ( $path ) { if ( is_generic_request() && 'index.php' == basename( $path ) ) { // Fall back to plugin.php if there's no generic.php return plugin_dir_path( __FILE__ ) . '/plugin.php'; } return $path; }
Note that this:
// From WP Core load_template( plugin_dir_path( __FILE__ ) . '/my-template.php' );
is different from this:
// From WP Core load_template( locate_template( 'generic.php' ) ); ... // From Plugin add_action( 'generic_content', function() { load_template( plugin_dir_path( __FILE__ ) . '/my-template.php' ); } );
In the second case, generic.php
encloses my-template.php
, making my-template.php a template part, rather than a whole template.
#29
@
12 years ago
My argument is that register_generic_plugin_file()
is a bad idea because it creates confusion.
The assumption should be that, once you've set is_generic_request = true
, the 'generic_content' hook should fire no matter what. It's up to Core to figure out how to do that.
#30
follow-up:
↓ 32
@
12 years ago
Maybe we can have a fallback generic.php template in Core, like we have for sidebar.php.
#31
follow-up:
↓ 40
@
12 years ago
Maybe we can have a fallback generic.php template in Core, like we have for sidebar.php.
I have a feeling that would go against (my understanding of) the purpose of the original idea myself.
The part of the problem that this is trying to solve is that you can't just include get_header() / get_sidebar() and get_footer() into a template file and expect the output to look like the theme, it needs to use the themes markup, As soon as we look at having a core-included "backup" we simply move the problem from plugins into core.
I'd have thought that the best backwards compatible option would be to make it easy for the page template to be used (failing that, the index template) through the same action as the generic template hook uses.. so hopefully the plugin/theme shouldn't have to do anything special at all.
#32
in reply to:
↑ 30
@
12 years ago
Replying to scribu:
Maybe we can have a fallback generic.php template in Core, like we have for sidebar.php.
That was actually going to be my originally suggestion in place of index.php but i wanted to do some homework on the core dev's views on including templates in core (considering their trying to deprecate all the other included templates - seemed like it would go against the goals) before i suggested it.
#33
in reply to:
↑ 27
@
12 years ago
Replying to scribu:
Speaking of which,
register_generic_plugin_file()
seems kind of wrong. The whole point is that the plugin can't know what ageneric.php
template should look like.
I think you're getting confused here. Currently, a plugin such as BuddyPress can contain its own template file for rendering output. Usually, this is going to be a file containing get_header()
and get_footer()
and some content in between.
This doesn't always work with themes that need to markup in the individual template files around the content area (as you said, "The whole point is that the plugin can't know what a generic.php
template should look like."), so the idea is that a theme can include a template file called generic.php
which, most of the time, is going to be pretty similar to page.php
or index.php
, but it'll have a call to do_action('generic_content')
in place of the loop.
This generic.php
file will be at the top of the template hierarchy for generic requests, but plugins will be able to register their bundled generic template file as a fallback for themes which don't have a generic.php
template file. This means that newer themes which include a generic.php
template get more control over the layout of a generic request, and older themes without the template file will fall back to the current system where plugins load their own template file.
BuddyPress could still do something like this:
add_filter( 'template_include', function ( $path ) { if ( is_generic_request() && 'index.php' == basename( $path ) ) { // Fall back to plugin.php if there's no generic.php return plugin_dir_path( __FILE__ ) . '/plugin.php'; } return $path; }
Correct, but that code should be in core instead of in each plugin, and the plugin should just be able to register its generic.php
file to be used in this situation.
// From Plugin add_action( 'generic_content', function() { load_template( plugin_dir_path( __FILE__ ) . '/my-template.php' ); } );In the second case,
generic.php
enclosesmy-template.php
, making my-template.php a template part, rather than a whole template.
It's entirely down to the plugin what it does inside the generic_content
hook. It can include other tempalte parts or output stuff directly. That's not what this ticket is addressing.
#34
@
12 years ago
Attached is a first pass at a patch and a test plugin.
If you visit example.com/?foo=1
then the generic request template will be located and loaded, and the plugin will output 'Hello world!'.
If your theme contains a generic.php
template file (see the earlier attached generic.php
from jjj) then this will be used. If not, then the plugin's bundled template/generic.php
will be used instead.
#35
@
12 years ago
This means that newer themes which include a generic.php template get more control over the layout of a generic request, and older themes without the template file will fall back to the current system where plugins load their own template file.
Yes, and what I'm suggesting is that the fallback be handled by Core, since plugins don't have more information than Core anyway.
I'd have thought that the best backwards compatible option would be to make it easy for the page template to be used (failing that, the index template) through the same action as the generic template hook uses.. so hopefully the plugin/theme shouldn't have to do anything special at all.
Ok, then let's do that.
#36
@
12 years ago
Regarding 20509.patch:
'is_generic_request' shouldn't be a property of WP_Query, since we'll want to skip WP_Query entirely.
#37
follow-up:
↓ 39
@
12 years ago
So, I went ahead and added generic.php to a theme. This is what I ended up with:
#39
in reply to:
↑ 37
@
12 years ago
Replying to scribu:
So, I went ahead and added generic.php to a theme. This is what I ended up with:
This is really neat. I like how simple it is. I have a problem with the semantics, though.
I'd rather we didn't use template_redirect, but instead ride on the shoulders of template_include and pull in the generic.php or plugin-{slug}.php from that point within WordPress core.
If plugins are expected to develop things to work and look like wordPress core, it needs to be just a bit easier to do it the WordPress way.
#40
in reply to:
↑ 31
@
12 years ago
Replying to dd32:
Maybe we can have a fallback generic.php template in Core, like we have for sidebar.php.
I have a feeling that would go against (my understanding of) the purpose of the original idea myself.
The part of the problem that this is trying to solve is that you can't just include get_header() / get_sidebar() and get_footer() into a template file and expect the output to look like the theme, it needs to use the themes markup, As soon as we look at having a core-included "backup" we simply move the problem from plugins into core.
I'd have thought that the best backwards compatible option would be to make it easy for the page template to be used (failing that, the index template) through the same action as the generic template hook uses.. so hopefully the plugin/theme shouldn't have to do anything special at all.
This is the way that bbPress works, and the problem with this approach is having additional markup for pagination, Previous/Next, and comments. It would be great if themes came with the header, sidebar, footer, and surrounding mark-up, without any of the post/page/WordPress specific bits hard-coded into them.
#41
@
12 years ago
I'd rather we didn't use template_redirect, but instead ride on the shoulders of template_include and pull in the generic.php or plugin-{slug}.php from that point within WordPress core.
I can make it use 'template_include' instead of 'template_redirect', but I don't see how that would help. You'd just do some more processing, only to throw it away.
Also, what do you mean by plugin-{slug}.php
?
#42
@
12 years ago
To maybe simplify discussion, what we're trying to implement here is called template inheritance in Django:
Template inheritance allows you to build a base "skeleton" template that contains all the common elements of your site and defines blocks that child templates can override.
https://docs.djangoproject.com/en/1.4/topics/templates/#template-inheritance
And the central idea is that a plugin cannot define a generic skeleton template, so it has to rely on the theme to do that. It can only fill in the content block.
That's why I asked what you meant by plugin-{slug}.php:
- If you mean an additional skeleton template, that defeats the purpose
- If you mean the name of a content block, it would be pretty rigid to only allow a single block per plugin.
#43
@
12 years ago
Let's call the generic.php template defined in the theme a "skeleton template".
In our case, the skeleton template would have a single block, the content block.
So, let's call the partial template files defined in plugins "partial templates".
Now, do you mean to suggest that a plugin should be able to select between multiple skeleton templates from the theme? Obviously, the fallback template would be page.php
I'll stop guessing now. :P
#51
@
12 years ago
Also, what do you mean by plugin-{slug}.php?
I mean plugin-buddypress.php, plugin-bbpress.php, plugin-post-type-switcher.php, etc... Taking the directory name, and using it as a fallback... This allows for themes to target specific plugins opt-in, conditionally. This allows for stuff like:
- BuddyPress gets a full-page width template.
- bbPress gets a forum specific sidebar.
Plugins are responsible for filling in their own content areas, and even the content sidebar area. Common elements should be left alone to the theme and WordPress to sort out (header, footer, container, etc...)
Adding plugin-{slug}.php to the template_include filter is actually pretty easy to do in a plugin already (both bbPress and BuddyPress do this) -- but it would be nice to have a way in core to register a callback that determines what those boundaries are to identify when that template should get used VS some other one, rather than having to build all of it every time.
This is not unlike the way the template-loader.php file works already; just a big if statement that says:
- Let's look for some conditions...
- If that thing happens...
- Load a specific template...
- In a specific expected order...
- Unless we don't have anything...
- Then fallback to index.php.
We could go as far as making it something that WordPress core uses and registers itself. Rather than having a huge if statement, we just register filters in priority order to template_include. That cleans up template-loader.php down to a few lines of code, and introduces router functions to handle the above logic.
I think this is a great idea. Obviously it would be hard to encourage theme developers to all adapt this, but it would make plugins developer's lives much easier for those that did.
If something like this is implemented, it would probably be immensely helpful in convincing developers to include the generic file if all themes on the repo were required to have it.