How does hook_theme() work? - drupal

I am having a hard time understanding what hook_theme() does.
My understanding is that it has something to do with making it possible to override templates.
I was looking at:
$theme_hooks = array(
'poll_vote' => array(
'template' => 'poll-vote',
'render element' => 'form',
),
'poll_choices' => array(
'render element' => 'form',
),
'poll_results' => array(
'template' => 'poll-results',
'variables' => array('raw_title' => NULL, 'results' => NULL, 'votes' => NULL, 'raw_links' => NULL, 'block' => NULL, 'nid' => NULL, 'vote' => NULL),
),
'poll_bar' => array(
'template' => 'poll-bar',
'variables' => array('title' => NULL, 'votes' => NULL, 'total_votes' => NULL, 'vote' => NULL, 'block' => NULL),
),
);
Could you provide an example of how it works?

It provides a place for a module to define its themes, which can then be overridden by any other module/theme. It will also provide the opportunity for any module to use a hook such as mymodule_preprocess_theme_name to change the variables passed to the eventual theme function or template file.
There are basically two ways to initialise a theme function:
theme('poll_results', array('raw_title' => 'title', 'results' => $results, etc...));
and
$build = array(
'#theme' => 'poll_results',
'#raw_title' => 'title',
'#results' => $results,
etc...
); // Note the '#' at the beginning of the argument name, this tells Drupal's `render` function that this is an argument, not a child element that needs to be rendered.
$content = render($build); // Exact equivalent of calling the previous example now that you have a render array.
Please keep in mind, you should avoid calling theme() directly (per the documentation in theme.inc) since it:
Circumvents caching.
Circumvents defaults of types defined in hook_element_info(), including attached assets
Circumvents the pre_render and post_render stages.
Circumvents JavaScript states information.
In Drupal 8, theme() is a private function, _theme(). For more detail, please see www.drupal.org/node/2173655.
When you compare the two of these to the poll_results element in the example you give above you can probably work out what's happening...since PHP is not a strongly typed language Drupal is providing 'named arguments' through either a keyed array passed to the theme function, or as hashed keys in a render array.
As far as 'render element' is concerned, this basically tells the theme system that this theme function will be called using a render array, with one named argument (in this case form). The code would look something like this:
$build = array(
'#theme' => 'poll_choices',
'#form' => $form
);
This will pass whatever's in the $form variable to the theme function as it's sole argument.
Regarding the template key:
'poll_vote' => array(
'template' => 'poll-vote',
'render element' => 'form',
)
defines a theme called poll_vote which uses a template file (hence the template key) with a name of 'poll-vote.tpl.php' (this is by convention). The path to that template file will be found by using the path to the module that implements it (e.g. modules/poll/poll-vote.tpl.php), so it's fine to put template files in sub-folders of the main module folder.
There are two ways to actually return the output for a theme function, by implementing the physical function name (in this case it would be theme_poll_vote) or by using a template file. If the template key is empty Drupal will assume you've implemented a physical function and will try to call it.
Template files are preferable if you have a fair bit of HTML to output for a theme, or you simply don't like writing HTML in strings inside PHP (personally I don't). In either case though, the variables passed when you call the theme (either using theme() or a render array as described above) are themselves passed through to the template file or theme function. So:
function theme_poll_results(&$vars) {
$raw_title = $vars['raw_title'];
$results = $vars['results'];
// etc...
}
If you were using a template file instead for the same method the variables would be available as $raw_title, $results, etc, as Drupal runs extract on the $vars before parsing the template file.
I'm sure there's a lot I've missed out here but if you have any more specific questions ask away and I'll try to help out.

Drupal 6
I was stuck all day with this and now successfully implemented, so sharing my finding here, may it will help understand hook_theme.
There are 3 steps involved:
hook_theme
function YOURMODULENAME_theme() {
return array(
'xxx_xxx' => array(
'template' => 'xxx-xxx', // define xxx-xxx.tpl.php inside module
'arguments' => array('xxx' => null), //define $xxx so it will available in your xxx-xxx.tpl.php
),
);
}
echo/return the theme in your .tpl or any .module
$output = theme('xxx_xxx', $xxx);
Now variable are magically available in you xxx-xxx.tpl.php.
<?php echo $xxx ?>
Note: you can pass $xxx as array,object or anything :)

There is yet another way: (can be found in Bartik theme)
The scenario here is that we have created our own module and want to override the default output for let's say a node with title 'zzz' only.We don't know and don't really care how the default output is generated. All we need is to tell Drupal to use our own custom template file (node--custom--name.tpl.php) to render that specific node.
These are the steps:
Tell Drupal where our template file lives. (Keep in mind that this function will take effect only once and after clearing Drupal's cache):
// Implements hook_theme()
function mymodulename_theme() {
$theme = array();
$theme['node__custom__name'] = array(
'render element' => 'node',
'template' => 'path_from_mymodule_root/node__custom__name',
);
return $theme;
}
Tell Drupal where and when to use it
//Implements hook_preprocess_node()
function mymodulename_preprocess_node($vars) {
if($vars['node']->title == 'zzzz') {
$vars['theme_hook_suggestions'][] = 'node__custom__name';
... your other code ...
}
}
Now Drupal will use our template file for that specific case only, provided that 'node--custom--name.tpl.php' file is in the declared path, otherwise it will keep searching according to the suggestions naming conventions for a fallback template.

Related

Drupal 8 Twig - custom block - two twig templates working third is not?

I have a custom module modero_kbo that creates a custom block.
I need to display this block differently depending on where it is placed on my page.
I have this function in my modero_kbo.module:
function modero_kbo_theme() {
return array(
'modero_kbo_vat' => array(
'variables' => array(
'form' => NULL
)
),
'modero_kbo__landing_page' => array(
'variables' => array(
'form' => NULL
)
),
'modero_kbo__landing_page__modero_kbo_form_2.html.twig' => array(
'variables' => array(
'form' => NULL
)
),
);
}
And this in my custom theme .theme file:
/**
* Implements hook_theme_suggestions_HOOK_alter() for modero_kbo.html.twig.
*/
function moderosolid_theme_suggestions_modero_kbo_vat_alter(array &$suggestions, array $variables) {
if($node = \Drupal::routeMatch()->getParameter('node')){
$suggestions[] = 'modero_kbo__' . $node->bundle();
$suggestions[] = 'modero_kbo__' . $node->bundle() . '__' . $variables['form']['#attributes']['data-drupal-selector'];
}
}
All 3 template suggestions are showing up in my html source on the page.
The first two actually work, the third one is not working.
I've tripple checked all the file names and spelling.
I have 3 different template files, the first two are working, the third one is showing in the suggestions list, but is not used for some reason?
modero-kbo-vat.html.twig
modero-kbo--landing-page.html.twig
modero-kbo--landing-page--modero-kbo-form-2.html.twig
One error we found here is that I should only be using the first array in the modero_kbo_theme() function.
The moderosolid_theme_suggestions_modero_kbo_vat_alter alters that theme.
We could not figure out why the 3rd hook was not working, we suspect that the form variables might not be available at some point in the process.
I solved this by copying the block and creating a new block with a custom template.

Why should I use theme() function in page.tpl.php?

Im working on my first site in drupal and I have also read some basics of theming and module development. Now I am creating (overriding stark theme) my own theme, i.e. page.tpl.php and there is an theme() function called for outputting main menu items:
<?php print theme('links__system_main_menu', array('links' => $main_menu, 'attributes' => array('id' => 'main-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Main menu'))); ?>
I roughly understand what this function is for, but why should I use it in this case? It would make sense if outputting data from module - to stylize that output by selected theme. But in this case everything I need is directly in $main_menu array and I can stylize it however I want, so what's the use for theme() function in page.tpl.php?
Why should I use theme() function in page.tpl.php?
You shouldn't.
To avoid calling theme() function in your page template, you can do this in your template.php:
/**
* Implements hook_preprocess_page().
*/
function yourtheme_preprocess_page(&$vars) {
$vars['main_menu'] = theme('links__system_main_menu', array('links' => $vars['main_menu'], 'attributes' => array('id' => 'main-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Main menu')));
}
And then simply print $main_menu; in your page.tpl.php.
The point of using theme('links__system_main_menu', ...) is to re-use the existing generic links theme implementation of the theme. The system_main_menu suffix (after the two _), allow you to provide a more specific implementation of the generic links. If you then override the page template for the front page (ie. page-front.tpl.php) and for nodes of a specific content, you don't have to duplicate your HTML code. Which make it easier to maintain since you won't have to duplicate changes to multiple files.
The point is that if you go trough drupal's theme system it gives a chance to other installed modules to do their changes - their hook functions will be called:
https://www.drupal.org/node/933976
In other words, if you don't do it "clean" way it may happen that some other's module feature won't work.

Output a "advanced-custom-fields"-field programmatically in wordpress backend

I created a custom field with the "advanced-custom-fields"-plugin. Now I want to get and output the custom field programmatically in my template file (backend, edit page), because my template is called via ajax if user want's to add a new region to the page.
Is there any function which returns the complete field? I only found functions which gave me values, but not the field as "form".
I found a solution. I duplicated the plugin folder into my theme root directory and put the following code into my functions.php:
function relationshipField() {
$newField = new acf_field_relationship();
$field = array(
'post_type' => array('post'),
'max' => '',
'taxonomy' => array('all'),
'filters' => array('search'),
'result_elements' => array('post_title', 'post_type'),
'return_format' => 'object'
);
return $newField->create_field($field);
}
In addition I append a custom input field (created in function.php) which stores only the post id's in database.

Add js to a template file using theme preprocess

I have a custom Drupal 7 module which is hooking into the media module and I'm having trouble with adding some js into its render.
The module is defining a theme like this:
function media_flash_theme($existing, $type, $theme, $path) {
return array(
'media_flash_video' => array(
'variables' => array('uri' => NULL, 'options' => array()),
'file' => 'media_flash.theme.inc',
'path' => $path . '/includes/themes',
'template' => 'media-flash',
)
);
}
And my formatter (view) is returning my element like so:
$element = array(
'#theme' => 'media_flash_video',
'#uri' => $file->uri,
'#options' => array(),
);
return $element;
Now, I have a preprocess function which adds some js:
function media_flash_preprocess_media_flash_video(&$variables) {
$path = libraries_get_path('swfobject');
drupal_add_js($path . '/swfobject.js');
...
}
And also a drupal add js in my template file:
/**
* #file media_flash/includes/themes/media-flash.tpl.php
*/
$javascript = '
(function ($) {
...
})(jQuery);
';
drupal_add_js($javascript, 'inline');
The issue is weird. When I'm logged in then everything works fine, all the time. However, when I am using it as an anonymous user it all works fine the first load (after cache clear) but then the two javascripts arent added anymore.
I have tried to change my preprocess function so it adds the javascript with this $variables['scripts'] = drupal_get_js(); but this also has the same behaviour.
Ive googled around a bit and found some suggestions but nothing's worked thus far. Any help is appreciated.
Thanks,
EDIT: So I looked into this a bit more and the code is being executed through the filter module. It seems as though the first time it gets executed it gets cached and then it is just recieved from the cache each time after that, so the drupal_add_js code isn't run again.
Is there a way around this caching or do I need to remove my js from this part all together?
You can always add the js in the page level "hook_page_alter(&$page)"

Drupal: Passing custom variable from custom module to my template

I realize this question has been asked, but I either simply don't understand or the previous answers don't apply (or I don't understand how to apply them) to my circumstance. Here goes:
I have a custom module named:
"my_module" in /sites/all/modules/custom/my_module
I have a module file:
/sites/all/modules/custom/my_module/my_module.module
I have a page template name "page-mypage" which is NOT in my module:
/sites/all/themes/mytheme/pages/page-mypath-mypage.tpl.php
I made the hook menu for this:
$items['mypath/mypage'] = array(
'title' => 'My Page!',
'page callback' => 'my_module_mypage',
'page arguments' => array(1,2),
'access callback' => true,
'type' => MENU_CALLBACK,
);
In the function, I build up some content like so:
function my_module_mypage($x, $y) {
$output = "foo AND bar!";
return $output;
}
In the template (again, NOT in my module folder, but in the THEME subfolder "pages", I have:
<?php print $content ?>
When I go to http://mysite/mypath/mypage I get "foo AND bar!"
Now for the question. I want a new variable, defined in my_module_mypage(), called '$moar_content'. I want to output $moar_content in my page-mypath-mypage.tpl.php. I only need to do this for this module and for this template. I do not need it theme-wide, so I don't think using mytheme's 'template.php' is appropriate.
I think I need to use some kind of preprocessing, but everything I try fails, and everything I read seems to be missing some kind of magic ingredient.
My thinking was:
function my_module_preprocess_page_mypath_mypage(&$variables) {
$variables['moar_content'] = 'OATMEAL';
}
or
function my_module_preprocess_my_module_mypage(&$variables) {
$variables['moar_content'] = 'OATMEAL';
}
or something. I'm pretty sure I'm on the right track, but I'm hitting a brick wall.
To do the job, you must follow Drupal's best practices, supposing you are using D6, so you can insert some variables to your template like this :
// You menu path is good
$items['mypath/mypage'] = array(
'title' => 'My Page!',
'page callback' => 'my_module_mypage',
'page arguments' => array(1,2),
'access callback' => true,
'type' => MENU_CALLBACK,
);
Second thing, we define the theme hook for our page
// We define here a new theme file for your your page
// Your theme file must be located in your module's folder
// you can use a subfolder to group all your module's theme files
// E.g : themes/my-module-theme.tpl.php
// Note that in theme files, we change _ by -
function my_module_theme() {
return array(
'my_module_theme' => array( // Keep that name in your mind
'template' => 'my_module_theme',
'arguments' => array(
'my_var' => NULL,
'my_var2' => NULL,
),
)
);
}
Now we can create a file "my-module-theme.tpl.php" in the root folder of our module, and paste something like "foo AND bar!"
Back to our my_module.module, the callback must be something like :
function my_module_mypage($x, $y) {
// $x and $y are optionnal, so this is the first manner
// to inject variables into your theme's file
$output = theme("my_module_theme", $x, $y);
return $output;
}
Also you can use preprocess hook to insert variables
// The hook must be named like this : template_preprocess_NAME_OF_THEME_FILE
// where NAME_OF_THEME_FILE is the name that you kept in your mind ;)
function template_preprocess_my_module_theme(&$variables) {
// Do some job
$var1 = 'Foobar';
// So in "my-module-theme.tpl.php", $my_var1 will print Foobar
$variables['my_var1'] = $var1;
}

Resources