After rooting around a bit in PHPTemplate, I realized the same mechanism that lets node.tpl.php be overridden by node-page.tpl.php could be used for other callbacks. The attached patch applies this logic to the phptemplate_page() function. It allows the first arg of the current URL to be used to distinguish page templates.

page-admin.tpl.php, page-node.tpl.php, page-forum.tpl.php, and so on could all be used to vary look and feel of the entire page.

This is my first attempt at a patch to PHPTemplate, so I may be going in a terribly flawed direction. It looks like it works, though, and would make the creation of custom administrative pages in particular a LOT simpler for themers.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Dublin Drupaller’s picture

+1 million from me for this idea. nice one Eaton

Dub

Dublin Drupaller’s picture

forgot to mention...I tried the patch and it works perfectly.

Dub

pwolanin’s picture

I like the generality of the approach, since it also means any module that creates a custom path can have a separately themed section.

bradlis7’s picture

Great idea, IMO. I didn't try it, but I don't see any potential problems with the code.

pwolanin’s picture

The patch code looks so obvious I'm surprised it doesn't already work this way!

One question, comparing your patch to the code on this handbook page:

http://drupal.org/node/45951

I'm wondering first if you need to special-case arg(0) == 'node'. Right now, I think your code would look for 'page-node.tpl.php' for every page with URL node/x, no?

eaton’s picture

Yes, it would look for page-node.tpl.php right now. I'm not too concerned about that; it could be used to always show an extra region on node pages, for example.

It differs from that theme-snippet in that it only looks at the first arg, and doesn't allow multiple args to be mapped to a single file (node/edit and admin all going to one file, for example). There's nothing, though, that prevents a themer from using the techniques outlined in that theme snippet to override the template file that will be used. This just makes it easier 'by default' to drop a file in and go, without any additional code on a themer's part.

eaton’s picture

I just realized it would be easy enough to put a front-page special case in there, too. if drupal_is_front_page() returns true, it could look for page-front.tpl.php as well. It wouldn't be as robust as comprehensive solutions like frontpage.module, but again, for most folks it would be More Than Good Enough.

Feature creep? Just Right? I think that could be very useful, too, though I'd really like to see it committed, and the simpler it is the easier it is to do that.

jadwigo’s picture

+1 from me

with this one nobody ever needs to ask for an admin theme

only what will happen when you go to admin/settings or any path with more than one level... if that would work too it would be even cooler

eaton’s picture

Any pages under the /admin menu hierarchy would use the /admin page template. There's nothing stopping us from going even deeper in the hierarchy, but this is a quick and easy approach that doesn't break anything.

Nick Lewis’s picture

Not to play captain grumpy here, but this patch is a demon we should avoid. I have enough problems getting one page.tpl.php right -- the idea of having a seperate page.tpl.php file for every arg(0) would be nightmarish from a theme development perspective. This is a great snippet of code, but it doesn't belong in the engine. People have enough trouble theming as it is, without then having to worry about maintaining 5 seperate page.tpl.php files.

Again, good snippet -- bad and uncessary default for the engine. What's wrong with just using the template.php file? My 2 cents.

Besides, most of the time, all you need to do is change the body ID of the page.tpl.php if you want wildly different looks. Remember?.

pwolanin’s picture

I don't think 5 files are required. If I understand the patch right, page.tpl.php will still be the default if no other temple is found. Thus, the patch will not break existing templeates, only allow a somewhat simpler mechanism for theming admin or other sections for those who care to try.

Nick Lewis’s picture

Well, that may be true, -- actually, wait no, I just tested the code and I was dead wrong. Did I mention I'm tired?

+1 from me

I still think that this method leads to a world of trouble ( with a capitol T )... but that's just my opinion -- and I'm a CSS snob and zealot.

Code works great, makes sense, and gives people more freedom. So I'm all for it.

Crell’s picture

If I'm understanding this correctly... would it make sections.module obsolete? And how could you get the same effect with some other engine, such as Smarty? Or is that even possible?

Dries’s picture

A little bit of topic, but from a user point of view, I always wanted to be able to associate a template with my posts (node pages). Here is how I envision this to wok. A designer makes me several templates; in its simplest form that could mean various templates with different color scheme, or maybe with different images. Then, when I'm writing a post, a drop down menu is presented that lets me pick one of the available templates. So when I'm writing about Drupal, I'd select my 'Drupal template' and when I'm writing about 'Photography' I'd select my photography template, or when it is Christmas, I'd select my Christmas template (regardless of the fact I'm writing about Drupal or Photography). So as a writer, I get to choose the template.

Of course, this isn't what this patch is about but I wonder if this patch will make it harder or easier to accomplish that.

pwolanin’s picture

I haven't tried it, but doesn't the Taxonomy Theme module do just what Dires wants?

I'm not sure where this kind of module injects intself into the process, but it might be worth checking that this patch won't have the potential to break all the existing theme-switching modules.

Dublin Drupaller’s picture

Dries said:

A little bit of topic, but from a user point of view, I always wanted to be able to associate a template with my posts (node pages). ...........Of course, this isn't what this patch is about but I wonder if this patch will make it harder or easier to accomplish that.

I don't think the patch will affect your "select template for this node" idea, Dries.

As I understand it, the way the page.tpl.php or page-admin.tpl.php works is that it loads the Node content (your posts) and places it within the overall page. So the "select template for this node" option would still work. The patch just gives users a very simple option of controlling and managing the overall, full page layouts (not node layouts) by uploading a page-variation.tpl.php file.

The patch just simplifies that in the same way you can upload a node-forum.tpl.php file or a node-image.tpl.php file to automatically override the default node.tpl.php for specific node types.

incidentally, there is a new, very useful module called the Content Template Module by jjef that is very useful for specifying a bespoke node template for content types. I imagine it wouldn't be too difficult to tweak that module to offer the available templates as a drop down list when creating a node. So you can select a "photography" template or "general post" template as opposed to it happening automatically.

hope that makes sense. I think it's a good patch, but, I would wait until a phptemplate developer double checks it. For example, to ensure that the correct variables are still available to the page-variation.tpl.php file.

Dub

eaton’s picture

I'm not sure where this kind of module injects intself into the process, but it might be worth checking that this patch won't have the potential to break all the existing theme-switching modules.

Thought that one through. Those modules work by switching which theme is active. This patch allows a specific theme to use different template files to render the page. Any theme-switching module, like Sections or Taxonomy Theme, will take precedence over this patch.

In regards to another question, all the same variables and context information that go to a normal page.tpl.php will carry over to page-variation.tpl.php. I agree that feedback from one of the PHPTemplate devs is critical here.

eaton’s picture

FileSize
854 bytes

I had a longish conversation with Morbus on #drupal about the potential for more complex alternate theming. After some thought, I whipped out this version of the patch. It moves the filename suggestion into an overridable theme function. By default, it will work just like the previous patch. If someone wants to override it (to use one page template for all /forum urls as well as /node/n urls that point to a 'forum' node-type, for example) they can do that in their theme by overriding phptemplate_page_filename().

Not sure if this approach is better or worse, but it's a possibility.

Anyone out there who feels one way or another? Anyone willing to pop this patch (or the original one) to RTBC?

pwolanin’s picture

In terms of this approach vs. the previous, I'd be curious if there is a way to benchmark the relative performance? I think the most recent patch provides more flexibilty, but that's not worth a noticeable performance hit.

In terms of the last patch, a quick question about the naming of the function phptemplate_page_filename() . Would it be better to name it theme_page_filename()? I'm only making the suggestion since I usually name my theme functions like phptemplate_search_item() so that I can easily move/reuse them among phptemplate-based themes.

eaton’s picture

Title: PHPTemplate: make separate theming of admin pages easier » PHPTemplate: Make theming of front and admin pages simpler
FileSize
856 bytes

After some examination, I think that providing an overridable theme function for this purpose is unecessary. The idea is to provide a very simple way for theme designers to include different page templates with a minimum of work. If someone's already into the code enough to override the suggested-page function, it should be easily just to implement some custom code in page.tpl.php to do dynamic switching using the 'old' method.

This updated version of the patch simplifies things again. It also checks drupal_is_front_page() and points to page-front.tpl.php if it's true. That allows easy alternate front-page theming regardless of what node or internal url one has mapped.

DaveNotik’s picture

Title: PHPTemplate: Make theming of front and admin pages simpler » PHPTemplate: make separate theming of admin pages easier

I wanted to be able to usea different template if it's the front page and the user is not logged in -- i.e. page_default.tpl.php (not logged in) versus page_front.tpl.php (logged in) if I was to avoid my own custom logic altogether (not that I should necessarily be able to, though a dedicated designer would appreciate not having to know the necessary PHP).

With Jeff's latest patch, while I still need logic to check if the user is logged in, I can include that logic in the page-front.tpl.php itself, thus keeping things cleaner.

+1

eaton’s picture

Hmmmm. Hearing your use case has caused me to reconsider the elimination of the helper function, Dave. In your case, while it does allow you to isolate things, it means that you'll need to duplicate layout code and HTML. Your default page template will be duplicated in page-front for the 'logged in' case, with a second custom version for the 'logged out' case.

If the support function were present, you could simply override it to check global $user, and suggest page-anonymous.tpl.php. Thoughts?

pwolanin’s picture

From browsing and reaponding to question in the forums, I thiink many people are overwhelmed trying to get a handle on how to use phptemplate. For this reason, I think using the simple approach is far preferrable. It would be much easier to explain to new users, and would probably capture the functionality needed by 90% or more.

eaton’s picture

Is anyone willing to put this at RTBC? I think I'm satisfied with the state of the patch as it stands, and there's a lot of positive feedback by folks who've actually tested...

eaton’s picture

Title: PHPTemplate: make separate theming of admin pages easier » PHPTemplate: Map menu arg path to page template filename
FileSize
3.42 KB

Annnnd after a longish discussion with chx about additional ways of handling this issue, I present another possible solution.

This version of the patch walks the menu tree of the current page, looking for override files that correspond to the current args. It will fall back to templates of lesser and lesser specificity until it reaches the ultimate default, page.tpl.php. That allows a site to utilize any of the following:

page.tpl.php
page-front.tpl.php
page-admin.tpl.php
page-admin-settings.tpl.php
page-node-edit.tpl.php

And so on. It also modifies phptemplate's core _default callback to support not just ONE suggested file, but an array of them. Previous phptemplate hacks, which used $variables['template_file'] to override the current template file, will still work... That filename is appended to the list of suggestions and is always checked first.

In addtion, $variables['template_files'] (note the plural) can be populated with a replacement array of suggested files for those who want to completely override the menu-arg based system for greater performance or alternate mappings.

It's somewhat more complicated than previous versions, but offers a tremendous amount of flexibility. Testing would be much appreciated.

eaton’s picture

FileSize
3.42 KB

This version of the patch changes only one thing: $variables['template_files'], if it's set, will be added to the list of suggestions rather than replacing it. This wouldn't cause any additional performance hit unless a theme added piles of suggestions without providing actual files to match them.

adrian’s picture

Status: Needs review » Reviewed & tested by the community

I am very much for this.

I was almost on the virge of adding it myself a couple of times.

chx’s picture

it's real nice, indeed.a much needed feature elegantly implemented in only so many lines of code.

pwolanin’s picture

This sounds great- sorry I have not had a chance to test it yet.

Two quick questions- I assume this walks the unaliased URL? For example, what happens if I alias my first forum (/forum/1) to /general-discussions?

Second, I don't know when this would be useful, but would this enable a separate page template for each node? I.e. page-node-100.tpl.php for /node/100?

Oh, wait, I see in the patch the code below for constucting the suggestions., so I guess the answer is no.

  while ($arg = arg($i++)) {
    $suggestions[] = $suggestion . '-' . $arg;
    if (!is_numeric($arg)) {
      $suggestion .= '-' . $arg;
    }
  }
eaton’s picture

Second, I don't know when this would be useful, but would this enable a separate page template for each node? I.e. page-node-100.tpl.php for /node/100?

Actually, it WOULD make that possible. page-node-1.tpl.php is still one of the suggestions, just not page-node-1-edit.tpl.php.

That allows you to create a custom *display* page per node, but not a custom *edit* page. just as an example. It's not perfect, that system, but it is reasonably speedy and doesn't add a lot of overhead.

eaton’s picture

Also, yes, it walks the unaliased arguments, like /node and /taxonomy rather than the paths you've assigned to them. It's possible for a theme's template file to override this, of course -- the $variables['template_files'] array can be populated with any list of suggested files if a site needs to use a different set of defaults and fallbacks.

m3avrck’s picture

Doesn't this need the base_path() before the path_to_theme() in there for compatability with weird setups?

Otherwise, Eaton, what's your address to mail some beer too??? :-D +1 indeed!

chx’s picture

Actually, it WOULD make that possible. page-node-1.tpl.php is still one of the suggestions, just not page-node-1-edit.tpl.php.

Really? I thought that !is_numeric kills that.

eaton’s picture

Nope. The numeric arg IS inserted as a suggestion, but NOT used for the creation of subsequent suggestions. Thus, you get:

page-node.tpl.php
page-node-1.tpl.php
page-node-edit.tpl.php

but not:
page-node-1-edit.tpl.php

That could lead to funkiness if someone used a path that includes numerous numeric args to generate a page (taxonomy/term/1/4 would become page-taxonomy-4.tpl.php), but those cases are relatively rare and special-handling for them would add a lot of code bloat, as well as a massive increase in the number of suggestions.

One possibility (suggested by chx) would be to use a wildcard when constructing subsequent paths. ie:
page-node.tpl.php
page-node-1.tpl.php
page-node-#-edit.tpl.php

In regards to m3avrck's note about base_path, I'm not sure. i used the same path-construction-code that phptemplate already uses to generate the paths themselves; if this code breaks under funky configurations, I believe the old PHPTemplate code would've, too...

drumm’s picture

Status: Reviewed & tested by the community » Needs work

- Update the in-code documentation.
- Don't use += on arrays.

tree’s picture

phptemplate.engine_12.patch appears to contain two typos: $suggetions is used in two places instead of $suggestions

eaton’s picture

Status: Needs work » Needs review
FileSize
4.09 KB

PHPDoc comments updated to reflect the new use of $suggestions, rather than $file. Typos fixed, as well. Thanks!

Dries’s picture

I'd love to hear Adrian's opinion about this patch.

eaton’s picture

Adrian commented earlier, in #27, though I don't think he's seen the minor tweaks that have taken place since then.

Dries’s picture

Why do we need the $extension = '.tpl.php'? Looks like that can be removed/simplified.

eaton’s picture

That default parameter allows modules and themes that directly utilize _phptemplate_callback() to use their own template file extensions. A module might use .widget.php instead of .tpl.php to theme one of its UI elements, for example, distinguishing it from other normal themed .tpl.php files.

Mind you, I don't think anyone actually USES that now.

jadwigo’s picture

*.tpl.php is a way to distinguish template files from other php files.. and *.php effectively hides the contents from unsuspecting directory hackers.

the only problem with it at the moment is the double extension in apache file uploads.

on the other hand, *.install, *.inc and *.module are already used, so maybe we can drop the extra extension anyway and don't worry about the false idea of security that should be handled by apache's .htaccess anyway in this case

shoq’s picture

This entire thread answers a number of questions I had, but I still would like an ID solution for the page such as Nick had suggested above. He wrote:

>

How do I do this? Let's say I want the user's blog edit form to have some custom tweaking only at the CSS level. THe path is

../drupal-4.7.2/?q=node/17/edit

How and where could i transpose this into a parent class that gets inserted into page.tpl.php file like this:

  <td class="main-content MY-class-HERE"     id="content-<?php print $layout ?>">

This second class will give me more than enough control over many thinks in CSS, without having to tamper with the current ID logic.

Somewhere, i have to decide the class, which I could decide with eaton's patch (I think). But then I have to assign it to a template variable which page.tpl can use (and I don't know how do make those either).

Guidance appreciated here.

shoq’s picture

Oops.. forgot what code works here. THe example above should have been

<td class="main-content MYCLASS_HERE" id="content-<?php print $layout ?>">

eaton’s picture

You're looking to alter the CSS class of in a single template file depending on the current location. This patch looks for different template files entirely depending on the current location. You're better off putting a GetCurrentPageClass() function in your template.inc that determines the class for the current page, and calling it directly in your page.tpl.inc file.

shoq’s picture

You lost me. Template.INC? (or php?)
What class am I getting via getcurrentpageClass? There IS no useful class yet.. is there?

What i meant was that I could derive a class name from some aspect of the path with a regex, and then create a page classname for the template.

shoq’s picture

Ok.. you meant I could write my own getcurrentpageclass() function, I assume. and yes, I could, but BASED ON WHAT? I need something generated from the URL, I assume like comments/add, node/edit, q=node/add/page, etc.

These woudl become classes like comments-add, and node-edit.

I would contruct this in a hacked phptemplate engine file and then assign the class to a new template variable that could be easily used in all subsequent template.phps

WOuldn't something like this work?

eaton’s picture

It could, though it's unrelated to this patch.

cklester’s picture

As a newb, I think this is something Drupal desperately needs. I think I should be able to theme a page without having to touch template.php... this patch makes that possible, right? :)

Now, I'm trying to apply the patch but I'm getting an error...

C:\wamp\www>patch -i phptemplate.engine_14.patch -p0
patching file `themes/engines/phptemplate/phptemplate.engine'
Hunk #1 FAILED at 46.
Hunk #2 FAILED at 63.
Hunk #3 succeeded at 215 (offset -4 lines).
Hunk #5 FAILED at 328.
Hunk #6 succeeded at 364 with fuzz 1 (offset -9 lines).
3 out of 6 hunks FAILED -- saving rejects to themes/engines/phptemplate/phptempl
ate.engine.rej

Please help! :)

eaton’s picture

Are you patching against HEAD or 4.7.x? It may have fallen out of sync with head, I'll see if it needs a reroll.

eaton’s picture

Still seems to apply fine on this end -- it sounds like you're applying the patch to an older version of the engine file.

eaton’s picture

*poke poke*

This looks good, and the consensus seems to be positive. Anyone? Anyone?

RobRoy’s picture

*ouch ouch* — +1 — Looks good to me for what it's worth. Great feature. — Rob

rkerr’s picture

Nice solution for a very often-used/requested feature. Would be great to get this committed.

m3avrck’s picture

Status: Needs review » Reviewed & tested by the community

As a self-proclaimed "drupal-theming-ninja" I love this patch.

moshe weitzman’s picture

i played with this. works as advertised. nice patch.

drumm’s picture

Status: Reviewed & tested by the community » Needs work

In the theme('page') section, $suggestions[] = $suggestion; looks like use of an uninitialized array. Please use $suggestions = array(...).

Making the patch with cvs diff -uF^f will make that sort of thing easier to verify.

What is the performance hit for stating one file per path argument?

moshe weitzman’s picture

@drumm - $arr[] = i a perfectly valid way to initialize an array. it seems that many people don't realize that, but the PHPDocs say so: "If $arr doesn't exist yet, it will be created. So this is also an alternative way to specify an array." from http://us3.php.net/manual/en/language.types.array.php. Further, this syntax does not even generate a NOTICE.

I leave this open because of neil's performance question.

eaton’s picture

I tested on a smallish drupal installation with devel.module and page timer installed, and there was no difference in execution times before and after the patch.

If there's anyone who would like to see larger scale stress testing, I can look into setting up something larger, but since the code only executes once per page-load, I'm not sure what difference we'd see. This is the same mechanism that the phptemplate exercises whenever a node is rendered (by looking for "$node->type.tpl.php"). The only real task that has to be performed is parsing arg() and checking the existance of a file -- and only once per page load. Just one or two l() calls have a greater impact on page-load speed.

moshe weitzman’s picture

Status: Needs work » Reviewed & tested by the community

i think eaton addressed the performance question, and i addressed the array initialization question. set to RTBC.

drumm’s picture

Status: Reviewed & tested by the community » Fixed

Committed to HEAD with some code style cleanup.

I did change the array initialization I mentioned earlier. I think it is slightly more expressive because it doesn't double as appending to an array.

tema’s picture

Title: PHPTemplate: Map menu arg path to page template filename » PHPTemplate: $suggestions sometimes is not an array
Category: feature » bug
Status: Fixed » Active

I have "Warning: array_reverse(): The argument should be an array in ...\phptemplate.engine on line 339" (and line 340: foreach())

I've trace it and it IS array when values are "page-something" and "node-something", but it's string when it contains "field-something".

I use HEAD versions of Drupal and CCK.

Thanks!

eaton’s picture

Title: PHPTemplate: $suggestions sometimes is not an array » PHPTemplate: Map menu arg path to page template filename
Status: Active » Fixed

tema, it appears that CCK is one of the only modules around that directly uses that callback -- the appropriate course is to fix CCK to work with HEAD.

tema’s picture

Thank You, Eaton!
I solved this problem by simple adding '(array)' to variable (Line 339):

<?php
// Loop through any suggestions in FIFO order.
$suggestions = array_reverse((array)$suggestions);
?>

So will You please to formulate an issue to CCK project?

Anonymous’s picture

Status: Fixed » Closed (fixed)
smanes’s picture

Version: » 4.7.3

Great minds, etc . I stumbled on this same solution working with a hack to Smarty's template engine. In my case, I wanted the ability to restyle the layout based on the URL. I'm using URL rewriting so that my pages look like:

cms.foo.bar/resume
cms.foo.bar/house
cms.foo.bar/dogs
cms.foo.bar/dogs/auggie
cms.foo.bar/dogs/jack
etc.

I wanted the "/resume", "/house" and "/dogs" pages to have different banners, colors, even different layouts based on the URL. That is, all '/resume' pages could have one look, all '/dogs' pages could have another layout. In addition, I wanted to be able to fine tune the layout to the second and third levels, if necessary, so that each of my dogs' web sites (yes, my dogs have their own web sites -- so shoot me) could each have slightly different layouts, if necessary.

What's nice about the _smarty_callback function is that you can (as noted above) pass it a "suggested" template resource. If it's not available then the software will default to the standard 'page.tpl'. What a nice safety net. You won't generate a Smarty exception if you forgot to create that "suggested" page template.

I haven't built a patch file yet but this mod is pretty easy to insert into smarty.engine with a non-corrupting text editor. Line 223 of the box stock smarty,engine is:

  if ((arg(0) == 'node') && is_numeric(arg(1))) {
    $variables['node'] = node_load(arg(1));
  }

  return _smarty_callback('page', $variables);
}

This is where I added the patch:

  if ((arg(0) == 'node') && is_numeric(arg(1))) {
    $variables['node'] = node_load(arg(1));

    // PATCH(SHM)
    if (isset($variables['node']->path)) {
        $path_elements = split('/',  $variables['node']->path);
        $variables['path_elements'] = $path_elements;
        return _smarty_callback('page', $variables, 'page-' . $path_elements[0]);
    }
    // END PATCH(SHM)
  }

  return _smarty_callback('page', $variables);
}

If the first element of path is /house then Smarty will try to load the template page page-house.tpl in your theme's root directory. Failing that it will load the default page.tpl as usual.

In addition, the path elements are broken out into an array and stuffed into Smarty's variable namespace as 'path_elements' so you can use it to fine tune the page template using Smarty ops, i,e.

$path_elements: array(0 => dogs, 1 => auggie);

Your Smarty template can use this to, for instance, insert a different photo in the banner.

{if isset($path_elements[1])}
 img src="{$path_elements[1]}.jpg">
{else}
....
{/if}