Long standing issue with the all-inclusive $content variable in node templates. It keeps base node templates simple (
print $content; 'just works'), but:
- if you want to a more granular layout ("put this field on top, then that image floated left"), you have to manually theme each component individually from the $node raw values. Lots of potential security issues in the hands of themers.
- then, you cannot use $content to say "display the rest floated to the right of the image", because you will get duplicate output for the fields you already displayed. You need eat your field-by-field manual display up to the end, and your templates will miss later node additions.
(Using the word 'field' above in the generic sense - this is true for all node additions. Field API extends the issue to all fieldable entities, though.)
CCK D6 used ugly hacks around the drupal_render() workflow to a) grab the rendered fields and expose them as separate template variables b) (worse) have a way to specify that they should be 'excluded' from the $content variable. This made node themeing a fair bit easier, but the hacks are nonetheless ugly, and they are now in core :-(, and I don't think we whould ship with this. And they only "work" for field additions.
The root of the issue is that whatever a theme wants to do, it comes after $node->content being already rendered by theme.inc's template_preprocess_node(). Nothing can be done of $node->content subparts because #printed = TRUE.
Possible ways out of this:
1) Move the
$variables['content'] = drupal_render($node->content); line out of template_preprocess_node(), and let each theme do it - or something more complex instead, like
$foo = drupal_render($node->content['foo']); $bar = ...; $content = drupal_render($node->content); if they need to, in their own node preprocessor.
Drawback: doesn't play nice with stark, or other CSS-only themes, since it forces themes to implement a preprocessor for each fieldable entity. So, probably a no go :-(
2) bring drupal_render() into the templates. The l(), t() functions are already "allowed" in templates. We might add r() as a shortcut to drupal_render() - or rename the function altogether. You can then do
<?php print r($content['foo']); ?> here,
<?php print r($content['bar']); ?> there, and
<?php print r($content); ?> to get 'the rest'.
- Adds some PHP for themers.
Re: Fairly simple, though, and the base node template would already ship with
<?php print r($content); ?>
- Adds a step of discovery ('what do I have in the $content array... ?').
Re: This is not really different from the current situation ('what 3rd-party-added variables do I have in the template ?'), and doing a print_r($content) is easier than print_r(get_defined_vars())
- Brings raw, unsafe stuff in the hands of themers.
Re: themers already have $node, $field_name (raw field values)... The fact that granular layout requires manually displaying the needed bits forces them do much more dangerous things with raw values right now...
- You have to figure out the render order : r($content['foo']) == '' if r($content) was called first in your template.
Re: right, I see no way around that - that would require moving back to solution 1) (render in preprocess)
At worst, you have to render you 'parts' in advance at the top of the template.
Passed: 11660 passes, 0 fails, 0 exceptions
Failed: 11167 passes, 572 fails, 86 exceptions