Index
- Debugging errors
- Override a theme function
- View the theme registry
- Update an unlimited date field
- Render a node’s field in page.tpl.php
- Get the value of a node’s field
- Add content to region
- Create a basic theme
- Use theme CSS with CKEditor
- Customise empty term page message
- Get body of page
- Get current language
- Run text through a filter
- Text sanitisation
- Theme view fields
- Get field name from [theme]_preprocess_views_view_fields()
- Create a relative link
- Alter form fields and validation
- Get node URL
- Write to the log
- Hide taxonomy term page
- Get URL from Drupal internal URI (e.g. public://..)
- Get URL for a particular image style from the image URI
- Get node object in [theme]_preprocess_node
- Iterate over a node field’s values
- Render a field in preprocess function
- Rename the contact form
- Remove menu items
- Remove class from fields
- Remove class from links
- Run tests from command line
- Change default input filter per content type
- Get stylesheets array
- Get top taxonomy term by vocabulary
- Get all terms in a vocabulary
- Get vocabulary by machine name
- Get vocabulary ID
- Theme specific node types and view modes
- Check out project from git
- Preprocess a particular field in a particular view (Views 3)
- Change module weights
- Reorder module hooks
- Change salt and reset passwords
- Dynamically alter a menu
Debugging errors
So you can place watchdog()
calls around the code to see what’s going on, or install the devel module and use dpm()
or kpr()
instead, but if all you’re getting is a generic exception, such as an EntityMetadataWrapperException, then it can be almost impossible to figure out where in the code it was generated. E.g:
EntityMetadataWrapperException: Invalid data value "foobar" given for wrapper chain user:. in EntityDrupalWrapper->set() (line 769 of ~/dev/test/d7/public/sites/all/modules/entity/includes/entity.wrapper.inc).
In this case, you can use PHP’s debug_backtrace()
to print an array of all function calls leading up to the exception. Temporary place the following just before the line that generates the exception (you should see which line this is in the exception message, as above):
print('<pre>'.print_r(debug_backtrace(),true).'</pre>');
die();
Note that the output could be very long and verbose, but it should provide enough to narrow down on the cause of the problem.
Reference: Tracing & Debugging Drupal Errors.. Backwards!
Override a theme function
I needed to change the icon output by the views_pdf module, which defined a theme_views_pdf_icon()
function to theme the icon.
First determine the registry key, which you can get from the hook_theme()
function call:
function views_pdf_theme() {
return array(
'views_pdf_plugin_style_table' => array(
'render element' => 'form',
'file' => 'views_pdf.admin.inc',
),
'views_pdf_icon' => array(
'render element' => 'form',
'variables' => array('url' => NULL, 'title' => NULL),
),
);
}
Alternatively poke around in the registry to find it - see View the theme registry.
You will find an associated theme function, in this case theme_views_pdf_icon()
.
Next, alter the registry with hook_theme_registry_alter()
and change the theme function, so that in this case I could change the path of the icon image.
function mymodule_theme_registry_alter(&$theme_registry) {
if (isset($theme_registry['views_pdf_icon']['function'])) {
$theme_registry['views_pdf_icon']['function'] = 'mymodule_theme_views_pdf_icon';
}
}
function mymodule_theme_views_pdf_icon($vars) {
...
$imagePath = drupal_get_path('module', 'mymodule') . '/images/pdf.png';
...
}
I also wrote about this here: Add text with link to pdf icon
Reference: Overriding a module’s theme function
View the theme registry
If you’d like to use hook_theme_registry_alter()
function to override a theme function, then it’s useful to be able to inspect the registry.
With the devel module installed (for the dpm()
call), you can view the registry as follows:
global $theme;
$registry = _theme_load_registry($theme);
dpm($registry);
Reference: Is there any way to see the content of the theme registry?
Update an unlimited date field
To programmatically add a date to an unlimited date field, use entity_metadata_wrapper()
:
// We already have $mid, the membership ID.
$membership = membership_entity_load($mid);
$membership_wrapper = entity_metadata_wrapper('membership_entity', $membership);
// Check that the field exists
if (isset($membership_wrapper->field_date_foobar)) { // Can only set field if it exists.
$membership_wrapper->field_date_foobar[] = REQUEST_TIME;
}
$membership_wrapper->save();
Check isset to avoid ‘EntityMetadataWrapperException: Unknown data property field_date_foobar. in EntityStructureWrapper->getPropertyInfo()’ when entity doesn’t have the field (e.g. if added to entity bundle after some have been created).
If the value (REQUEST_TIME in this example) isn’t a timestamp you’ll get ‘EntityMetadataWrapperException: Invalid data value given. Be sure it matches the required data type and format. in EntityMetadataWrapper->set()’
Reference: Save a new value with entity_metadata_wrapper to an entity field which is an array
Render a node’s field in page.tpl.php
First hide the field in node.tpl.php:
hide($content['field_manufacturer_web_site']); // We render manufacturer web site field in page.tpl.php.
Then render the field in page.tpl.php:
// Print manufacturer web site below the 'content' region so it appears below view blocks in content.
if ($node) {
$manufacturer_web_site_fields = field_get_items('node', $node, 'field_manufacturer_web_site');
if ($manufacturer_web_site_fields) {
print render(field_view_field('node', $node, 'field_manufacturer_web_site', $manufacturer_web_site_fields[0]));
}
}
Reference: http://stackoverflow.com/questions/16015514/render-link-field-url-and-title-separately-in-node-template-in-drupal-7
Get the value of a node’s field
Use field_view_field
function.
$field = field_view_field('node', $node, 'field_name');
$output = render($field);
That could render the title of the field as well as it’s value, depending on how you’ve set up the field’s display.
If you’d like the value only, you can use field_get_items
with field_view_value
.
$field = field_get_items('node', $node, 'field_name);
$field_value = field_view_value('node', $node, 'field_name', $field[0]);
$output = render($field_value);
If the field has more than one value then you’ll have to replace the 0 with the appropriate deltas.
Reference: Rendering Drupal 7 fields (the right way)
Render a node’s field in page.tpl.php
First hide the field in node.tpl.php:
hide($content['field_manufacturer_web_site']); // We render manufacturer web site field in page.tpl.php.
Then render the field in page.tpl.php:
// Print manufacturer web site below the 'content' region so it appears below view blocks in content.
if ($node) {
$manufacturer_web_site_fields = field_get_items('node', $node, 'field_manufacturer_web_site');
if ($manufacturer_web_site_fields) {
print render(field_view_field('node', $node, 'field_manufacturer_web_site', $manufacturer_web_site_fields[0]));
}
}
Reference: http://stackoverflow.com/questions/16015514/render-link-field-url-and-title-separately-in-node-template-in-drupal-7
Get the value of a node’s field
Use field_view_field
function.
$field = field_view_field('node', $node, 'field_name');
$output = render($field);
That could render the title of the field as well as it’s value, depending on how you’ve set up the field’s display.
If you’d like the value only, you can use field_get_items
with field_view_value
.
$field = field_get_items('node', $node, 'field_name);
$field_value = field_view_value('node', $node, 'field_name', $field[0]);
$output = render($field_value);
If the field has more than one value then you’ll have to replace the 0 with the appropriate deltas.
Reference: Rendering Drupal 7 fields (the right way)
Add content to region
In a theme hook e.g. theme_preprocess_page()
:
function my_theme_preprocess_page(&$variables) {
$variables['page']['header']['foo'] = array('#markup' => '<p>bar</p>');
}
You can use a render array to e.g. load a template like [my_module]/templates/foo.tpl.php:
/**
* Implementation of hook_theme() to register templates for this theme.
*/
function my_theme_theme($existing, $type, $theme, $path) {
return array('foo' => array('path' => drupal_get_path('theme', 'my_theme').'/templates',
'template' => 'foo'));
}
/**
* Implementation of template_preprocess_page(). Show the 'foo' template in the header region.
*/
function my_theme_preprocess_page(&$variables) {
$variables['page']['header']['foo'] = array('#theme' => 'foo');
}
Create a basic theme
- Create a directory sites/all/themes/[mytheme]
- Add a [mytheme].info file
- Copy templates from modules/system/ e.g. html.tpl.php into your theme directory and customise as desired.
- Follow Theming Drupal 6 and 7 guide.
Use theme CSS with CKEditor
In the CSS section of the WYSIWYG profiles for CKEditor, ‘Use theme CSS’ doesn’t work. Instead I had to ‘Define CSS’ with the following value:
/sites/all/themes/[your-theme]/css/[some.css],/sites/all/themes/[your-theme]/css/[some-more.css]
Note that the tokens could not be used either (e.g. %b%t/../[your-theme]/css/[some.css]
) as the double dot doesn’t work.
Customise empty term page message
By default, when you view a taxonomy term page which has no nodes you get the default message “There is currently no content classified with this term.”. You can change this by altering the markup in the page preprocess function:
function [theme]_preprocess_page(&$variables, $hook) {
if(isset($variables['page']['content']['system_main']['no_content'])) {
$term = $variables['page']['content']['system_main']['term_heading']['term']['#term'];
if ($term->vid == 2) { // Target vocab ID 2 only.
$variables['page']['content']['system_main']['no_content']['#prefix'] = '<p>';
$variables['page']['content']['system_main']['no_content']['#markup'] = t('No nodes classified with this term dagnamit!');
$variables['page']['content']['system_main']['no_content']['#suffix'] = '</p>';
}
}
}
Get body of page
To get the body of the page within node.tpl.php, use the $body
variable as it will contain the body for the user’s current language - using $node->body[$lang]
requires knowledge of $lang
(e.g. ‘und’ for undefined, or ‘en’, ‘fr’, etc).
Get current language
global $language;
$language->language;
Reference: global $language
$node->language
is a node’s default language.
Run text through a filter
check_markup('<a href="#">Test anchor</a>', 'filtered_html')
Text sanitisation
Use t()
for text sanitisation.
There are three different types of placeholders for the t function:
- @ is sanitized - escaped to HTML using
check_plain()
. - % is sanitized - escaped as a placeholder for user-submitted content using
drupal_placeholder()
, which shows up as emphasized text. - ! is not sanitized - text is inserted as is.
See Sanitization in Drupal: the t function
Theme view fields
You can override the Views field templates (copy from sites/all/modules/views/theme/ folder into your own theme):
- views-view-fields.tpl.php
- views-view-field.tpl.php
You can also target specific views and fields, e.g:
- views-view-field–[view_name]–[field_name].tpl.php
Preprocess functions:
- [theme]_preprocess_views_view_fields()
- [theme]_preprocess_views_view_field()
The Views module does most of its work theming fields in its template_preprocess_views_view_fields()
function, where it creates an object for each field to hold its output (markup) and other related variables.
You can override to e.g. remove markup that’s added by the Views module.
function [theme]_preprocess_views_view_fields(&$variables) {
$view = $variables['view'];
if (in_array($view->name, array('highest_rated_products','promoted_products', 'latest_products'))) {
foreach($variables['fields'] as $id => $field) {
$field->wrapper_prefix = '';
$field->label_html = '';
$field->wrapper_suffix = '';
}
}
}
Get field name from [theme]_preprocess_views_view_fields()
E.g. This adds YAML subtemplate markup around some view fields.
function [theme]_preprocess_views_view_fields(&$variables) {
$view = $variables['view'];
if (in_array($view->name, array('highest_rated_products','promoted_products', 'latest_products'))) {
foreach($variables['fields'] as $id => $field) {
$field->label_html = '';
switch($field->handler->options['id']) {
case 'field_multimedia':
$field->wrapper_prefix = '<div class="subcolumns"><div class="c33l"><div class="subcl">';
$field->wrapper_suffix = '</div></div>';
break;
case 'body':
$field->wrapper_prefix = '<div class="c66r"><div class="subcr">';
$field->wrapper_suffix = '</div></div></div>';
break;
default:
$field->wrapper_prefix = '';
$field->wrapper_suffix = '';
break;
}
}
}
}
Create a relative link
l('link text', '/a/link', array( 'attributes' => array( 'class' => a_class )));
l('<img src="whatever.jpg" alt="whatever"/>', '/a/link', array( 'html' => TRUE, 'attributes' => array( 'class' => a_class )));
Alter form fields and validation
TODO - reminder to grab code from my f3_rmi_contact_form module and update this page!
Get node URL
url(drupal_get_path_alias('node/' . $node->nid), array('absolute' => TRUE));
Write to the log
watchdog('[module name]', 'the message');
Hide taxonomy term page
Write a custom module and use hook_menu_alter
to change the access callback for ‘taxonomy/term/%taxonomy_term’ to a function that does what it needs to do and returns the appropriate permission.
TODO - reminder to grab code from my f3_protect_taxonomy_term_pages module and update this page!
Get URL from Drupal internal URI (e.g. public://..)
$file = $variables['field_image']['file'];
$url = file_create_url('large', $file->uri);
Here, $file->uri
would be something like public://field/image/some-image.jpg and $url
would be something like http://somesite.com/sites/default/files/styles/large/public/field/image/some-image.jpg.
Get URL for a particular image style from the image URI
If you have an image URI you can get the URL of the image associated with an image style using the image_style_url
function, e.g. in [theme]_preprocess_node
function, where the field is called ‘field_image’ and the style is ‘large’:
$file = $variables['field_image']['file'];
image_style_url('large', $file->uri);
Get node object in [theme]_preprocess_node
$node = $variables['node'];
Iterate over a node field’s values
E.g. a multimedia field may have many values, each of which is a media item (audio, image, video, etc).
$node = $variables['node'];
foreach ($node->field_multimedia[$node->language] as $key => $value) {
$file = $value['file'];
...
}
Render a field in preprocess function
$field = field_view_value('node', $node, 'field_myfield', $node->field_myfield[$node->language][0], 'full');
$markup = render($field);
- This can be done with any entity, e.g. user, taxonomy, etc. Just change ‘node’ to the entity type and replace $node with the appropriate object.
- The fourth argument is the specific item of the field that you’d like to get. E.g. A Media module field field_multimedia may have many image items. In this example we’re simply getting the first item. Change the delta from 0 to 1 to get the second item, to 2 to get the third, etc.
- Change ‘full’ to e.g. ‘teaser’ to get the rendering for a different view / display mode.
See function field_view_value.
Rename the contact form
The contact form takes its title from its default menu item, so rename that and the page name will change. This works even if the default menu item is disabled and you’ve a custom item in another menu.
Remove menu items
TODO - reminder to view code in template.php from my “rmi” site and update this page!
Remove class from fields
E.g. To remove the ‘clearfix’ class from the categories and tags fields, use the following in [theme]’s template.php:
function [theme]_preprocess_field(&$variables) {
$field_names = array('field_categories','field_tags');
if(in_array($variables['element']['#field_name'], $field_names)) {
foreach($variables['classes_array'] as $key => $value) {
if ($value == 'clearfix') {
unset($variables['classes_array'][$key]);
break;
}
}
}
}
Remove class from links
E.g. To remove the ‘inline’ class from the links, use the following in [theme]’s template.php:
function [theme]_preprocess_node(&$variables, $hook) {
foreach($variables['content']['links']['#attributes']['class'] as $key => $value) {
if ($value == 'inline') {
unset($variables['content']['links']['#attributes']['class'][$key]);
break;
}
}
}
Run tests from command line
php ./scripts/run-tests.sh --url [the url] --verbose --all
Change default input filter per content type
Can’t be done in Drupal 7 core :-/
Get stylesheets array
drupal_add_css()
Get top taxonomy term by vocabulary
$terms = taxonomy_get_tree(5);
print('Top term is in vocab 5 is '.$terms[0]->name);
TODO: Is there a more efficient way to do this?
Get all terms in a vocabulary
taxonomy_get_tree($vid);
Get vocabulary by machine name
taxonomy_vocabulary_machine_name_load('machine name');
Get vocabulary ID
Inspect the output of taxonomy_get_vocabularies()
.
Theme specific node types and view modes
To add support for node type and view node templates, use the following theme hook suggestions code:
function mytheme_preprocess_node(&$variables) {
$variables['theme_hook_suggestions'][] = 'node__' . $variables['type'] . '__' . $variables['view_mode'];
}
See Addition of a node–teaser.tpl.php by default.
TODO: Does this automatically add preprocess functions? If not, use the following code (from Zen STARTERKIT):
function theme_preprocess_node(&$variables, $hook) {
// Optionally, run node-type-specific preprocess functions, like
// theme_preprocess_node_page_full() or theme_preprocess_node_story_teaser().
$function = __FUNCTION__ . '_' . $variables['node']->type . '_' . $variables['view_mode'];
if (function_exists($function)) {
$function($variables, $hook);
}
}
Check out project from git
Use the git.drupal.org domain rather than drupalcode.org. See the Git section above.
Preprocess a particular field in a particular view (Views 3)
E.g. for the mytheme theme:
/**
* Change div into span for 'foo' fields in 'bar' view.
*/
function mytheme_preprocess_views_view_field(&$variables, $hook) {
if ($variables['view']->name == 'bar') {
// Either
$field = $variables['field_foo'];
// Or
$view = $variables['view'];
$field = $view->field['field_foo'];
// Then
$field->options['element_type'] = 'span';
}
}
Change module weights
See How to update a module’s weight.
Reorder module hooks
See function hook_module_implements_alter.
Change salt and reset passwords
First change the salt in [drupal]/sites/default/settings.php:
$drupal_hash_salt = 'whatever-you-want-but-make-it-random';
If you’re on Linux you could use the makepasswd
command to generate it:
makepasswd --chars 23
Once you’ve changed the salt you’ll need to update the database with the new salted passwords. First generate the salted passwords using Drupal’s password-hash script (in the Drupal root):
./scripts/password-hash.sh 'the-password'
Example output:
me@pc ~/tmp/dev/drupal $ ./scripts/password-hash.sh 'XhMPi5uHI6t4'
password: XhMPi5uHI6t4 hash: $S$DWKCUpZbdddB21hlkdDJjwN67hp32RLsE/N9VPyevytdJG8L5rvw
Again, make sure the password is strong. For the previous example I used makepasswd
to generate a random 12 character string.
Once you have the salted password (AKA hash) you can add it to the database via an SQL query. First determine the user ID (uid) of the user you’d like to update:
mysql> select uid,name from users;
+-----+-----------------+
| uid | name |
+-----+-----------------+
| 0 | |
| 1 | admin |
| 2 | stephan |
+-----+-----------------+
6 rows in set (0.00 sec)
mysql> update users set pass='$S$DWKCUpZbdddB21hlkdDJjwN67hp32RLsE/N9VPyevytdJG8L5rvw' where uid='2';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
Go through that process with every user.
References: Reset admin password in drupal 7
Dynamically alter a menu
To alter a menu dynamically, or more specifically a menu item, implement hook_menu_link_alter()
to flag a menu item as alterable then implement hook_translated_menu_link_alter()
to alter it, like so:
/**
* Implements hook_menu_link_alter().
*
* Flag the 'Join' menu item as alterable via hook_translated_menu_link_alter().
*/
function mymodule_menu_link_alter(&$item) {
if ($item['menu_name'] == 'user-menu' && $item['link_title'] == 'Join') {
$item['options']['alter'] = TRUE;
}
}
/**
* Implements hook_translated_menu_link_alter().
*
* Remove the 'Join' link if the user has a membership.
*/
function mymodule_translated_menu_link_alter(&$item) {
if ($item['menu_name'] == 'user-menu' && $item['link_title'] == 'Join') {
global $user;
$memberships = membership_entity_load_by_user($user);
if (!empty($memberships)) {
$item['access'] = FALSE;
}
}
}
References: How can I change a menu link dynamically? (D7)