Converting 4.7.x modules to 5.x

This page describes changes to the module interface; a 5.x themes conversion guide is also available.

Overview of Drupal API changes in 5.x

  1. New module .info files must read
  2. New handling of links for hook_link()
  3. New hook_link_alter()
  4. Changed menu_primary_links(), and menu_secondary_links() return structured links
  5. Change user_mail() to drupal_mail()
  6. New hook_mail_alter()
  7. Change user_mail_wrapper() to drupal_mail_wrapper()
  8. Removed hook_settings()
  9. New hook_profile_alter()
  10. New message_na() removed
  11. New administration layout
  12. New drupal_add_css() - proper way to add css
  13. hook_taxonomy('form') has been removed. use hook_form_alter() instead
  14. Change form_render() to drupal_render()
  15. Change hook_view() and hook_nodeapi($op = 'view')
  16. New hook_nodeapi($op = 'alter') has been added
  17. Changes to hook_node_info() and the node type system.
  18. The node_get_names() and node_get_base() functions no longer exist.
  19. New hook_node_type()
  20. Changes to node type settings form
  21. New db_table_exists()
  22. New hook_node_operations(), hook_user_operations()
  23. Altered the behaviour of placeholders in t() calls
  24. Changed drupal_get_form() to take a $form_id, not a $form
  25. Changed - forms must be created in dedicated builder functions
  26. New hook_forms() optionally maps form_ids to builder functions
  27. New drupal_execute() function allows form data to be submitted programmatically
  28. module_exist() is now module_exists()
  29. format_plural() @count change
  30. Vastly extended drupal_add_js()
  31. Removed drupal_call_js()
  32. New drupal_add_feed() and drupal_get_feeds replaces theme_add_link()
  33. theme('page') may omit standard blocks
  34. New #disabled Form API property
  35. New changed cache API
  36. Uninstall hook
  37. Added jQuery to Drupal
  38. $_POST[op] deprecated in favor of $form_values[op]
  39. Change system_listing() to drupal_system_listing()
  40. Change menu item and node links to use Sentence capitalization instead of lowercase
  41. Major changes to node access system will affect all modules utilizing node_access table
  42. Changes to how confirm_form() works
  43. Changes to how #prefix and #suffix are rendered in form arrays
  44. Changes to how #options are represented in some form arrays (Especially the $form['taxonomy'] array).
  45. $node->moderate no longer used by core.

.info files

All modules now need to have a modulename.info file, containing meta information about the module. The format is:

; $Id$
name = Module Name
description = A description of what your module does.

Without this file, your module will not show up in the module listing!. You may remove this information from your hook_help() implementation.

There are also 2 optional lines that may appear in the .info file:

dependencies = module1 module2 module3
package = "Your arbitrary grouping string"

If you assign dependencies for your module, Drupal will not allow it to be activated until the required dependencies are met.

For compatibility with PHP versions earlier than 5.0, each value must be on a single line. It is not possible to include line breaks (e.g. neither newline nor return characters) within a single entry in this file.

If you assign a package string for your module, on the admin/build/modules page it will be listed with other modules with the same category. If you do not assign one, it will simply be listed as 'Other'. Not assigning a package for your module is perfectly ok; in general packages are best used for modules that are distributed together or are meant to be used together. If you have any doubt, leave this field blank.

Suggested examples of appropriate items for the package field:

  • Audio
  • Bot
  • CCK
  • Chat
  • E-Commerce
  • Event
  • Feed Parser
  • Organic groups
  • Station
  • Video
  • Views
  • Voting (if it uses/requires VotingAPI)

The files use the ini format and can include a ; $Id$ to have CVS insert the file ID information.

For more information on ini file formatting, see the PHP.net parse_ini_file documentation.

See http://drupal.org/node/101009 for more information on .info files.

hook_link() has changed in 5.x. No longer do you pass in an array of l()s, rather, you pass in a structured link array, much like forms api.

4.7.x:

<?php
function blog_link($type, $node = 0, $main = 0) {
 
$links = array();

  if (
$type == 'node' && $node->type == 'blog') {
    if (
arg(0) != 'blog' || arg(1) != $node->uid) {
     
$links[] = l(t("%username's blog"
                   array(
'%username' => $node->name)),
                  
"blog/$node->uid",
                   array(
'title' => t("Read %username's latest blog entries.",
                   array(
'%username' => $node->name))));
    }
  }

  return
$links;
}
?>

Now in 5.x:

<?php
function blog_link($type, $node = 0, $main = 0) {
 
$links = array();

  if (
$type == 'node' && $node->type == 'blog') {
    if (
arg(0) != 'blog' || arg(1) != $node->uid) {
     
$links['blog_usernames_blog'] = array(
       
'title' => t("%username's blog",
        array(
'%username' => $node->name)),
       
'href' => "blog/$node->uid",
       
'attributes' => array('title' => t("Read %username's latest blog entries.", array('%username' => $node->name)))
      );
    }
  }

  return
$links;
}
?>

To add links, the following variables can be used:

<?php
$links
['my_module_name_link_description'] = array(
  
'title' => t('Link Title'),
  
'href' => "node/$nid",
  
'attributes' => array('title' => t('Descriptive link title')),
  
'query' => 'some_query',
  
'fragment' => 'my_page_fragment',
  
'html' => FALSE,
);
?>

Note, be sure to specify 'my_module_name_link_description' in that order to be consistent and avoid namespace conflicts. Examples, 'menu-4-3-2', 'node-read-more', 'comment-add-new'.

The final variable, 'html', is used to specify if the link title is itself html code. If FALSE (the default), the title will be filtered for safe output. However, sometimes you need to have the title printed out without filtering (in which case it is your responsibility to ensure the input is safe). See the documentation for the l() function for more details. For example, to include an image in a link title, you would use something like this:

<?php
function station_listen_links($node, $short = FALSE)  {
 
$listen_url = 'station/archives/'. $node->nid;
 
$img_listen = drupal_get_path('module', 'station_schedule') .'/images/listen_tiny.gif';
 
  return array(
   
'station_archive_listen' => array(
     
'href' => $listen_url,
     
'title' => theme('image', $img_listen, t('Listen')) . t('Listen to previous'),
     
'attributes' => array('title' => t('Listen to previous broadcasts of this show')),
     
'html' => TRUE,
    ),
  );
}
?>

Additionally, for each link created, a class is automatically added to each link based on the name given to the link (e.g., my_module_name_link_description). You can optionally add more classes as well, but in most cases, this is probably not needed.

A new hook has been created for modules wishing to alter links of other modules: hook_link_alter().

To use this:

<?php
function forum_link_alter(&$node, &$links) {
  foreach (
$links AS $module => $link) {
    if (
strstr($module, 'taxonomy_term')) {
     
// Link back to the forum and not the taxonomy term page
     
$links[$module]['href'] = str_replace('taxonomy/term', 'forum', $link['href']);
    }
  }
}
?>

The $node object and array of $links are passed by reference for altering, much like hook_form_alter().

Note, this works for both node links and comment links.

menu_primary_links() and menu_secondary_links() now return structured links. No longer is a an array of l()s returned, but rather a structured array of links. When you loop through these links in $primary_links or $secondary_links in a phpTemplate theme (or menu_primary_links() and menu_secondary_links() respectively), you must build your l() by passing in the values.

4.7.x themes:

<?php
print '<ul>';
foreach (
$primary_links as $link) {
   print
'<li>'. $link .'</li>';
}
print
'</ul>';
?>

5.x themes:

<?php
print '<ul>';
foreach (
$primary_links as $link) {
  print
'<li>'. l($link['title'], $link['href'],
   
$link['attributes'], $link['query'],
   
$link['fragment'], FALSE, $link['html']) .'</li>';
}
print
'</ul>';
?>

It is recommended to pass these directly to theme('links') which will take care of this for you.

Example:

<?php
print theme('links', $primary_links);
?>

If you wish to have your links appear in a list as in above, simply override theme('links') in your theme and add this logic to print the UL and LI. This is the cleanest approach.

user_mail() is replaced by drupal_mail()

If your module uses user_mail() to send email, then you have to change that to use the new drupal_mail() function.

<?php
drupal_mail
($mailkey, $to, $subject, $body, $from, $headers);
?>

The new $mailkey is used to identify the email for hook_mail_alter (see below). $to is a string with one or more recipients, $subject

is the mail subject, $body is the message body. The optional $from sets From, Reply-To, Return-Path and Error-To headers to this same value (a common practice). The optional $headers is an associative array with header names and values. This function ensures that the header values are MIME encoded, and the newlines are correct.

New hook_mail_alter()

Your module can implement a hook_mail_alter() that takes the same arguments as the drupal_mail() function. You can use this to add a standard site footer to all outgoing emails, add special headers, or completely HTML-ize your mails. You should take the parameters by reference and change the values. hook_mail_alter() is not supposed to return anything.

user_mail_wrapper() changed to drupal_mail_wrapper()

The user_mail_wrapper() function used to support an alternate mail backend (instead of the PHP built-in mail() function) should now be called drupal_mail_wrapper(). It should take the same arguments as drupal_mail() does.

Removed hook_settings()

hook_settings() has been removed. Modules that want to add a settings page should now use the menu callback system. Settings pages are no longer a special case. Add a MENU_NORMAL_ITEM to your modules _menu() function and register your module's settings page(s) under ?q=admin/settings/<some-path>. In your callback, you need to define the form using the forms API. You don't have to implement a _validate or _submit hook. The system.module implements default validate and submit functions for simple setting forms. Just use the helper function system_settings_form() or return the form using the following snippet:

Here is an example:

The 4.7.x way:

<?php
function yourmodule_settings() {
$form['blah'] = ...

return
$form;
}
?>

In 5.x, this should be changed to:

<?php
function yourmodule_menu($may_cache) {
...
$items[] = array(
   
'path' => 'admin/settings/your-module',
   
'title' => t('your module name'),
   
'description' => t('Describes what the settings generally do.'),
   
'callback' => 'drupal_get_form',
   
'callback arguments' => array('yourmodule_admin_settings'),
   
'access' => user_access('administer site configuration'),
   
'type' => MENU_NORMAL_ITEM, // optional
  
);
...
}

function
yourmodule_admin_settings() {
...
$form['something'] = array(
  ...
  );
 
return
system_settings_form($form);
}
?>

New hook_profile_alter()

A new hook has been created for modules wishing to alter fields which appear on the user profile page: hook_profile_alter(). Modules may remove fields (e.g. a privacy control module for individuals) and/or change the order/organization of the fields.

To use this:

<?php
function mymodule_profile_alter(&$account, &$fields) {
  foreach (
$fields AS $key => $field) {
   
// do something
 
}
}
?>

All fields should have a unique key provided by the module which initially inserted the field. If no such key exists, please file an issue (and patch) against the offending module.

message_na() removed

The function message_na() was removed, remove it from your modules as well and replace it with t('n/a').

New administration layout

The layout of the administration pages have changed.

References to some common administration pages have changed; in particular, admin/modules is now admin/build/modules, admin/menu is now admin/build/menu and admin/block is now admin/build/block; for all existing links you may have into the admin/ tree, please check to see if the destination has moved, especially in your hook_help().

Particularly noteworthy, in your hook_help(), you need to remove the admin/modules#description section and, instead, put the description in the new module .info file. The .info file is required.

You should no longer put your module's administration links directly under admin/, but instead should identify an area for your module's administration links. Existing administration sections are:

  • admin/content -- for content related activities; modules that create a node type should put their administrative items here.
  • admin/build -- for site layout related activities; modules that modify the the structure of the site may put their administrative items here.
  • admin/log -- for logging and site status related pages and administrative items.
  • admin/user -- for items dealing with user or user access management.
  • admin/settings -- for pretty much everything else, considered general configuration.

Modules and module groups that may have many administration pages are allowed to create a block for themselves. For example, ecommerce might create an admin/ecommerce item. Administrative menu items that create these areas may set their callback to system_admin_menu_block_page(), or they can set it to a page that provides a general overview (but the page may not be visited often so do not put important information only on this page). The administrative overview allows these items to set 'position' => 'left' or 'right'; this doesn't need to be set, and it is expected some administrative themes will ignore this setting.

For example:

<?php
  $items
[] = array(
   
'path' => 'admin/my_settings',
   
'title' => t('My modules configuration'),
   
'description' => t('Adjust my modules configuration options.'),
   
'position' => 'right',
   
'weight' => -5,
   
'callback' => 'system_admin_menu_block_page',
   
'access' => $access);
?>

Finally, all administrative items should provide a 'description' in their menu item array that the administrative overview can provide to the user.

For example:

<?php
  $items
[] = array(
   
'path' => 'admin/my_settings/mymodule-information',
   
'title' => t('My module information'),
   
'description' => t('Change my module information, such the e-mail address.'),
   
'callback' => 'mymodule_information_settings',
   
'access' => user_access('administer site configuration'),
  );
?>

drupal_add_css() - the proper way to add CSS

Modules should now use:

<?php
drupal_add_css
(drupal_get_path('module', 'module-name') .'/module.css');
?>

To properly add CSS files to the theme. Read more about it at the theming level.

hook_view() and hook_nodeapi($op = 'view') have changed

Nodes are now prepared for display by assembling a structured array in $node->content, rather than directly manipulating $node->body and $node->teaser. The format of this array is the same used by FormAPI.

Modules that use hook_view() to prepare their custom node types for display should now use the following format:

<?php
function mymodule_view($node, $teaser, $page) {
 
$node = node_prepare($node, $teaser);
 
$node->content['myfield'] = array(
   
'#value' => theme('mymodule_myfield', $node->myfield),
   
'#weight' => 1,
  );
  return
$node;
}
?>

Modules that use hook_nodeapi() to alter the node's content should use a format :

<?php
function mymodule_nodeapi(&$node, $op) {
  if (
$op == 'view') {
   
$node->content['my_additional_field'] = array(
     
'#value' => theme('mymodule_my_additional_field', $additional_field),
     
'#weight' => 10,
    );
  }
}
?>

As with FormAPI arrays, the #weight property can be used to control the relative positions of added elements. This replaces the old method of appending or prepending text to $node->body. If for some reason you need to change the body or teaser returned by node_prepare(), you can modify $node->content['body']['#value']. Not that this will be the un-rendered content. To modify the rendered output, see hook_nodeapi($op = 'alter').

Other operations, like setting the breadcrumb trail, are unchanged.

hook_nodeapi($op = 'alter') has been added

The new NodeAPI 'alter' operation lets modules modify the fully-rendered node body or teaser. This works the way Drupal 4.7's 'view' operation did, and should only be used when text substitution, filtering, or other raw text operations are necessary. For example:

<?php
function mymodule_nodeapi(&$node, $op) {
  if (
$op == 'alter') {
   
$node->body = str_replace($node->body, 'Original word', 'Replacement word');
   
$node->teaser = str_replace($node->teaser, 'Original word', 'Replacement word');
  }
}
?>

Changes to hook_node_info() and the node type system

hook_node_info() now allows node modules to define more attributes for their node types. Node types are still defined by returning an array of arrays. The 'base' array sub-element has been renamed 'module'. There are now three required attributes for all node types defined through hook_node_info(): 'name', 'module', and 'description'.

Example:

<?php
function book_node_info() {
  return array(
   
'book' => array(
     
'name' => t('Book page'),
     
'module' => 'book',
     
'description' => t("A book is a collaborative writing effort: users
      can collaborate writing the pages of the book, positioning the
      pages in the right order, and reviewing or modifying pages previously
      written. So when you have some information to share or when you
      read a page of the book and you didn't like it, or if you think a certain
      page could have been written better, you can do something about it."
),
    )
  );
}
?>

Additionally, there are now seven optional attributes: 'help', 'has_title', 'title_label', 'has_body', 'body_label', 'min_word_count', and 'locked'. Previously, node modules could only define the machine-readable name (the key of each array element), the human-readable name (the 'name' array sub-element), and the base function name (the former 'base' array sub-element) of each node type.

hook_node_info() is now to be used only for defining module-provided node types. User-provided (or 'custom') node types are defined only in the new 'node_type' database table, and they should be maintained by using the node_type_save() and node_type_delete() functions. These node types should not be dynamically (re-)defined through hook_node_info().

The 'node_type' database table is now the authoritative source that defines all node types on a site. Previously, this table did not exist, and hook_node_info() was the authoritative source.

The node_get_names() function no longer exists. Please use node_get_types('name', $node) instead. Similarly, the node_get_base() function no longer exists. Please use node_get_types('module', $node) instead.

New hook_node_type()

As a result of the changes to the node type system (above), node types can now be modified by site administrators. The new hook_node_type() allows modules to respond to changes to a node type.

The hook has two parameters: $op, which defines the operation being performed on the node type (either insert, update, or delete); and $info, which is an object containing all of the attributes of the node type:

<?php
function node_node_type($op, $info) {
  if (!empty(
$info->old_type) && $info->old_type != $info->type) {
   
$update_count = node_type_update_nodes($info->old_type,
     
$info->type);

    if (
$update_count) {
     
$substr_pre = 'Changed the content type of ';
     
$substr_post = strtr(' from %old-type to %type.', array(
       
'%old-type' => theme('placeholder', $info->old_type),
       
'%type' => theme('placeholder', $info->type)));
     
drupal_set_message(format_plural($update_count, $substr_pre
       
.'@count post'. $substr_post, $substr_pre .'@count posts'.
       
$substr_post));
    }
  }
}
?>

As the node.module implementation of the hook (above) demonstrates, one of the most common uses of the hook is to update all database references when the machine-readable name of a node type changes. This is very important, because the machine-readable name is no longer a constant value: it can be edited by a site administrator (for many node types), just like most other fields of a node type can be edited. Any modules that use the machine-readable node type name as a reference field in their database tables need to implement hook_node_type(), in order to keep these references valid.

Changes to node type settings form

Modules can altered the 'default settings' form for any node type, adding an additional workflow option, for example. In 4.7.x, the form_id of that form changed for each node type. Now, the form_id is always 'node_type_settings'. In 4.7.x, fields added to this form were always saved to the settings table using the field name as a key. In 5.x, the node type is appended to the field name.

In Drupal 4.7.x:

<?php
function mymodule_form_alter($form_id, &$form) {
  if (
strpos($form_id, '_node_settings') !== FALSE) {
   
$node_type = str_replace('_node_settings', '', $form_id);
   
$form['workflow']['my_module_settings_'. $node_type] = array(
     
'#type' => 'checkbox',
     
'#title' => t('My module settings go here'),
     
'#default_value' => variable_get('my_module_settings_'. $node_type, 0),
    );
  }
}
?>

Now, in 5.x, that should look like:

<?php
function mymodule_form_alter($form_id, &$form) {
  if (
$form_id == 'node_type_form') {
   
$node_type = $form['old_type']['#value'];
   
$form['workflow']['my_module_settings'] = array(
     
'#type' => 'checkbox',
     
'#title' => t('My module settings go here'),
     
'#default_value' => variable_get('my_module_settings_'. $node_type, 0),
    );
  }
}
?>

New db_table_exists()

This function (which works under MySQL and PostgreSQL) will indicate if the given table exists in the database.

New hook_node_operations, hook_user_operations()

These hooks function identically, one for the mass operations found at admin/content/node, and the the other at admin/user/user. They allow for modules to inject custom operations into the dropdown menus, which are then executed via a declared callback function when the form is submitted (the user is subsequently directed back to the same url by default). Here's a short example of hook_node_operations in action:

  • The hook:
    <?php
     
    function node_node_operations() {
       
    $operations = array(
         
    'approve' => array(
           
    'label' => t('Approve the selected posts'),
           
    'callback' => 'node_operations_approve',
          ),
         
    'promote' => array(
           
    'label' => t('Promote the selected posts'),
           
    'callback' => 'node_operations_promote',
          ),
        );
        return
    $operations;
      }
    ?>
  • One of the callbacks:
    <?php
     
    function node_operations_approve($nodes) {
       
    db_query('UPDATE {node} SET status = 1 WHERE nid IN(%s)',
         
    implode(',', $nodes));
      }
    ?>

Altered the behaviour of placeholders in t() calls

The t() function was changed to be able to transparently escape and format its arguments to be safe for output. The idea is that where you previously called check_plain() or theme('placeholder') on an argument before passing it to t(), this is now done for you, based on the first character of the substitution key:

<?php
   
// Before:
   
print t('%type: %title was posted',  array('%type' => check_plain($node->type),  '%title' => theme('placeholder', $node->title)));
    print
t('Submitted by %name', array('%name' => theme('username', $node)));

   
// After:
   
print t('@type: %title was posted',  array('@type' => $node->type'%title' => $node->title));
    print
t('Submitted by !name', array('!name' => theme('username', $node)));
?>

Note that in the first print statement, we removed the calls to check_plain() and theme('placeholder') as they are now applied automatically to arguments whose key starts with respectively '@' and '%'. In the second case, there was no escaping before, so we use the '!' prefix to insert the argument as is.

Using the above code as a template for upgrading your module only works if your module already used check_plain() and theme('placeholder') where appropriate. Remember that it is important to use them in output to avoid XSS security problems.

If you are not sure when to use them, it is better to try '%' or '@' first, and only switch to '!' when the first two are causing problems. It is advised to read the book page about dealing with text in Drupal in a secure way as it contains coding guidelines and examples of good and bad code.

And obviously, you could just be lazy and replace all '%placeholders' with '!placeholders' to get the same behaviour as before. But that doesn't help you make your code more readable.

drupal_get_form() now takes a $form_id, not a $form

Forms must be created in dedicated builder functions

In Drupal 4.7.x, forms were built using a 'push' model: a form array was constructed and passed to the drupal_get_form() function. Now, in 5.x, each form is built in a dedicated function and identified by its form_id. drupal_get_form() only needs to know that form_id to properly load, process, and render the form.

By default, the Forms API will check for a function that shares the same name as the form's ID.

In Drupal 4.7.x:

<?php
function mymodule_edit_record($record) {
 
$output = t('This is my edit page!');
 
$form['my_field'] = array(
   
'#type' => 'textfield',
   
'#title' => 'Record name',
   
'#default_value' => $record->name,
  );
 
$output .= drupal_get_form('record_edit_form', $form);
  return
$output;
}
?>

In 5.x that code should look like:

<?php
function mymodule_edit_record($record) {
 
$output = t('This is my edit page!');
 
$output .= drupal_get_form('mymodule_edit_record_form', $record);
  return
$output;
}

function
mymodule_edit_record_form($record) {
 
$form['my_field'] = array(
   
'#type' => 'textfield',
   
'#title' => 'Record name',
   
'#default_value' => $record->name,
  );
  return
$form;
}
?>

hook_forms() optionally maps form_ids to builder functions

Modules can now implement hook_forms() to handle complex mapping of form IDs to builder functions. It's how node.module maps the id for each node type editing form to the central form-building function for the node edit screen. Here's an example:

<?php
function mymodule_forms() {
 
$forms['mymodule_first_form'] = array(
   
'callback' => 'mymodule_form_builder',
   
'callback arguments' => array('some parameter'),
  );
 
$forms['mymodule_second_form'] = array(
   
'callback' => 'mymodule_form_builder',
   
'callback arguments' => array('a different parameter'),
  );
  return
$forms;
}

function
mymodule_first_page() {
  return
drupal_get_form('mymodule_first_form');
}

function
mymodule_second_page() {
  return
drupal_get_form('mymodule_second_form');
}

function
mymodule_form_builder($param) {
 
$form = array()
 
// build the form here

 
if ($param == 'some parameter') {
   
// Add another field, change a default value...
 
}

 
// This is used the way $callback was in 4.7.x Forms API: it is used as the prefix for
  // _submit() and _validate() functions to process the form.
 
$form['#base'] = 'mymodule_form';

  return
$form;
}
?>

In the above code, the mymodule_first_page() and mymodule_second_page() functions display slight variations on the same form. Because no functions named 'mymodule_first_form' or 'mymodule_second_form' exist, Forms API looks to hook_forms() to find the builder function. Each entry in the array returned by hook_forms() can also define callback arguments that will be passed to the builder function.

In the mymodule_form_builder() function, the use of $form['#base'] is also important to note. It's a lot like the little-used but helpful $callback parameter that was used in the 4.7.x version of drupal_get_form(). If $form['#base'] is set, its value will be used to look up the proper submit, validate, and theme functions for the form rather than the form's ID.

New drupal_execute() function allows form data to be submitted programmatically

Forms can now be built and submitted programmatically without any user input using the drupal_execute() function. Pass in the id of the form, the values to submit to the form, and any parameters needed by the form's builder function. For example:

<?php
// register a new user
$values = array();
$values['name'] = 'robo-user';
$values['mail'] = 'robouser@example.com';
$values['pass'] = 'password';
drupal_execute('user_register', $values);

// Create a new node
$node = array('type' => 'story');
$values = array();
$values['title'] = 'My node';
$values['body'] = 'This is the body text!';
$values['name'] = 'robo-user';
drupal_execute('story_node_form', $values, $node);
?>

Calling form_get_errors() after execution will return an array of any validation errors encountered.

module_exist() is now module_exists()

For consistency with PHP function_exists, file_exists, etc. That's about all there is to say about that. :)

format_plural() @count change

This function used to substitute a number where you placed %count. Instead, use @count now. This is a result of the t() changes described above.

Vastly extended drupal_add_js()

The function to add JavaScript to a Drupal page has been reworked:

Definition

drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE)
includes/common.inc, line 1329

Description

Add a JavaScript file, setting or inline code to the page.

The behavior of this function depends on the parameters it is called with. Generally, it handles the addition of JavaScript to the page, either as reference to an existing file or as inline code. The following actions can be performed using this function:

  • Add a file ('core', 'module' and 'theme'): Adds a reference to a JavaScript file to the page. JavaScript files are placed in a certain order, from 'core' first, to 'module' and finally 'theme' so that files, that are added later, can override previously added files with ease.
  • Add inline JavaScript code ('inline'): Executes a piece of JavaScript code on the current page by placing the code directly in the page. This can, for example, be useful to tell the user that a new message arrived, by opening a pop up, alert box etc.
  • Add settings ('setting'): Adds a setting to Drupal's global storage of JavaScript settings. Per-page settings are required by some modules to function properly. The settings will be accessible at Drupal.settings.

Parameters

$data (optional) If given, the value depends on the $type parameter:

  • 'core', 'module' or 'theme': Path to the file relative to base_path().
  • 'inline': The JavaScript code that should be placed in the given scope.
  • 'setting': An array with configuration options as associative array. The array is directly placed in Drupal.settings. You might want to wrap your actual configuration settings in another variable to prevent the pollution of the Drupal.settings namespace.

$type (optional) The type of JavaScript that should be added to the page. Allowed values are 'core', 'module', 'theme', 'inline' and 'setting'. You can, however, specify any value. It is treated as a reference to a JavaScript file. Defaults to 'module'.

$scope (optional) The location in which you want to place the script. Possible values are 'header' and 'footer' by default. If your theme implements different locations, however, you can also use these.

$defer (optional) If set to TRUE, the defer attribute is set on the <script> tag. Defaults to FALSE. This parameter is not used with $type == 'setting'.

$cache (optional) If set to FALSE, the JavaScript file is loaded anew on every page call, that means, it is not cached. Defaults to TRUE. Used only when $type references a JavaScript file.

Return value

If the first parameter is NULL, the JavaScript array that has been built so far for $scope is returned.

Removed drupal_call_js()

As it is now very easy to attach JavaScript to a page, this function is not needed anymore. Use this syntax instead:

<?php
drupal_add_js
('myCustomFunction(your, parameters, here)', 'inline');
?>

If you have more complex parameters that should be passed to the function, consider using drupal_to_js() to convert them to a JSON object before.

Note: It is recommended to not call functions in the page header directly. Instead use unobtrusive JavaScript that automatically runs when the page is loaded.

New drupal_add_feed() and drupal_get_feeds replaces theme_add_link()

There is a new function drupal_add_feed() which you should use to add feeds to a page. This function takes the URL and the title of the feed and automatically adds the link to the feed in the HEAD for autodiscovery, along with adding an appropriate feed icon to the page itself.

To get an array of these feeds, call drupal_get_feeds().

Appropriately, in your theme there is a new $feed_icons so you can move these about

theme('page') may omit standard blocks

Modules that want more control over their presentation can call theme('page', $content, FALSE) to avoid outputting standard blocks on the page. That third parameter is new.

New #disabled Form API property

Before, to mark an input field such as a textfield or select box "disabled" (greyed-out), you had to do the following:

<?php
$form
['textfield'] = array(
  ...
 
'#attributes' => array('disabled' => 'disabled'),
);
?>

Now, you can simply do:

<?php
$form
['textfield'] = array(
  ...
 
'#disabled' => TRUE,
);
?>

Changed cache API

The cache functions have changed. cache_set now requires a table name as the second parameter if you don't want to use the standard cache table. The same is true for cache_get. cache_clear_all called with no arguments will only invalidate the page cache in the new table cache_page. To clear expirable items from a specific table use cache_clear_all(NULL, 'tablename').

There are now four cache tables in core: cache, cache_page, cache_filter, cache_menu.

Uninstall hook

In module.install, you can now place an "uninstall" hook, which drops tables and deletes any variables that the module adds. For example:

<?php
/**
* Implementation of hook_uninstall().
*/
function profile_uninstall() {
 
db_query('DROP TABLE {profile_fields}');
 
db_query('DROP TABLE {profile_values}');
 
variable_del('profile_block_author_fields');
}
?>

Added jQuery to Drupal

The addition of jQuery has significantly altered drupal.js:

  1. All drupal.js functions are namespaced with Drupal. For example, if you called absolutePosition(...) before, you should now call Drupal.absolutePosition(...).
  2. The JsEnabled() killswitch has been changed from a function into a boolean. In practice, you just remove the () when doing the check: if (Drupal.JsEnabled) ....
  3. addLoadEvent() was removed in favor of jQuery's own $(document).ready() function.

Drupal includes the complete jQuery 1.0.1 library (without plug-ins). Check the jQuery website for its documentation.

$_POST[op] deprecated in favor of $form_values[op]

For better form api manageability, your validate and submit handlers should no longer inspect the value of $_POST['op'] to determine what button was pressed (for example). Instead, the $form_values array which is passed as the second parameter includes an op element.

Change menu item and node links to use Sentence capitalization instead of lowercase

Drupal 5 will require contrib modules to change menu item and links to use Sentence capitalization instead of lowercase. Here are some common places to look for capitalization changes to your modules:

  • Your module's hook_node_info() should now include 'name' to be t('My module') instead of t('my module')
  • Menu items defined in hook_menu()
  • Links defined in hook_link()
  • Module blocks defined in hook_block()

Major changes to node_access system

The node access system now requires that only node.module touch the node_access table directly. In the old method, a module wrote records to the node_access table at its discretion, meaning multiple modules couldn't successfully use the table. Instead, you must now use hook_node_access_records to tell Drupal what node_access records you want to write for a given node.

In addition, when you want to force the node module to rewrite the access records for a given node, use node_access_acquire_grants which will go through the process of collecing node access records to write. Also see the node access example module for a more complete description of how to deal with the node access system.

Changes to confirm_form()

The core confirm_form() function has been modified to use the new model of form builder functions. Not only have the arguments for confirm_form() changed, but how it must be invoked has changed as well. For example:

4.7.x:

<?php
function foo_delete_confirm() {
 
$node = node_load(arg(1));
 
$form['nid'] = array('#type' => 'value', '#value' => $node->nid);
 
$output = confirm_form('foo_delete_confirm', $form,
     
t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $node->title))),
     
'node/'. $node->nid, t('This action cannot be undone.'),
     
t('Delete'), t('Cancel')  );
  }
  return
$output;
}
?>

Now, in 5.x, this must be something like:

<?php
function foo_delete_confirm_page() {
  return
drupal_get_form('foo_delete_confirm', arg(1));
}

function
foo_delete_confirm($nid) {
 
$node = node_load($nid);
 
$form['nid'] = array('#type' => 'value', '#value' => $node->nid);
  return
confirm_form($form,
     
t('Are you sure you want to delete %title?', array('%title' => $node->title)),
     
'node/'. $node->nid, t('This action cannot be undone.'),
     
t('Delete'), t('Cancel')  );
  }
}
?>

(Note that this example also includes the change to the behavior of placeholders in t()). Also, keep in mind that the submit handler for your confirm form must be named to match the name of the form builder function. So, continuing the above example, the submit handler (the code to execute if the user confirms the operation), would be:

<?php
function foo_delete_confirm_submit() {
 
// Your logic here
}
?>

For more details, please see: http://api.drupal.org/api/5/function/confirm_form

Changes to how #prefix and #suffix are rendered in form arrays

In 4.7.x, if you put something in $form['#prefix'] it was included inside the <form> HTML that was rendered for your form. So, for example, if you used this in a node-type form:

$form['#prefix'] = '<div class="your-custom-node-type">

the resulting HTML gave you the <div class="node-form"> before your custom div from the #prefix. Now, the #prefix is always rendered outside the <form>. This could impact any custom CSS your module is relying on. For example, if you used to do this:

4.7.x:

.node-form .project ... {
  /* something interesting for project nodes */
}

you now need to change the order of the classes in your .css file, like this:

5.x:

.project .node-form ... {
  /* something interesting for project nodes */
}

Changes to how #options are represented in some form arrays

Due to a critical bug, select elements in form arrays that were trying to represent choices where there were multiple options with the same label (for example, inside multiple hierarchy taxonomies) would fail to work correctly. As a result, there is now a new Object-based way you can optionally represent the #options array you define for select form elements. This allows you to have unique select indexes (the keys in the #options array) and still have multiple options with the same label.

Here's how it works. Instead of just doing this:

<?php
foreach (array('foo', 'bar', 'foo') as $choice) {
 
$options[$choice] = $choice// kills the original 'foo' when handling the last one...
}
$form['baz']['#options'] = $options;
?>

You can now do this:

<?php
$options
= array();
foreach (array(
'foo', 'bar', 'foo') as $choice) {
 
$obj = new stdClass();
 
$obj->option = array($choice => $choice);
 
$options[] = $obj;
}
$form['baz']['#options'] = $options;
?>

That's all well and good if you're writing your own array for #options. However, if you're on the other end of this change, and trying to manipulate the #options array someone else has already created (for example, from inside hook_form_alter()), this change can create quite a headache.

For example, if you're trying to see if the key '17' exists in the array of options, you can no longer just use something like:

if (isset($form['baz']['#options'][17])) {
  ...
}

Luckily, there's a new helper method to ease your pain: form_get_option_key(). So, now you can do something like this:

<?php
$key
= form_get_option_key($form['baz'], 17);
if (
$key !== FALSE) {
 
// Do something interesting
}
?>

Unfortunately, this helper *only* works if the incoming #options array is using the crazy new object syntax. The $form['taxonomy'] array always uses the objects, so if your module is trying to manipulate that, you should use the new method. Otherwise, you can fall back to the old approach and just look in $form['baz']['#options'] directly.

$node->moderate no longer used by core

Unless your module specifically deals with moderation of nodes, all code referencing $node->moderate or the moderate column in node table should be removed. This column in the database is still present for modules who want to implement node moderation logic. See modr8.module for an example of a module which does this.

hook_view() should return a node

drewish - August 14, 2006 - 20:06

Make sure that when you update your implementation of hook_view() you aren't trying to make changes to $node by reference, you need to return the updated $node.

node_get_name() has been removed

drewish - August 16, 2006 - 18:49

node_get_name($node); becomes node_get_types('name', $node);

module.info

rszrama - September 1, 2006 - 15:23

Modules need a .info file to be picked up by admin/settings/modules (and I'm presuming everywhere else?). It's a very simple two line deal looking something like:


name = Test
description = Test module for me to dink around with.

practical information about .info files

ontwerpwerk - September 4, 2006 - 11:40

You need to escape strings containing non alphanumeric characters with quotes .. so that means any text with braces, tildes, slashes, other quotes etc.

These .info file functions rely on the php parse_ini_file function http://www.php.net/manual/function.parse-ini-file.php

--
I work for Ontwerpwerk

.info files translation

KarenS - September 2, 2006 - 12:04

Do .info files descriptions and names get translated? Should it be t('Here is my module') or just 'Here is my module'?

More on .info files

KarenS - September 3, 2006 - 13:06

Not noted above is that one reason for this change is to avoid the white screen of death on the modules page since this info will be parsed instead of loading all modules into memory (hooray!!)

It looks like you should put non-translated info into the name and description, but I couldn't figure out where the translation is happening. Also important, you cannot use double quotes in the description.

Documentation is in module.inc (_module_parse_info_file() function) and the reasoning and usage for .info files is discussed at http://drupal.org/node/80952.

Yes they are translated

ChrisKennedy - September 6, 2006 - 08:02

Translation occurs in system_modules(), in particular:
$form['description'][$file->name] = array('#value' => t($info['description']));.

New Section on drupal.js?

jvandervort - September 5, 2006 - 17:56

How about a new section on drupal.js

Changes I've found: Removal of addLoadEvent and new drupal namespace for js functions

Examples:
isJsEnabled() to Drupal.jsEnabled
addLoadEvent(somefunc) to $(document).ready(somefunc);

-John

other jQuery changes

snufkin - April 4, 2007 - 17:11

Also the hasClass, addClass and removeClass have been removed, you can use the jQuery strings:
$(ELEMENT.CLASS).attr(), $(E.CLASS).removeClass() should be used.

See jQuery documentation for more detail.

#process

ufku - November 1, 2006 - 21:10

process function(used for integrating wysiwyg editors into textareas) is now called with 2 parameters before the custom parameters.

<?php

//before
mymodule_process_textarea($element, $arg0, $arg1, ...) {
  ...
}

//after
mymodule_process_textarea($element, $edit=NULL, $arg0, $arg1, ...) {
  ...
}
?>

--
Geneticists from METU

Note on hook_nodeapi($op = 'alter')

jjeff - November 4, 2006 - 13:08

The hook_nodeapi($op = 'alter') example above shows altering the rendered teaser or body on output to the page. Please note that this type of behavior is usually better handled with hook_filter() as filtered text is cached (and thus more efficient) and filters are also controllable by the administrator.

--= Jeff Robbins | www.lullabot.com =--

Very true!

Eaton - November 7, 2006 - 01:22

It really only exists for a few special cases, like 'inline' module, which needs the context of the node itself (file attachments, etc) in order to do its processing. The 'filter' op doesn't get any context, only a snippet of text.

--
Lullabot! | Eaton's blog | VotingAPI discussion

I believe the constant for

rszrama - November 20, 2006 - 21:22

I believe the constant for Javascript enabled is wrong... that didn't work for me, but I've seen Drupal.jsEnabled in other code. I'm guessing that's the right one?

description = containing "quoted" words

earnie - December 6, 2006 - 22:22

Be careful with your .info file description values. The php parse_ini_file function will give issues in the module.inc _module_parse_info_file function. If your description looks like

description = This is the "description" of "my" module.

where " is a double quote you may change it to
description = This is the ''description'' of ''my'' module.

where '' are two single quote characters.

Earnie Boyd
http://For-My-Kids.Com
http://Give-Me-An-Offer.com
http://AffiliationMaster.com