ARCHIVE: Converting 4.6.x modules to 4.7.x

Last modified: October 26, 2009 - 21:35

Overview of Drupal API changes in 4.7

  1. new handling of return values from callbacks: must know.
  2. new node definition system: must know.
  3. node_load(): must know.
  4. node_save(): must know.
  5. node_list(): moderately used.
  6. node titles now handled by node modules: must know
  7. node_get_module_name() → node_get_base(): minor required change.
  8. format_name() → theme('username'): minor required change.
  9. theme_table(): minor required change.
  10. check_output() → check_markup(): infrequently used, security requirement.
  11. XML-RPC: infrequently used, backported to 4.6.3 for security reasons.
  12. taxonomy_save_vocabulary(), taxonomy_save_term(): advanced use, taxonomy specific.
  13. message_access() removed.
  14. Unicode string API: must know.
  15. conf_url_rewrite(): rarely used.
  16. revisions overhaul: important for node modules.
  17. Upgrading to forms API
  18. node_delete(): moderately used
  19. New order of node hooks
  20. hook_nodeapi('settings', ...) replaced by form api.
  21. hook_nodeapi('form', ...) replaced by form api.
  22. file_directory variables: moderately used, replaced by functions
  23. array2object moderately used: replace by native PHP conversion
  24. user_load moderately used: different return value when user not found.
  25. UTF-8 SQL conversion: required.
  26. We no longer use the <base> element.
  27. hook_onload replaced by addLoadEvent()
  28. database creation instructions, updates, and other module setup code go in .install files.
  29. hook_search_item was replaced by hook_search_page.
  30. node_validate_title was removed.
  31. tablesort_pager was removed.

New handling of return values from callbacks

Most (menu) callbacks used print theme('page', $output); to return HTML code. To enable the reuse of any callback in blocks, sub-pages, etc callbacks are no longer expected to call theme('page', ...):

  • If you return something (return $output;), then print('page', ...); is called.
  • If you do not return anything, it will assume you have handled your own output.

In general, there is no longer need to use theme('page', ...); in your module.

  • Impacts most modules.
  • Importance: recommended.
  • Compatibility: old code will still work, but it is deprecated -- you can expect it to break any time.
  • Affected code: any use of theme('page', ...).

Example:

<?php
// Drupal 4.6
function mymodule_admin_page(...) {
 
$output .= "some content";
  print
theme('page', $output);
}

// Drupal 4.7
function mymodule_admin_page(...) {
 
$output .= "some content";
  return
$output;
}
?>

node definition changes

In 4.6, you had hook_node_name and hook_node_types. Now you must implement hook_node_info if you want to create a module which defines node type(s). The implementation of the hook_node_info() needs to:

<?php

    
return array($type1 => array('name' => $name1, 'base' => $base1),
                 
$type2 => array('name' => $name2, 'base' => $base2));

?>

where $type is the node type, $name is the human readable name of the type and $base is used instead of
<hook>
for
<hook>
_load()
,
<hook>
_view()
, etc.

For example, the story module's node_info hook looks like this:

<?php

function story_node_info() {
  return array(
'story' => array('name' => t('story'), 'base' => 'story'));
}
?>

The page module's node_info hook looks like:
<?php
function page_node_info() {
  return array(
'page' => array('name' => t('page'), 'base' => 'page'));
}
?>

However, more complex node modules like the project module and the flexinode module can use the 'base' parameter to specify a different base.

The project module implements two node types, projects and issues, so it can do:

<?php
function project_node_info() {
  return array(
    array(
'project_project' => array('name' => t('project'), 'base' => 'project'),
    array(
'project_issue' => array('name' => t('issue'), 'base' => 'project_issue'));
}
?>

In the flexinode module's case there can only be one base.

Now you have node_get_name($type) to get the name of a node type and node_get_base($type) to get the base of a node type.

  • Impacts most modules.
  • Importance: absolute must.
  • Compatibility: old code will not work
  • Affected code: any use of node_name hook, node_types.

node_load() changes

You should write $node = node_load($nid); instead of $node = node_load(array('nid' => $nid));. The array syntax is for cases when you are not using $nid or need to add more fields.

  • Impacts many modules.
  • Importance: suggested.
  • Compatibility: old code will still work, but is not as efficient: by passing an array, the node_load() cache is not used resulting in lower performance.
  • Affected code: any use of node_load(array('nid' => $nid)); can be replaced by node_load($nid).
  • Docs: node_load().

The only use of node_load() that should be changed are the ones that only pass the $nid:

<?php
// Drupal 4.6
$node = node_load(array('nid' => $nid));

// Drupal 4.7
$node = node_load($nid);
?>

node_save() changes

node_save() now receives the $node parameter by reference, and modifies the object as needed. It has no return value anymore.

node_list() changes

node_list() became node_get_types now returns an associative array about node types. The types are now returned as the keys of this arrays (formerly they were returned as the values). The values are now the relevant human readable names.

  • Impacts some modules.
  • Importance: required.
  • Compatibility: old code will not work as the types are now the keys of the returned array and not the values (and the function name is changed, too).
  • Affected code: any use of node_list().
  • Docs: node_get_type().

The most typical use is:

<?php
// Drupal 4.6
foreach (node_list() as $type) {

// Drupal 4.7
foreach (node_get_types() as $type => $name) {
?>

Node titles now handled by node modules

Formerly hard-coded into the node.module, titles are now handled by the individual node modules.

In many cases, the only change needed is to add a title field at the beginning of the hook_form() hook, like this:

<?php
function modulename_form(&$node) {
 
$form['title'] = array('#type' => 'textfield',
   
'#title' => t('Subject'),
   
'#default_value' => $node->title,
   
'#size' => 60,
   
'#maxlength' => 128,
   
'#required' => TRUE);
...
?>

  • Impacts: all node modules.
  • Importance: required.
  • Compatibility: old code will not work.
  • Affected code: hook_form() need to be edited to add the title field.

module_get_node_name deprecated

Fetching a node type's module name is now handled by the node_get_base function.

Example:

<?php
// Drupal 4.6
$module = node_get_module_name($type);

// Drupal 4.7
$module = node_get_base($type);
?>

format_name() renamed

For consistency and enabling theming, format_name() was renamed to theme_username() and should be invoked using the theme('username', ...) API. Every use of format_name() must be replaced.

  • Impacts: few modules.
  • Importance: required.
  • Compatibility: old code will not work.
  • Affected code: any use of format_name(...) should be replaced by theme('username', ...).
  • Docs: format_name() (Drupal 4.6) and theme_username() (Drupal 4.7).

Example:

<?php
// Drupal 4.6
$output = t('by %username', array('%username' => format_name($node)));

// Drupal 4.7
$output = t('by %username', array('%username' => theme('username', $node)));
?>

theme_table() change

theme('table', ...); sometimes was called with arguments set to NULL or an empty string ('') to indicate that there were either no rows or no header. This is no longer allowed: the $rows and $header parameters now have to be an array. If you have no rows or no header to pass to theme('table', ...); you have to pass an empty array.

  • Impacts some modules.
  • Importance: required.
  • Compatibility: old code will not work if you pass NULL or an empty string.
  • Affected code: any use of theme('table', ...) where you pass no rows or no header.
  • Docs: theme_table().

Example:

<?php
// Drupal 4.6
theme('table', '', $rows);

// Drupal 4.7
theme('table', array(), $rows);
?>

check_output() change

Due to a security vulnerability discovered in the filter system, we have tightened security around the check_output() function. The format passed to check_output() is now checked for access by default. If you don't want this check, pass FALSE for the third parameter, $check. check_output() was renamed to check_markup() to enforce this change. The new syntax is:

<?php
function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $check = TRUE) {
?>

Note that if you disable the check by passing FALSE, you need to make sure the $format value has been checked by filter_access() before. filter_access() checks the permissions of the current user, so it should be checked on submission, not on output.

In general you will want to use check_markup($text, $format, TRUE) prior to validating or saving the $text and use check_markup($text, $format, FALSE) when viewing the $text.

Please review any use of check_markup() carefully!

  • Impacts few modules.
  • Importance: required.
  • Compatibility: old code will not work.
  • Affected code: any use of check_output() should be checked (and double checked!) before replacing it with check_markup().
  • Docs: check_output() (Drupal 4.6), check_markup() (Drupal 4.7) and filter_access().

XML-RPC changes

As this happened between 4.6.2 and 4.6.3, there is a separate guide for this.

  • Impacts few modules.

Taxonomy API change

In order to provide more meaningful messages to the user, now you can provide your own messages when using the taxonomy APIs to create or modify terms and vocabularies. The taxonomy_save_vocabulary() and taxonomy_save_term() functions now return a status message which can be either SAVED_NEW, SAVED_UPDATED or SAVED_DELETED. The function will return NULL when there was an error. Also note that you must call these with an $edit array parameter which will be modified, and will include the 'vid' key.

  • Impacts few modules.
  • Importance: required.
  • Compatibility: old code will not work as expected: no feedback is given to the user anymore. If you used the return value of the functions, you should be aware that this return value has changed.
  • Affected code: any use of taxonomy_save_vocabulary() or taxonomy_save_term().
  • Docs: taxonomy_save_vocabulary() and taxonomy_save_term().

For example, this snippet shows you a typical handling of the returned status values:

<?php
// Drupal 4.6
taxonomy_save_vocabulary($edit);
?>

<?php
// Drupal 4.7
switch (taxonomy_save_vocabulary($edit)) {
  case
SAVED_NEW:
   
drupal_set_message(t('Created new vocabulary %name.', array('%name' => theme('placeholder', $edit['name']))));
    break;
  case
SAVED_UPDATED:
   
drupal_set_message(t('Updated vocabulary %name.', array('%name' => theme('placeholder', $edit['name']))));
    break;
  case
SAVED_DELETED:
   
drupal_set_message(t('Deleted vocabulary %name.', array('%name' => theme('placeholder', $deleted_name))));
    break;
}
?>

message_access() removed

The message_access() function was removed. Replace all occurrences with a nice case error message that is specific to the error that has been caught.

Unicode string API

Drupal now provides some wrappers for doing string handling in the most language-independent fashion. The following functions are available and behave exactly as their PHP counterparts, except that they count and work with Unicode characters in UTF-8 encoding and not literal bytes: drupal_strlen(), drupal_strtolower(), drupal_strtoupper(), drupal_substr(), drupal_ucfirst().

If you use any of the original functions in your own module, you should almost certainly replace them with their Drupal counterparts. Only use the plain PHP string API when you want to work with the literal bytes.

Note that strpos() is not mirrored, because it can still be safely used. If you want to chop off a string at a location found by strpos(), you should use substr() instead of drupal_substr().

For more information, see the API reference.

conf_url_rewrite() became custom_url_rewrite()

Watch out for changes in the URL rewriting process if you have a module or settings file, which implements mass URL aliasing. The conf_url_rewrite() you are expected to implement if you are about to provide mass URL aliasing in Drupal was renamed to custom_url_rewrite(). Now this function is always called on all URL aliasing request, so if you would not like to mangle already aliased URLs, you need to take action yourself.

<?php
// Drupal 4.6
function conf_url_rewrite($path, $mode = 'incoming') {
 
// $mode is either 'incoming' or 'outgoing' and function
  // is only called if there is no system alias found
}
?>

<?php
// Drupal 4.7
function custom_url_rewrite($type, $path, $original) {
 
// $type is either 'alias' or 'source', depending on the
  // desired resulting path type of the operation (to be
  // in line with drupal_lookup_path() operation),
  // $path is possibly already processed by Drupal,
  // $original is the originally passed path

 
if ($path == $original) {
   
// path is not yet aliased (this is the only case
    // conf_url_alias() was invoked in earlier versions)
 
}
}
?>

node_delete(): moderately used

The confirmation screen has been abstracted from the function. node_delete now strictly handles deletion of a node. It no longer takes an array as an argument--instead pass the nid of the node you wish to delete.

Instead of:

node_delete(array('nid' => $node->nid, 'confirm' => 1));

Now use:

node_delete($node->nid);

If a confirmation screen is desired, redirect to node/[nid]/delete, where [nid] is the nid of the node to delete.

New order of node hooks

There is a new order in the way node hooks are called in 4.7 vs. 4.6:

Drupal 4.6
1. validate
2. form pre
3. form post
4. if the form was submitted then validate (again)
5. insert/update

Drupal 4.7
1. prepare
2. form (return an array)
3. validate (you can only validate here, no changes possible)
4. submit (prepare the node for save)
5. insert/update

In short, Drupal 4.7 separates out the individual parts of the validation, rather than combining it together into one function.

hook_nodeapi('settings', ...) replaced by form api

Adding elements to the node type settings page is now done exclusively with the form api. Below is an example of the new way to add elements to this form from the node module.

<?php
function node_form_alter($form_id, &$form) {
  if (isset(
$form['type']) && $form['type']['#value'] .'_node_settings' == $form_id) {
   
$form['workflow']['node_options_'. $form['type']['#value']] = array(
     
'#type' => 'checkboxes', '#title' => t('Default options'), '#default_value' => variable_get('node_options_'. $form['type']['#value'], array('status', 'promote')),
     
'#options' => array('status' => t('Published'), 'moderate' => t('In moderation queue'), 'promote' => t('Promoted to front page'), 'sticky' => t('Sticky at top of lists'), 'revision' => t('Create new revision')),
     
'#description' => t('Users with the <em>administer nodes</em> permission will be able to override these options.'),
    );
  }
}
?>

hook_nodeapi('form x', ...) replaced by form api

nodeapi op 'form' is no more. Use hook_form_alter instead. This lets you not just add to the node form but change at will.

See the core modules for examples. The conversation process is the following: let's suppose we have foo.module , and it compiles its nodeapi form in _foo_form which takes one argument, $node. and the last command is return $form (core actually had such functions). Then we can rename _foo_form to foo_form_alter and wrap the code as follows:

<?php
function foo_form_alter($form_id, &$form) {
  if (isset(
$form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
   
$node = $form['#node'];
   
// _form_form code follows:
   
$form['foo']['myfield'] = array(...); 
   
// the final return is not needed because $form is a reference.
 
}
}
?>

file_directory variables replaced by functions

variable_get('file_directory_temp', ...) or variable_get('file_directory_path', ...) should be replaced with calls to file_directory_temp() and file_directory_path(). No arguments are necessary for either function.

array2object replaced by native PHP type conversion

$my_object = array2object($my_array); should be replaced with$my_object = (object) $my_array;.

user_load returns FALSE if a user cannot be loaded

user_load now returns FALSE if a user cannot be loaded, instead of an empty object.

MySQL tables are now always UTF-8 encoded

In order to ensure this, two changes need to be made.

First, all MySQL database tables created (e.g. in a my_module.mysql file) need to have a character set attribute appended to each CREATE TABLE statement:

Drupal 4.6

CREATE TABLE my_table (
...
) TYPE=MyISAM;

Drupal 4.7

CREATE TABLE my_table (
...
) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;

Use the snippet as is, including the comment markers. This ensures your .mysql file still works on MySQL 4.0 and below.

The second change is to upgrade all existing tables. Thanks to the upgrade system, this is very easy. Simple create a my_module.install file in the same directory as your my_module.module file, containing:

<?php
function my_module_update_1() {
  return
_system_update_utf8(array('table1', 'table2', 'table3'));
}
?>

Replace 'table1', 'table2', ... by the names of your module's table(s).

We no longer use the <base> element

In versions of Drupal prior to 4.7, we used the HTML BASE tag to indicate the base to which all relative links should be appended to. In Drupal 4.7, however, the BASE tag has been removed entirely. All Drupal functions that specifically return URLs (such as l() or url() or the various theme_add_style() features) have been updated to now return the base_path() prepended to any URLs. If, however, you are manually creating your own URLs and not using one of Drupal's internal functions, you'll need to do something like:

BEFORE:
print '<link rel="stylesheet" type="text/css" href="themes/chameleon/common.css" />";

AFTER (generic approach):
print '<link rel="stylesheet" type="text/css" href='. base_path() .'"themes/chameleon/common.css" />";

AFTER (strongest specific approach, using Drupal functions):

// this would only apply to styles, naturally
theme('stylesheet_import', base_path() . path_to_theme() ."/common.css");

hook_onload replaced by addLoadEvent()

If you need to add Javascript onLoad events from your modules you now need to use the Drupal custom Javascript function addLoadEvent(func). This was done to allow for new Javacript functions in the core.

<?php
// Drupal 4.6
function hook_onload() {
  return array(
'my_javascript_function()');
}
?>

// Drupal 4.7
// Note: this code is Javascript and should be included in a script tag or in a .js file.
if (isJsEnabled()) {
  addLoadEvent(yourCustomJSFunction);
}

hook_search_item replaced by hook_search_page

This was done to result in cleaner code. Modules must now implement hook_search_page and are expected to provide the necessary themable functions there.

node_validate_title() was removed

Simply specify the '#required' => TRUE attribute on the form item instead, and form API will validate for you.

tablesort_pager() was removed

This function was only ever used in combination with theme('pager'), as the last argument ($parameters). If you simply passed tablesort_pager() here, you can drop this argument altogether. Otherwise you should only specify your custom $parameters:

<?php
// Drupal 4.6
 
$output .= theme('pager', NULL, 50, 0, tablesort_pager());
 
$output .= theme('pager', NULL, 50, 0, $parameters + tablesort_pager());

// Drupal 4.7
 
$output .= theme('pager', NULL, 50, 0);
 
$output .= theme('pager', NULL, 50, 0, $parameters);
?>

user_deny() now known as user_is_blocked()

gordon - October 16, 2005 - 01:19

What more do I need to say...
--
Gordon Heydon
Heydon Consulting

Couple more changes

mikeryan - October 30, 2005 - 21:47

The previous set of form ops (form pre, form post, etc.) to hook_nodeapi() are now replaced by a single 'form' op. The return value should be a form array (see the new form API).

The $object argument to hook_taxonomy() is now optional (omitted on form callbacks), thus needs to be defaulted to NULL:

<?php
function pathauto_taxonomy($op, $type, $object = NULL) {
?>

Mike
Fenway Views

theme('confirm', ...)

mfredrickson - November 28, 2005 - 20:31

The theme('confirm', ...) function has disappeared in favor of confirm_form():

http://drupaldocs.org/api/head/function/confirm_form

Thanks to Dries Knapen for pointing me in the right direction.

-Mark

array2object() and object2array() removed

Chris Johnson - January 3, 2006 - 19:00

Drupal common routines array2object() and object2array() were both removed in favor of the faster, native-PHP casting equivalents:

<?php
  $node
= array2object($node);
 
// becomes:
 
$node = (object) $node;
?>

<?php
  $user
= object2array($user);
 
// becomes:
 
$user = (array) $user;
?>

message_access()

deekayen - January 5, 2006 - 19:19

I replace message_access() with drupal_access_denied(). The docs above say to generate a custom error message, but I found that doesn't work so great for the module settings hook, because the hook still expects to generate a form (albeit empty).

_user_authenticated_id()

Michelangelo - February 1, 2006 - 01:17

The function was replaced by the constant DRUPAL_AUTHENTICATED_RID

drupal_get_path_map() has gone away

dman - February 26, 2006 - 14:23

Hopefully
drupal_lookup_path()
drupal_get_normal_path()
drupal_get_path_alias()
will be good enough.

I needed to lookahead and see if there was indeed an alias set, and the older versions would return the query path even if there wasn't a match, so I was inspecting the drupal_get_path_map() directly.
We now seem to have no way of accessing the path map at all unless you do the SQL yourself.

http://www.coders.co.nz/

AddLoadEvent is a JS function

edrex - March 1, 2006 - 19:47

The sample code calls addLoadEvent from php. This is actually just a JS function.

node_validate

joshk - March 8, 2006 - 17:03

It appears in 4.7 that node_validate no longer returns any result, so if you ever had code that looked like:

<?php
$node
= node_validate($node);
?>

You'll need to find a new way to get what you want, as the above will now set your $node to NULL.

------
Personal: Outlandish Josh
Professional: Trellon

hook_comment parameters

Ayman - April 28, 2006 - 17:47

It looks like the parameters of hook_comment where reversed in 4.7.

4.6: function hook_comment($op, $comment)
4.7: function hook_comment($comment, $op)

--
Ayman Hourieh
http://aymanh.com/

hook_exit behaviour has changed

joel_guesclin - May 31, 2006 - 12:04

I have given a detailed example in a bug report. I found that hook_exit in one of my modules was being called multiple times instead of only once, on exiting (or even - apparently - on entering a page. I have determined the specific cause behind this: another module was calling drupal_set_html_head in hook_menu on (!$may_cache). However, I don't know why this should cause this to happen in 4.7 when it worked fine in 4.6, and I don't know if other modules are likely to affect, or be affected by, this changed behaviour.

taxonomy_node_form no longer available

joel_guesclin - June 14, 2006 - 07:05

In 4.6, this was a very useful function for returning the taxonomy choice fields for a particular node (ie with all the vocabularies appropriate to the node type, and with each vocabulary choice filled in appropriately to the node. This seems to have disappeared without substitute in 4.7

I've raised this point here.

Additional change

Junyor - January 30, 2007 - 20:28

The "changed" column in the users table changed name to "access".

 
 

Drupal is a registered trademark of Dries Buytaert.