Converting 6.x modules to 7.x

Last modified: July 5, 2009 - 18:46

Overview of Drupal API changes in 7.x

Annotation

To help the developers working on the Coder Review and Coder Upgrade modules keep track of changes, an annotation of [X] has been added to each conversion change on this page that has been cross-referenced in the Categorical page.

UNSTABLE-1

  1. [X] Permissions are required to have descriptions
  2. [X] Permissions are no longer sorted alphabetically
  3. [X] _comment_load() is now comment_load()
  4. [X] Module .info files must now specify all loadable code files explicitly.
  5. [X] The hook_menu() and hook_theme() "file" and "file path" keys have been removed.
  6. [X] New permission tables.
  7. [X] Use '#markup' not '#value' for markup.
  8. [X] Comment status values in the database have flipped so they match node status
  9. [X] Rebuild functions have changed names
  10. [X] Use defined constant REQUEST_TIME instead of time()
  11. [X] referer_uri() has been removed
  12. [X] Some #process functions have been renamed
  13. [X] A completely new database API has been added
  14. [X] file_validate_extensions() enforces check for uid=1
  15. [X] file_scan_directory() and drupal_system_listing() use preg regular expressions
  16. [X] Simpler checking for the node form during hook_form_alter()
  17. [X] Update functions in .install files must include a Doxygen style comment
  18. [X] New #text_format property to assign text format selection to fields. Changes 'body' field location in node, comment, block, etc.
  19. [X] Replace drupal_clone() with clone

UNSTABLE-2

  1. [X] Permissions are required to have titles additionally to descriptions
  2. [X] Remove $op from hook_nodeapi and hook_user
  3. [X] In hook_node_info() change 'module' back to 'base' and change 'node' to 'node_content'
  4. [X] Use absolute path (constructed from DRUPAL_ROOT) when including a file
  5. [X] File operations that don't affect the database have been renamed
  6. [X] "administer nodes" permission split into "administer nodes" and "bypass node access"
  7. [X] New hooks: hook_modules_installed, hook_modules_enabled, hook_modules_disabled, and hook_modules_uninstalled
  8. [X] drupal_uninstall_module() is now drupal_uninstall_modules()

UNSTABLE-3

  1. [X] drupal_set_title() uses check_plain() by default
  2. [X] Changed parameters for drupal_add_js() and drupal_add_css()
  3. [X] Changed Drupal.behaviors to objects having the methods 'attach' and 'detach'
  4. [X] Standardized and renamed taxonomy term save/load/delete functions
  5. [X] Added taxonomy term hooks
  6. [X] Removed file_set_status()
  7. [X] Replace 'core', 'module' and 'theme' with 'file' in drupal_add_js()
  8. [X] Parameters to hook_filter() have changed
  9. [X] Parameters to check_markup() have changed

UNSTABLE-4

  1. [X] Schema descriptions are no longer translated
  2. [X] file_scan_directory() now uses a preg regular expression for the no match parameter
  3. [X] use module_implements not module_list when calling hook implementations
  4. [X] New hook_js_alter to alter JavaScript
  5. [X] Changed log out path from 'logout' to 'user/logout' for consistency
  6. [X] node_load() and node_load_multiple()
  7. [X] taxonomy_term_load() and taxonomy_term_load_multiple()
  8. [X] file_load_multiple()
  9. [X] Taxonomy CRUD functions renamed and refactored
  10. [X] New hooks: hook_taxonomy_term_load(), hook_taxonomy_term_insert(), hook_taxonomy_term_update(), hook_taxonomy_term_delete() and hook_taxonomy_vocabulary_load(), hook_taxonomy_vocabulary_insert(), hook_taxonomy_vocabulary_update(), hook_taxonomy_vocabulary_delete()
  11. [X] Code documentation to module.api.php
  12. [X] taxonomy_get_tree()
  13. [X] Move node links into $node->content
  14. [X] Remove $op from hook_block
  15. [X] Parameters for actions_synchronize() have changed
  16. [X] Parameters for drupal_http_request() have changed
  17. [X] db_rewrite_sql() replaced with hook_query_alter()
  18. [X] Removed FILE_STATUS_TEMPORARY
  19. [X] Renamed user_delete() to user_cancel(), likewise renamed hook_user_delete() to hook_user_cancel()
  20. [X] Taxonomy db table names have changed to begin with 'taxonomy_'

UNSTABLE-5

  1. [X] User pictures are now managed files
  2. [X] drupal_set_session() replaces $_SESSION
  3. [X] Ability to reset JavaScript/CSS
  4. [X] Moved statistics settings from admin/reports/settings to admin/settings/statistics and added a new 'administer statistics' permission
  5. [X] Default parameter when getting variables
  6. [X] Menu callbacks and blocks should return an array; hello hook_page_alter()
  7. [X] Block module now optional
  8. [X] Element theming properties used by drupal_render() have changed
  9. [X] Element theme functions should call drupal_render_children()
  10. [X] Replace node_view() with node_build()

UNSTABLE-6

  1. [X] JavaScript should be compatible with other libraries than jQuery
  2. [X] file_scan_directory()'s optional parameters are now an array
  3. [X] External JavaScript can now be referenced through drupal_add_js()
  4. user_load_multiple() and hook_user_load()
  5. [X] jQuery 1.3.x
  6. [X] Settings passed locally to JavaScript Behaviors
  7. [X] file_scan_directory() now uses same property names as file_load()

UNSTABLE-7

  1. [X] Moved filter module administrative URLs from admin/settings/filters/* to admin/settings/filter/*
  2. [X] Added taxonomy_vocabulary_load_multiple()
  3. [X] Added a new top level admin item, 'international'.
  4. [X] Changed hook_menu_link_alter() (removed the $menu parameter)
  5. [X] Standardized API for static variables and resetting them
  6. [X] The function drupal_set_html_head() has been renamed to drupal_add_html_head()
  7. [X] Inline cascading style sheets from drupal_add_css()
  8. [X] Attached JavaScript and CSS for forms
  9. [X] Make sticky tableheaders optional
  10. [X] Save new users and nodes with specified IDS
  11. [X] Parameters swapped in book_toc()
  12. [X] drupal_execute() renamed to drupal_form_submit()
  13. [X] node_invoke_nodeapi() removed
  14. [X] Removed $op "rss item" from hook_nodeapi() in favor of NODE_BUILD_RSS
  15. [X] drupal_eval() renamed to php_eval
  16. [X] "use PHP for settings" permission should be used for all PHP settings rights (replaces "use PHP for block visibility")
  17. [X] Changes to HTTP header functions
  18. [X] drupal_get_form() returns an array
  19. [X] Add Doxygen @file tag to all install files
  20. [X] Add node_delete_multiple().
  21. [X] Renamed drupal_set_content() and drupal_get_content()
  22. [X] Instead of theme('page', ...), think of drupal_set_page_content()

UNSTABLE-8

  1. [X] Node access hooks now have drupal_alter() functions
  2. [X] Hide empty menu categories with access callback
  3. [X] Commenting style - use 'Implement hook_foo().' when documenting hooks.
  4. [X] node_get_types($op) replaced by node_type_get_$op()
  5. [X] Added hook_block_list_alter()
  6. [X] Renamed module_rebuild_cache() and system_theme_data() to system_get_module_data() and system_get_theme_data()
  7. [X] Added string context support to t() and format_plural(), changed parameters
  8. [X] AHAH Processing has changed; new 'callback' member of the array, and the callback must be rewritten
  9. [X] Alternative cache implementations changed.
  10. [X] $teaser parameter changed to $build_mode in node building functions and hooks, $node->build_mode property removed
  11. [X] comment_save() now supports programatic saving
  12. [X] comment_validate() has been removed
  13. [X] Login validation change for distributed authentication modules.
  14. jQuery UI (1.7) was added into core.
  15. comment_node_url() was removed
  16. #theme recommended for specifying theme function
  17. [X] hook_perm() renamed to hook_permission()

Rename user tables

NOTE: This change has been postponed (see issue links below) and is not included in an 'UNSTABLE-n' list above.

(issue) Make all tables singular. User tables (issue).

Drupal 6 schema:

users
users_roles

Drupal 7 schema:

user
user_role

Permissions are required to have titles and descriptions

(issue) released in UNSTABLE_1 and (issue) released in UNSTABLE_2. In the implementation of hook_permission(), the returned array format changed.

Drupal 6 supported this format:

<?php
function example_perm() {
  return array(
'administer my module', ...);
}
?>

Drupal 7 code should use the following format:

<?php
function example_permission() {
  return array(
   
'administer my module' => array(
     
'title' => t('Administer my module'),
     
'description' => t('Perform maintenance tasks for my module'),
    ),
    ...
  );
}
?>

Previously, the values were the permission names; now permission names are the keys and the corresponding value is an array with permission title and description. If you happen to have composite permission names, which contain dynamic values such as content type names, look at the array format generated by node_list_permissions() which is reused by node_permission() and hook_permission() implementations of other core node modules.

NB. While no API changes are involved, user.module will now remove permissions automatically when a module is uninstalled.

Permissions are no longer sorted alphabetically

(issue) Permissions are no longer sorted alphabetically, but are displayed in the order defined in your hook_permission().

The recommended order for permissions is:

  1. administer-related permissions
  2. access-related permissions
  3. other global-level permissions
  4. create-related permissions
  5. edit-related permissions
  6. delete-related permissions
  7. other

_comment_load() is now comment_load()

(issue) _comment_load() was renamed to comment_load() as it is an external-facing API function.

In addition to the function name itself, any menu items that are using the %_comment wildcard will need to change to %comment.

Module .info files must now specify all loadable code files explicitly.

(issue) Drupal now supports a dynamic-loading code registry. To support it, all modules must now declare their code files in the .info file like so:

name = Node
description = Allows content to be submitted to the site and displayed on pages.
...
files[] = node.module
files[] = content_types.inc
files[] = node.admin.inc
files[] = node.pages.inc
files[] = node.install

When a module is enabled, Drupal will rescan all declared files and index all functions and classes it finds. That means any class or function that is to be called indirectly (such as a hook) can live outside the .module file, and it will be included on-demand. Classes will be loaded automatically by PHP when they are first accessed. For functions, replace all calls to function_exists() with drupal_function_exists(), which will load the necessary file and return TRUE or FALSE depending on whether the function now exists. In the case of hooks or any code called via module_invoke*(), node_invoke(), etc. that will be done automatically.

In Drupal 6, menu items and theme elements could declare an include file to be loaded on-demand for that page request. In Drupal 7, that is no longer necessary as such files will be loaded automatically by the registry. The file and file path entries should be removed from both hook_menu() and hook_theme() implementations.

New permission tables.

(Issue) The {permission} table is gone, instead we have a {role_permission} which stores (role ID, permission string) pairs. Thus, each permission granted for a given role ID is a separate row in the table. This update is in system.install so that it will already be complete when any other core or contributed module update is running. This should make any alteration of existing permissions much easier.

Use '#markup' not '#value' for markup.

(Issue) The new default type for items in forms or other structured array data (e.g. node content) passed to drupal_render() is '#type' => 'markup'. In Drupal 6 and earlier, the HTML content was added to the array using the #value attribute. In Drupal 7, this needs to be changed to #markup. This change also applies to those form elements of '#type' => 'item'. This change reduces the confusion between form values and markup and allows the code in drupal_render() to be simplified.

Example 1, from system.admin.inc
In Drupal 6:

<?php
  $screenshot
= $screenshot ? theme('image', $screenshot, t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');

 
$form[$theme->name]['screenshot'] = array('#value' => $screenshot);
?>

In Drupal 7:

<?php
  $screenshot
= $screenshot ? theme('image', $screenshot, t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');

 
$form[$theme->name]['screenshot'] = array('#markup' => $screenshot);
?>

Example 2, from contact.pages.inc
In Drupal 6:

<?php
  $form
['from'] = array(
   
'#type' => 'item',
   
'#title' => t('From'),
   
'#value' => check_plain($user->name) . ' &lt;' . check_plain($user->mail) . '&gt;',
  );
?>

In Drupal 7:

<?php
  $form
['from'] = array(
   
'#type' => 'item',
   
'#title' => t('From'),
   
'#markup' => check_plain($user->name) . ' &lt;' . check_plain($user->mail) . '&gt;',
  );
?>

Comment status values in the database have flipped so they match node status

This handy table shows the status indicator and what the numeric values are:

Version          Type       Published   Unpublished

6.x and before   Nodes      1           0
6.x and before   Comments   0           1
7.x              Nodes      1           0
7.x              Comments   1           0

The major change is to queries and code that interacts with comments.

If you had code that looked something like this in Drupal 6.x or before:

<?php
// Get a list of all unpublished comment IDs.
$results = db_query("SELECT cid FROM {comments} WHERE status = 1");
?>

Here's what it should be in 7.x

<?php
// Get a list of all unpublished comment IDs.
$results = db_query("SELECT cid FROM {comments} WHERE status = 0");
?>

You may also want to use the constants that are defined in the comment module for increased code clarity and future-proofing:

<?php
// Get a list of all unpublished comment IDs.
$results = db_query("SELECT cid FROM {comments} WHERE status = :comment_constant", array(':comment_constant' => COMMENT_NOT_PUBLISHED));
?>

Rebuild functions have changed names

6.x:

<?php
drupal_rebuild_theme_registry
();
drupal_rebuild_code_registry();
?>

7.x:

<?php
drupal_theme_rebuild
();
registry_rebuild();
?>

Use defined constant REQUEST_TIME instead of time().

(Issue) For improved performance, it is highly recommended that any calls to time() are replaced with REQUEST_TIME, a defined constant which will always return the UNIX timestamp from the start of the current request. If you absolutely need to get the current time, you can still use time() but it is not recommended.

referer_uri has been removed.

(Issue) referer_uri() has been removed and replaced with the PHP-provided global variable $_SERVER['HTTP_REFERER']. If there is no referrer, Drupal will automatically set $_SERVER['HTTP_REFERER'] to an empty string.

Some #process functions have been renamed

(Issue) Some functions used for processing form elements (specified by the #form item in the Forms API) have been renamed to unify their overall naming. The follow changes occured:

Old Name
New Name

expand_password_confirm
form_process_password_confirm

expand_date
form_process_date

expand_radios
form_process_radios

form_expand_ahah
form_process_ahah

expand_checkboxes
form_process_checkboxes

process_weight
form_process_weight

If your module defines a new element using hook_elements(), you have to update your #process callbacks.

A completely new database API has been added

(Issue) Drupal 7 introduces a completely new database API, utilizing a number of dynamic query builders and formal prepared statements. The following conversion guide should cover most basic cases, but reading the full documentation is recommended.

Normal Select queries:

<?php
// Drupal 6
$result = db_query("SELECT nid, title FROM {node} WHERE uid=%d AND created < %d", 5, REQUEST_TIME);

// Drupal 7
$result = db_query("SELECT nid, title FROM {node} WHERE uid = :uid AND created < :created", array(
 
':uid' => 5,
 
':created' => REQUEST_TIME,
));
?>

Iterating a result set from db_query()

<?php
// Drupal 6
while ($record = db_fetch_object($result)) {
 
// Do stuff with $record, which is an object
}

// Drupal 7
foreach ($result as $record) {

 
// Do stuff with $record, which is an object
}
?>

Insert statements

<?php
// Drupal 6
db_query("INSERT INTO {mytable} (intvar, stringvar, floatvar) VALUES (%d, '%s', %f)", 5, 'hello world', 3.14);
$id = db_last_insert_id();

// Drupal 7
$id = db_insert('mytable')
  ->
fields(array(
   
'intvar' => 5,
   
'stringvar' => 'hello world',
   
'floatvar' => 3.14,
  ))
  ->
execute();
?>

Update statements

<?php
// Drupal 6
db_query("UPDATE {node} SET title='%s', status=%d WHERE uid=%d", 'hello world', 1, 5);

// Drupal 7
db_update('node')

  ->
fields(array('title' => 'hello world', 'status' => 1))
  ->
condition('uid', 5)
  ->
execute();
?>

Delete statements

<?php
// Drupal 6
db_query("DELETE FROM {node} WHERE uid=%d AND created < %d", 5, time() - 3600);

// Drupal 7
db_delete('node')
  ->
condition('uid', 5)
  ->
condition('created', time() - 3600, '<')
  ->
execute();
?>

For more examples, see the unit tests.

Also note that dynamic queries that have tags may be altered by any module that implements hook_query_alter() or hook_query_TAG_alter().

file_validate_extensions() enforces check for uid=1

(issue) In 6.x the function for validating file extensions would bypass this check for the uid=1 user. This has been removed in 7.x. If your module depended on this behavior you need to check the user's uid and conditionally specify the file_validate_extensions() validator.

file_scan_directory() and drupal_system_listing() use preg regular expressions

(issue) The file_scan_directory() and drupal_system_listing() function now use preg_match() rather than ereg() which allows case-insensitive matching and speed improvements. As a result the formats of the regular expressions these functions accept as parameters have changed.

For most modules the change is as simple as adding a leading and trailing / (forward slash).

Drupal 6.x:

<?php
  
// Get current list of modules
 
$files = drupal_system_listing('\.module$', 'modules', 'name', 0);
?>

Drupal 7.x:

<?php
 
// Get current list of modules
 
$files = drupal_system_listing('/\.module$/', 'modules', 'name', 0);
?>

Modules with more complicated regular expressions should review the PHP documentation for the ereg() and preg_match() functions.

Easier check for node form during hook_form_alter()

Modules wishing to alter the node form may now check for !empty($form['#node_edit_form']) instead of the verbose if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id). See #161301: Make checking for node edit forms easier

Update functions in .install files must include a Doxygen style comment

The Doxygen style comment is displayed to administrators on update.php. Background info - #286035: Remove update.php number dropdowns

New #text_format property to assign text format selection to fields. Changes 'body' field location in node, comment, block, etc.

(issue) In Drupal 6, you have been assigning text formats (formerly known as input formats) to textareas by wrapping the textarea into a parent element, and adding a format selector sibling element, such as:

<?php
  $form
['comment_filter']['comment'] = array(
   
'#type' => 'textarea',

   
'#title' => t('Comment'),
   
'#rows' => 15,
   
'#default_value' => $default,
   
'#required' => TRUE,
  );
  if (!isset(
$edit['format'])) {
   
$edit['format'] = FILTER_FORMAT_DEFAULT;
  }
 
$form['comment_filter']['format'] = filter_form($edit['format']);
?>

There was no standard for naming, parenting the format selector, so it was impossible to identify textareas with text formats attached (compared to plain text input widgets). It was also common to use this construct with a tree structure, so you'd get ['comment_filter']['comment'] and ['comment_filter']['filter'] values or a non-tree structure so that you get ['comment'] and ['format'].

In Drupal 7, this is all strictly defined and automated with the introduction of the #text_format FAPI property. Just specify the text format used by default for the field and Drupal will automatically expand the element to a 'value' and a 'format' element later, so the above code becomes:

<?php
  $form
['comment'] = array(
   
'#type' => 'textarea',
   
'#title' => t('Comment'),
   
'#rows' => 15,
   
'#default_value' => $default,
   
'#text_format' => isset($edit['format']) ? $edit['format'] : FILTER_FORMAT_DEFAULT,
   
'#required' => TRUE,
  );
?>

This is way simpler, and Drupal 7 takes care of the rest. #text_format should have the value of the default text format to use with the selector. Through the form processing, Drupal transform this structure to have 'value' and 'format' children in form_process_text_format(). Although the structure results in ['comment']['value'] and ['comment']['format'] respectively, the $form_values array with the submitted form data contains the comment value in ['comment'] and the format in ['comment_format']. This lets you swap between formatted and non-formatted input easily, without rewriting your code to more complex structures. This new format also lets WYSIWYG editors to attach themselves to widgets with text formats they support.

Replace drupal_clone() with clone

(issue) Since Drupal 7 requires at least PHP 5, it allowed the removal of the drupal_clone function for a direct call to clone.

Drupal 6.x:

<?php
  
// Make a copy of the node
 
$cloned_node = drupal_clone($node);
?>

Drupal 7.x:

<?php
 
// Make a copy of the node
 
$cloned_node = clone $node;
?>

Remove $op from hook_nodeapi, hook_user and hook_block

(hook_nodeapi issue, hook_user issue, released in UNSTABLE-2) (hook_block issue) Due to the addition of the registry, we can dynamically load a bunch of granular hooks instead of just one huge hook with an operation argument. Gábor posted a note in the developer mailing list about this if you'd like to read more about it.

With this addition, we can remove the $op argument and have hook_hookname_operationname instead...

Drupal 6.x:

<?php
/**
* Implementation of hook_nodeapi().
*/
function book_nodeapi(&$node, $op, $teaser, $page) {
  switch (
$op) {
    case
'load':
     
// Load a book
 
}
}

/**
* Implementation of hook_user().
*/
function block_user($type, $edit, &$account, $category = NULL) {
  switch (
$type) {
    case
'form':
     
// Construct the user form

 
}
}

/**
* Implementation of hook_block().
*/
function system_block($op = 'list', $delta = 0, $edit = array()) {
  switch (
$op) {
    case
'list':
     
// List of blocks
 
}
}
?>

Drupal 7.x:

<?php
/**
* Implementation of hook_nodeapi_load().
*/
function book_nodeapi_load(&$node, $teaser, $page) {
 
// Load a book
}

/**
* Implementation of hook_user_form().
*/
function block_user_form(&$edit, &$account, $category = NULL) {
 
// Construct the user form
}

/**
* Implementation of hook_block_list().
*/
function system_block_list() {
 
// List of blocks
}
?>

In hook_node_info() change 'module' back to 'base' and change 'node' to 'node_content

(issue) between Drupal 4.7 and Drupal 5, the 'base' attribute in hook_node_info() was changed to 'module'. However, this is confusing for developers, since there is no requirement that this attribute correspond to the name of any module, and there may be multiple node types defined per module. This change reverts to using 'base'.

In addition, the content types managed by the node module had a 'module' (now 'base') attribute of 'node'. However, to prevent conflicts with the node module's own hooks, this was selectively altered when invoking hooks to 'node_content'. For example, they have always used node_content_form(). By simply assigning the 'base' attribute of these types to be 'node_content' in the database, we can eliminate the error-prone coercing of these attributes at each hook invocation.

The helper function _node_type_set_defaults() is now a more useful public function node_type_set_defaults().

Drupal 6.x:

<?php
/**
* Implementation of hook_node_info().
*/
function blog_node_info() {
  return array(
   
'blog' => array(
     
'name' => t('Blog entry'),
     
'module' => 'blog',
     
'description' => t('A <em>blog entry</em> is a single post to an online journal.'),
     );
}
?>

Drupal 7.x:

<?php
/**
* Implementation of hook_node_info().
*/

function blog_node_info() {
  return array(
   
'blog' => array(
     
'name' => t('Blog entry'),
     
'base' => 'blog',
     
'description' => t('A <em>blog entry</em> is a single post to an online journal.'),
     );
}
?>

Saving to the DB a node-type to be managed by node module:

Drupal 6.x:

<?php
function _book_install_type_create() {
 
$book_node_type = array(
   
'type' => 'book',
   
'name' => t('Book page'),
   
'module' => 'node',
   
'description' => t('A <em>book page</em>.'),
   
'custom' => TRUE,
   
'modified' => TRUE,
   
'locked' => FALSE,
  );

 
$book_node_type = (object)_node_type_set_defaults($book_node_type);
 
node_type_save($book_node_type);
}
?>

Drupal 7.x:

<?php
function _book_install_type_create() {
 
$book_node_type = array(
   
'type' => 'book',
   
'name' => t('Book page'),
   
'base' => 'node_content',
   
'description' => t('A <em>book page</em>.'),
   
'custom' => 1,
   
'modified' => 1,
   
'locked' => 0,
  );

 
$book_node_type = node_type_set_defaults($book_node_type);
 
node_type_save($book_node_type);
}
?>

Use absolute path (constructed from DRUPAL_ROOT) when including a file

(issue) When including a file (via include(), include_once(), require() or require_once()) specify the absolute path to the file, using the named constant DRUPAL_ROOT to give the absolute path to the root of the Drupal installation.

Drupal 6.x:

<?php
// Allow specifying special cache handlers in settings.php, like
// using memcached or files for storing cache information.
require_once variable_get('cache_inc', './includes/cache.inc');
?>

Drupal 7.x:

<?php
// Allow specifying special cache handlers in settings.php, like
// using memcached or files for storing cache information.
require_once DRUPAL_ROOT . '/' . variable_get('cache_inc', 'includes/cache.inc');
?>

In addition, a standalone script that requires access to Drupal will need to define DRUPAL_ROOT before including bootstrap.inc and invoking drupal_bootstrap(), e.g. for a script located in the Drupal root folder:

<?php
/**
* Root directory of Drupal installation.
*/
define('DRUPAL_ROOT', dirname(realpath(__FILE__)));
?>

This will need to be modified accordingly for a script located outside the Drupal root folder.

File operations that don't affect the database have been renamed

(issue) The file_copy(), file_move(), file_delete() and file_save_data() functions from Drupal 6 have been renamed to file_unmanaged_copy(), file_unmanaged_move(), file_unmanaged_delete() and file_unmanaged_save_data().

file_copy() and file_move() now return the new path rather than assigning it by reference.

Summary of File API changes

Drupal 6
Drupal 7
Description

file_copy()
file_unmanaged_copy()
Copy a file to a new location without saving a record in the database.

n/a
file_copy()
Copies a file to a new location and adds a file record to the database. Also invokes hook_file_copy() so that other modules may act on the copy action.

file_move()
file_unmanaged_move()

Move a file to a new location but make no changes to the database.

n/a
file_move()
Move a file to a new location and update the file's database entry. Also invokes hook_file_move() so that other modules may act on the move action.

file_delete()
file_unmanaged_delete()
Delete a file.

n/a
file_delete()
Delete a file and its database record. Also involes hook_file_delete() to let other modules perform clean-up actions when file is deleted.

file_save_data()
file_unmanaged_save_data()
Save a string to the specified destination but makes no changes to the database.

n/a
file_save_data()
Save a string to the specified destination and create a database file entry.

n/a
file_load()
Load a file object from the database. Also invokes hook_file_load() to allow other modules to do things as the file is loaded.

n/a
file_validate()
Check that a file meets the criteria specified by the validators. Accepts an associative array of callback functions used to validate the file. Also calls hook_file_validate() to let other modules perform validation on the new file.

n/a
file_save()
Save a file object to the database. Calls either hook_file_insert() or hook_file_update(), depending on whether a $file->fid is specified.

file_copy()

Drupal 6.x:

<?php
file_copy
($source, $paths['target'] . $base);
$paths['files'][] = $source;
?>

Drupal 7.x:

<?php
$filepath
= file_unmanaged_copy($source, $paths['target'] . $base);
$paths['files'][] = $filepath;
?>

file_delete()

Drupal 6.x:

<?php
file_delete
($file_path);
?>

Drupal 7.x:

<?php
file_unmanaged_delete
($file_path);
?>

file_save_data()

Drupal 6.x:

<?php
if (file_save_data($data, $dest)) {
 
$language->javascript = $data_hash;
 
$status = ($status == 'deleted') ? 'updated' : 'created';




}
?>

Drupal 7.x:

<?php
if (file_unmanaged_save_data($data, $dest)) {
 
$language->javascript = $data_hash;
 
$status = ($status == 'deleted') ? 'updated' : 'created';
}
?>

"administer nodes" permission split into "administer nodes" and "bypass node access"

(issue) The "administer nodes" permission has been split into two separate permissions, "administer nodes" and "bypass node access".

The "administer nodes" permission now grants the ability to alter all data associated with a given node, such as menu items, publication status and path aliases, provided that the user already has permission to modify the node. Unlike in 6.x and earlier, this permission does not allow the user to view or edit all nodes on a site.

The "bypass node access" permission grants the ability to view, edit or delete all nodes, without regard for any status checks made by hook_access or the node access system. The "bypass node access" check overrides any other checks made by permissions such as "edit page nodes". However, users with this permission will not automatically be able to edit all attributes of a given node (such as menu item, publication status and path alias), unless their other permissions allow them to do so.

When upgrading a site from 6.x to 7.x, roles with the "administer nodes" permission are automatically given the "bypass node access" permission as well, since having the "administer nodes" permission in 6.x is equivalent to having both in 7.x.

For modules that used the old "administer nodes" permission for access checks, you may now need to switch to "bypass node access" or to both permissions, for access checks.

New hooks: hook_modules_installed, hook_modules_enabled, hook_modules_disabled, and hook_modules_uninstalled

(Issue) Four new hooks are provided so that other modules can react to modules' status changes. Each hook is passed an array of module short-names, i.e. array('blog', 'blogapi'). Note, these hooks should NOT be used by a module for its own installation or uninstallation; hook_install or hook_uninstall should be used. See the documentation for each hook for more information.

drupal_uninstall_module() is now drupal_uninstall_modules()

(issue) The function drupal_uninstall_module() used to take one parameter (the name of the module to uninstall). It has been changed to a new function called drupal_uninstall_modules(), whose one parameter is an array of modules to uninstall.

drupal_set_title() uses check_plain() by default

(issue) drupal_set_title() now uses check_plain() on the title text by default. To pass through text that has already been sanitized (e.g. using check_plain(), or the % or @ placeholders in t()), then supply the constant PASS_THROUGH as the second argument. If you are currently calling check_plain(), that can be removed to avoid double-escaping.

For example:

Drupal 6.x:

<?php
    drupal_set_title
(check_plain($node->title));
?>

Drupal 7.x:

<?php
    drupal_set_title
($node->title);
?>

Drupal 6.x:

<?php
  drupal_set_title
(t("@name's blog", array('@name' => $account->name)));
?>

Drupal 7.x:

<?php
  drupal_set_title
(t("@name's blog", array('@name' => $account->name)), PASS_THROUGH);
?>

Changed parameters for drupal_add_js() and drupal_add_css()

(drupal_add_js() issue, drupal_add_css() issue) The drupal_add_js() and drupal_add_css() functions now take either a string defining the type of script that's being added to the page, or an array defining more about the data being added. The new function signature is as follows:

<?php
drupal_add_js
($data = NULL, $options = NULL);
drupal_add_css($path = NULL, $options = NULL);
?>

Again, $options can be a string defining the type of script being added ('module', 'theme', 'setting', etc), or an associative array containing arguments about the data ('type', 'scope', 'defer', 'cache', 'preprocess', 'media'). In the end, the code change looks like the following:

Drupal 6.x:

<?php
drupal_add_js
('misc/collapse.js', 'module');
drupal_add_js('misc/collapse.js', 'module', 'footer');
drupal_add_js('misc/collapse.js', 'module', 'header', FALSE, TRUE, FALSE);
drupal_add_css('/modules/devel/devel.css', 'module');
drupal_add_css('/modules/devel/devel.css', 'module', 'screen');
drupal_add_css('/modules/devel/devel.css', 'module', 'all', FALSE);
?>

Drupal 7.x:

<?php
drupal_add_js
('misc/collapse.js', 'module');
drupal_add_js('misc/collapse.js', array('type' => 'module', 'scope' => 'footer'));
drupal_add_js('misc/collapse.js', array('preprocess' => FALSE));
drupal_add_css('/modules/devel/devel.css', 'module');
drupal_add_css('/modules/devel/devel.css', array('media' => 'screen'));
drupal_add_css('/modules/devel/devel.css', array('preprocess' => FALSE));
?>

Note that the second parameter can either be the type, or an array defining more about the data being added. Therefore, this change should only apply to drupal_add_js() or drupal_add_css() calls that defined more then just the type during its call.

Changed Drupal.behaviors to objects having the methods 'attach' and 'detach'

(issue) JavaScript behaviors are now objects, having the methods 'attach' and 'detach'. Correspondingly, Drupal.attachBehaviors() is now accompanied by Drupal.detachBehaviors(). Developers who make use of dynamic AJAX/AHAH contents should additionally invoke Drupal.detachBehaviors() before a page element is going to be processed or removed from the output, allowing special behaviors (such as client-side editors) to detach from it.
Conversion of simple behaviors is easy:

Drupal 6.x:

Drupal.behaviors.tableSelect = function(context) {
  $('form table:has(th.select-all):not(.tableSelect-processed)', context).each(Drupal.tableSelect);
};

Drupal 7.x:

Drupal.behaviors.tableSelect = {
  attach: function(context) {
    $('form table:has(th.select-all):not(.tableSelect-processed)', context).each(Drupal.tableSelect);
  }
};

All AJAX/AHAH-driven applications should invoke Drupal.detachBehaviors(context) before a form is processed/sent and content is removed from the output, where 'context' refers to the affected page element, just like the context of Drupal.attachBehaviors(context). For example:

Drupal 7.x:

/**


* Process a form in a modal dialog.
*/
Drupal.myModule.ajaxSubmit = function() {
  $('form#mymodule-ajax-form').submit(
    Drupal.detachBehaviors($(this));
    $(this).ajaxSubmit({
      url: Drupal.settings.myAjaxURL,
      data: '...',
      method: 'post'
    });
    return false;
  );
};

/**
* Remove a modal dialog (f.e. dismissing form processing).
*/
Drupal.myModule.cancelModal = function() {
  var dialog = $('#mymodule-modal-content');
  Drupal.detachBehaviors(dialog);
  dialog.remove();
};

If behaviors need to detach from contents, they should additionally implement the 'detach' method, searching for elements using the reversed selector of the attaching method:

Drupal.behaviors.tableSelect = {
  attach: function(context) {
    $('form table:has(th.select-all):not(.tableSelect-processed)', context).each(Drupal.tableSelect);
  },
  detach: function(context) {
    $('form table.tableSelect-processed', context).each(
      ...
    );
  }
};

Removed file_set_status()

(Issue) The file_set_status() function which was used, primarily, to make temporary files permanent has been removed file_save() should be used in it's place.

Drupal 6.x:

<?php
file_set_status
($file, FILE_STATUS_PERMANENT);
?>

Drupal 7.x:
<?php
$file
->status |= FILE_STATUS_PERMANENT;
$file = file_save($file);
?>

Replace 'core', 'module' and 'theme' with 'file' in drupal_add_js()

(issue) JavaScript can now be ordered according to its weight rather then in what order drupal_add_js() was called. Rather then using 'core', 'module' or 'theme' types to determine the order in which JavaScript is presented on the page, we now use a 'file' type, along with a 'weight' parameter. For the upgrade path in Drupal 6, we must simply replace 'core', 'module' and 'theme' with the respective weight parameter.

Drupal 6.x:

<?php
drupal_add_js
($jquery_plugin, 'core');
drupal_add_js($module_javascript, 'module');
drupal_add_js($theme_javascript, 'theme');

drupal_add_js($other_javascript, 'module');
drupal_add_js($file);
?>

Drupal 7.x:
<?php
drupal_add_js
($jquery_plugin, array('weight' => JS_LIBRARY));
drupal_add_js($module_javascript, array('type' => 'file', 'weight' => JS_DEFAULT));
drupal_add_js($theme_javascript, array('weight' => JS_THEME));
drupal_add_js($other_javascript, 'file');
drupal_add_js($file);
?>

For more information about the added 'weight' parameter, visit the drupal_add_js() documentation.

Parameters to hook_filter() have changed

hook_filter() now accepts an additional parameter, $langcode, which allows filters to be language aware. This means modules can now implement language specific text replacements.

Drupal 6.x:

<?php
function my_module_filter($op, $delta = 0, $format = -1, $text = '', $cache_id = 0) {
 
// Apply filter...
}
?>

Drupal 7.x:

<?php
function my_module_filter($op, $delta = 0, $format = -1, $text = '', $langcode = '', $cache_id = 0) {
 
// Apply filter...
}
?>

Parameters to check_markup() have changed

check_markup() now accepts an additional parameter, $langcode. This is needed for filters that implement language-specific text replacements.

Objects that include a language should now pass this to check_markup().

Drupal 6.x:

<?php
$node
->body = check_markup($node->body, $node->format);
?>

Drupal 7.x:

<?php
$node
->body = check_markup($node->body, $node->format, $node->language);
?>

Also, because the $langcode argument comes before the existing $check argument, any calls to check_markup() that included the third argument ($check) need to be updated to include the new $langcode argument, even if they don't pass a $langcode.

Drupal 6.x:

<?php
$content
= check_markup($block->body, $block->format, FALSE);
?>

Drupal 7.x:

<?php
$content
= check_markup($block->body, $block->format, '', FALSE);
?>

Schema descriptions are no longer translated

(Issue) To reduce the strings to translate, descriptions of the schema API (tables and fields) in module.install no longer need to be translated.

6.x:

<?php
function forum_schema() {
 
$schema['forum'] = array(
   
'description' => t('Stores the relationship of nodes to forum terms.'),
   
'fields' => array(
     
'nid' => array(
       
'description' => t('The {node}.nid of the node.'),
      ),
    ),
  );
  return
$schema;
}
?>

7.x:

<?php
function forum_schema() {
 
$schema['forum'] = array(
   
'description' => 'Stores the relationship of nodes to forum terms.',
   
'fields' => array(
     
'nid' => array(
       
'description' => 'The {node}.nid of the node.',
      ),
    ),
  );
  return
$schema;
}
?>

file_scan_directory() uses a regex for the $nomask paramter

(issue) The file_scan_directory() function now uses a preg style regular expression to determine which files and directories it ignores.

The majority of calls to file_scan_directory() just specify in the function's default parameter. In these cases all that's needed is to use the new default parameter. If you're doing more complicated exclusions you'll want to consult the PHP documentation for the preg_match() function.

Drupal 6.x:

<?php
file_scan_directory
(file_create_path('css'), '/.*/', array('.', '..', 'CVS'), 'file_unmanaged_delete', TRUE);
?>

Drupal 7.x:
<?php
file_scan_directory
(file_create_path('css'), '/.*/', '/(\.\.?|CVS)$/', 'file_unmanaged_delete', TRUE);
?>

use module_implements not module_list when calling hook implementations

All code that did this:

<?php
foreach (module_list() as $module)) {
 
// do something with a hook on each module
?>

should now do this:

<?php
foreach (module_implements($hook) as $module)) {
 
// do something with $hook on each module
?>

New hook_js_alter to alter JavaScript

(issue) Before Drupal 7, when you wanted to alter JavaScript being presented on the page, you'd have to alter the $scripts variable. The jQuery Update module, for example, would use hook_preprocess_page($variables) to alter the $scripts variable directly. Now, with hook_js_alter(), we can just directly alter any JavaScript that will be presented on the page.

A live example of hook_js_alter() being used is available in simpletest_js_alter(), which moves the simpletest.js's weight above tableselect.js's weight. The $javascript parameter that's pass into hook_js_alter() is all JavaScript that's going to be presented on the page.

Drupal 6.x:

<?php
function jquery_update_preprocess_page(&$variables) {
 
// Modify the scripts variable.
 
if (!empty($variables['scripts'])) {
?>

Drupal 7.x:

<?php
function jquery_update_js_alter(&$javascript) {
 
// Modify the JavaScript being presented on the page.
?>

Changed log out path from 'logout' to 'user/logout' for consistency

(issue) The menu path to log out the current user is now 'user/logout' and fits in line with all the other user-related paths like user/login, user/register, etc.

Drupal 6.x:

<?php
drupal_goto
('logout');
l(t('Log out'), 'logout');
?>

Drupal 7.x:

<?php
drupal_goto
('user/logout');
l(t('Log out'), 'user/logout');
?>

node_load() and node_load_multiple()

(issue) Multiple nodes can now be loaded at once using node_load_multiple(). All nodes loaded by this function are loaded in a single query. hook_nodeapi_load() now takes an array of node objects, and modules should use one database query here where possible also.

node_load() has been simplified to take only nid and vid as parameters, and is a simple wrapper around node_multiple_load().

taxonomy_term_load() and taxonomy_term_load_multiple()

(issue) Multiple taxonomy terms can now be loaded at once using taxonomy_term_load_multiple(). All terms loaded by this function are loaded in a single query. hook_taxonomy_term_load() now takes an array of term objects, and modules should use one database query here where possible also. taxonomy_term_load() is a now a simple wrapper around the multiple function, sharing the same static cache.

file_load_multiple()

issue. Multiple file objects can now be loaded at once using file_load_multiple(). hook_file_load() takes an array of file objects.

Taxonomy CRUD functions renamed and refactored

To improve consistency, the CRUD functions for both taxonomy terms and vocabularies have been renamed and refactored:

  • (Issue) To load a taxonomy term, you should now use taxonomy_term_load() which has replaced taxonomy_get_term().
  • (Issue) To save taxonomy terms, you should now use taxonomy_term_save() which has replaced taxonomy_save_term(). Note that the new function takes a term object as a parameter instead of an array.
  • (Issue) To delete a taxonomy term, you should now use taxonomy_term_delete() which has replaced taxonomy_del_term().

New hooks: hook_taxonomy_term_load(), hook_taxonomy_term_save(), hook_taxonomy_term_delete()

(Issue) New hooks are provided for taxonomy terms to allow modules to add information to the term object.

taxonomy_get_tree()

(issue) taxonomy_get_tree()'s $depth and $max_depth parameters have switched positions, since $depth is for internal use only. You will not have to specify its default value anymore if you want to set $max_depth

Code documentation added to module.api.php

(issue) If your module creates any hooks or has an associated API, add examples of these hooks in modulename.api.php. This not only helps other developers understand the provided API a little easier, but also gives IDEs the hook information. See system.api.php for an example of this.

(issue) Node and taxonomy links are no longer emitted by hook_link() and hook_link_alter(). Those hooks are now only used for comment links. Instead, links are added to $node->content during hook_nodeapi_view(). Links and all of $node->content may be re-arranged/changed during the new new hook: hook_node_view_alter(). Also note the new form api element - node_links. For example:

Drupal 6

<?php
function blog_link($type, $node = NULL, $teaser = FALSE) {
 
$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;
}
?>

Drupal 7

<?php
function blog_nodeapi_view($node, $teaser = FALSE) {
  if (
$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))),
      );
     
$node->content['links']['blog'] = array(
       
'#type' => 'node_links',
       
'#value' => $links,
      );
    }
  }
}
?>

Parameters for actions_synchronize() have changed

(issue) The $actions_in_code parameter has been removed from actions_synchronize().
Drupal 6.x:

<?php
function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
  if (!
$actions_in_code) {
   
$actions_in_code = actions_list(TRUE);
  }
  ...
?>

Drupal 7.x:
<?php
function actions_synchronize($delete_orphans = FALSE) {
 
$actions_in_code = actions_list(TRUE);
  ...
?>

Parameters for drupal_http_request() have changed

(issue) Instead of a long line of single arguments (the exact options and order of which were hard to remember), this function now takes an array to specify the optional arguments. The new function signature is as follows:

<?php
drupal_http_request
($url, array $options = array());
?>

$options is an associative array that contains 'headers', 'method', 'data', and 'max_redirects.' Thus, the $headers array that was a parameter in 6.x will now be passed in as $options['headers'].

Drupal 6.x

<?php
drupal_http_request
('http://example.com/', array('Header-Title' => 'value'), 'GET', NULL, 0);
?>

Drupal 7.x

<?php
drupal_http_request
('http://example.com/', array('headers' => array('Header-Title' => 'value'), 'max_redirects' => 0));
?>

See http://api.drupal.org/api/function/url/6 and http://api.drupal.org/api/function/l/6 for more information.

db_rewrite_sql() replaced with hook_query_alter()

(issue) The string-parsing-based db_rewrite_sql() has been removed in favor of hook_query_alter(). Instead of passing a literal query through db_rewrite_sql() in order to add node access restrictions, for instance, in Drupal 7 use a dynamic query and tag it "node_access".

Drupal 6.x

<?php
$sql
= db_rewrite_sql("SELECT n.nid, n.title, l.comment_count, l.last_comment_timestamp FROM {node} n INNER JOIN {term_node} tn ON tn.vid = n.vid INNER JOIN {term_data} td ON td.tid = tn.tid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 AND td.vid = %d ORDER BY l.last_comment_timestamp DESC");
$result = db_query_range($sql, variable_get('forum_nav_vocabulary', ''), 0, variable_get('forum_block_num_active', '5'));
?>

Drupal 7.x

<?php
$query
= db_select('node', 'n');
$tn_alias = $query->join('taxonomy_term_node', 'tn', 'tn.vid = n.vid');
$td_alias = $query->join('taxonomy_term_data', 'td', 'td.tid = tn.tid');
$l_alias = $query->join('node_comment_statistics', 'l', 'n.nid = l.nid');
$query->fields('n', array('nid', 'title'));
$query
 
->fields($l_alias, array('comment_count', 'last_comment_timestamp')
  ->
condition("n.status", 1)
  ->
condition("{$td_alias}.vid", variable_get('forum_nav_vocabulary', ''))
  ->
orderBy("{$l_alias}.last_comment_timestamp", 'DESC')
  ->
range(0, variable_get('forum_block_num_active', '5'));
$query->addTag('node_access');
$result = $query->execute();
?>

The "node_access" tag tells implementations of hook_query_alter() that this query needs to be altered appropriately for "node_access"-relevant queries. Additional tags may also be used as desired. See the handbook page for more information.

Removed FILE_STATUS_TEMPORARY

(issue) The FILE_STATUS_TEMPORARY constant has been removed. $file->status is a bitmapped field and having FILE_STATUS_TEMPORARY for the 0 value of the bit didn't make any sense. To make a permanent file temporary use the inverse of the FILE_STATUS_PERMANENT constant:

<?php
$file
->status &= ~FILE_STATUS_PERMANENT;
?>

Renamed user_delete() to user_cancel(), likewise renamed hook_user_delete() to hook_user_cancel()

(issue) Users can now cancel their own accounts, when the corresponding "cancel account" permission has been granted. There are multiple account cancellation methods, from which one can be configured as default. Users with the "administer users" permission are able to override the default method, f.e. to completely delete a user including all of the related content. Modules that implemented hook_user_delete() (resp. hook_user($op == 'delete')) previously, need to update their code to use hook_user_cancel() instead, which takes an (internal) account cancellation method name as third argument.

Drupal 6.x:

<?php
/**
* Implementation of hook_user().
*/
function node_user($op, &$edit, &$user) {
  if (
$op == 'delete') {
   
db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
   
db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid);
  }
}
?>

Drupal 7.x:

<?php
/**
* Implementation of hook_user_cancel().
*/
function node_user_cancel(&$edit, &$account, $method) {
  switch (
$method) {
    case
'user_cancel_block_unpublish':
     
// Unpublish nodes (current revisions).
     
module_load_include('inc', 'node', 'node.admin');
     
$nodes = db_select('node', 'n')->fields('n', array('nid'))->condition('uid', $account->uid)->execute()->fetchCol();
     
node_mass_update($nodes, array('status' => 0));
      break;

    case
'user_cancel_reassign':
     
// Anonymize nodes (current revisions).
     
module_load_include('inc', 'node', 'node.admin');
     
$nodes = db_select('node', 'n')->fields('n', array('nid'))->condition('uid', $account->uid)->execute()->fetchCol();
     
node_mass_update($nodes, array('uid' => 0));
     
// Anonymize old revisions.
     
db_update('node_revision')->fields(array('uid' => 0))->condition('uid', $account->uid)->execute();
     
// Clean history.
     
db_delete('history')->condition('uid', $account->uid)->execute();
      break;

    case
'user_cancel_delete':
     
// Delete nodes (current revisions).
      // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
     
$nodes = db_select('node', 'n')->fields('n', array('nid'))->condition('uid', $account->uid)->execute()->fetchCol();
      foreach (
$nodes as $nid) {
       
node_delete($nid);
      }
     
// Delete old revisions.
     
db_delete('node_revision')->condition('uid', $account->uid)->execute();
     
// Clean history.
     
db_delete('history')->condition('uid', $account->uid)->execute();
      break;
  }
}
?>

Each module should only do the appropriate action for the passed in $method. All methods available in Drupal core by default are defined in user_cancel_methods(). Modules can enhance those methods by implementing hook_user_cancel_methods_alter().

IMPORTANT: Please note that the default method for Drupal 7.x is user_cancel_block, which effectively only disables (blocks) a user account. Most modules should not need to alter any user data or information for this method.

Taxonomy db table names have changed to begin with 'taxonomy_'

(issue)The names of the taxonomy database tables have changed to be consistent with our naming conventions. Each table has been prefixed with 'taxonomy_'. Table vocabulary_node_types has also changed to the singular form: taxonomy_vocabulary_node_type.

  • term_data -> taxonomy_term_data
  • term_hierarchy -> taxonomy_term_hierarchy
  • term_node -> taxonomy_term_node
  • term_relation -> taxonomy_term_relation
  • term_synonym -> taxonomy_term_synonym
  • vocabulary -> taxonomy_vocabulary
  • vocabulary_node_types -> taxonomy_vocabulary_node_type (singular)

User pictures are now managed files

(issue) The {users}.picture column is now a file id rather than a path. When a user is loaded with user_load() then $user->picture is a file object.

drupal_set_session() replaces $_SESSION

(issue) The $_SESSION superglobal should not be accessed directly. Instead, use drupal_set_session($name, $value). This is a performance optimization so that no session is started for anonymous users unless a module has put data into the user's session. Note that it is important that modules try not to use $_SESSION for anonymous users. Such use ruins the optimization here. This optimization is particularly impressive when Drupal is coupled with a reverse proxy like Squid or Varnish.

Ability to reset JavaScript and CSS

(issue) If you need to reset the entire JavaScript or CSS, you can use drupal_static_reset, that will empty the cached values. Also take note of hook_js_alter().

<?php
// Reset all JavaScript that has been added.
drupal_static_reset('drupal_add_js');

// Reset all cascading stylesheets that have been added.
drupal_static_reset('drupal_add_css')
?>

Moved statistics settings from admin/reports/settings to admin/settings/statistics and added a new 'administer statistics' permission

(issue) The settings page for the statistics module has been moved to the more appropriate location of admin/settings/statistics. A new permission (administer statistics) has been added in order to restrict access to the settings page.

Default parameter when getting variables

(issue) When using variable_get to retrieve variables, the $default parameter now defaults to NULL, so if you're getting a variable where you don't care about what the default is, then you don't have to pass "NULL" as the second parameter.

Drupal 6.x:

<?php
// Retrive myawesomevariable. If it doesn't exist, retrieve NULL.
variable_get('myawesomevariable', NULL);
?>

Drupal 7.x:

<?php
// Retrieve myawesomevariable. If it doesn't exist, this will return NULL.
variable_get('myawesomevariable');
?>

(issue) Menu callbacks and blocks should return a renderable array instead of a string. You are probably familiar with renderable arrays from Form API. These are arrays which drupal_render() knows how to process. The big benefit of returning an array is that sites may implement hook_page_alter() and change the contents of the menu callback. For example, they could move some of the information to the 'left' region.

Your array should have as many array elements as reasonably possible. For example, the default homepage has an array element for each node and another at the end for the pager. For examples of how some callbacks have been converted to arrays, see node_page_default() and node_show() and blog_page_last() and taxonomy_term_page()

Block module now optional

(issue) Modules and install profiles should be aware that block.module is enabled by default but no longer mandatory. Install profiles usually want to enable it in hook_profile_modules().

Element theming properties used by drupal_render() have changed

(issue) The value of the #type property of elements declared in hook_elements is no longer treated as a theming function. Instead, there are now two explicit properties that control HTML generation: #theme and #theme_wrapper. #theme is mostly the same as in Drupal 6, it can be used to render the element's children. #theme_wrapper is called afterwards and can be used to wrap markup around the rendered children, similar to the way the #type property used to be used.

Element theme functions should call drupal_render_children()

If you implement hook_elements, you usually will have to set at least one of the theme properties for your elements (#theme and #theme_wrapper), likely to the same value as #type.
In Drupal 6, in the #theme function of an element, it was common to call drupal_render() on the element itself to render all remaining children. This now causes an endless loop. Use drupal_render_children() instead.

Also see the API docs for drupal_render()

Replace node_view() with node_build()

(issue) A decision was made for Drupal 7 to keep data in structured arrays as long as possible before rendering. The node_view() function was therefore removed, in favor of node_build(). Also, see $teaser parameter changed to $build_mode in node building functions and hooks below.

Drupal 6.x:

<?php
$output
= node_view($my_nid, TRUE);
?>

Drupal 7.x:

<?php
$nstruct
= node_build($my_nid, 'teaser');
$output = drupal_render($nstruct);
?>

Also see the API docs for node_build()

JavaScript should be compatible with other libraries than jQuery

(issue). Javascript should be made compatible with other libraries than jQuery by adding a small wrapper around your existing code:

(function ($) {
  // Original JavaScript code.
})(jQuery);

The $ global will no longer refer to the jquery object. However, with this construction, the local variable $ will refer to jquery, allowing your code to access jQuery through $ anyway, while the code will not conflict with other libraries that use the $ global.

file_scan_directory()'s optional parameters are now an array

(issue). file_scan_directory() had five optional parameters that have now been combined into an array making it easy to specify a single option with out remembering all the default values.

Drupal 6.x:

<?php
file_scan_directory
(file_create_path('css'), '/.*/', '/(\.\.?|CVS)$/', 'file_unmanaged_delete', TRUE);
?>

Drupal 7.x:

<?php
file_scan_directory
(file_create_path('css'), '/.*/', array('callback' => 'file_unmanaged_delete'));
?>

External JavaScript can now be referenced through drupal_add_js()

(issue) The drupal_add_js() function can now add reference to external JavaScript files by passing a 'external' type.

Drupal 6.x:

<?php
drupal_set_html_head
('<script type="text/javascript" src="http://example.com/example.js" />');
?>

Drupal 7.x:

<?php
drupal_add_js
('http://example.com/example.js', 'external');
drupal_add_js('http://example.com/example.js', array('type' => 'external'));
?>

user_load_multiple() and hook_user_load()

user_load_multiple() allows you to load multiple users with reduced queries. user_load() has been reduced to a thin wrapper around user_load_multiple() and now only takes user ID as an argument. hook_user_load() now takes an array of user objects keyed by user ID, and acts on them by reference.

jQuery 1.3.x

(issue) The move to jQuery 1.3.x brought in a lot of JavaScript performance enhancements as well as a number of other new features. In order to upgrade any module's JavaScript, see the changes documentation. The most common API change is:

jQuery 1.2.x:

$("a[@href*='admin/build/testing']").doSomething();

jQuery 1.3.x:

$("a[href*='admin/build/testing']").doSomething();

Settings passed locally to JavaScript Behaviors

(issue) Settings are now passed locally to drupals JavaScript behaviors rather than using the global Drupal.settings variable. This enables settings not part of the Drupal.settings to be used with the behaviors which is useful for AJAX/AHAH.

Drupal 6.x:

Drupal.behaviors.tableSelect = function(context) {
  $('#example', context).html(Drupal.settings.myvar);
};

Drupal 7.x:

Drupal.behaviors.tableSelect = {
  attach: function(context, settings) {
    $('#example', context).html(settings.myvar);
  }
};

file_scan_directory() now uses same property names as file_load()

(issue) file_scan_directory() previously returned objects with the properties filename, basename, and name (e.g., respectively, "foo/bar.txt," "bar.txt" and "bar"). filename has been renamed to filepath, and basename has been renamed to filename, i.e. the same names as used in objects returned by file_load().

Moved filter module administrative URLs from admin/settings/filters/* to admin/settings/filter/*

(issue) For greater consistency, administrative URLs in the Filter module have been renamed from plural to singular. Any hardcoded links to these URLs should be updated to point to the correct path. For example, instead of url('admin/settings/filters/add') (Drupal 6.x), use url('admin/settings/filter/add') (Drupal 7.x).

Added taxonomy_vocabulary_load_multiple()

(issue)

Taxonomy vocabularies have a multiple load function (similar to users, nodes, files and taxonomy terms). taxonomy_get_vocabularies() and taxonomy_vocabulary_load() remain as wrapper functions around multiple load, and also return full vocabulary objects.

Added a new top-level 'international' admin menu item

(issue)
A new top level 'international menu item has been provided at admin/international - all module exposing administrative pages dealing with internationalization and localization should group their pages under this link.

(Issue)

Drupal 6.x:

<?php
function mymodule_menu_link_alter(&$item, $menu) {
 
// Example 1 - make all new admin links hidden (a.k.a disabled).
 
if (strpos($item['link_path'], 'admin') === 0 && empty($item['mlid'])) {
   
$item['hidden'] = 1;
  }
}
?>

Drupal 7.x:

<?php
function mymodule_menu_link_alter(&$item) {
 
// Example 1 - make all new admin links hidden (a.k.a disabled).
 
if (strpos($item['link_path'], 'admin') === 0 && empty($item['mlid'])) {
   
$item['hidden'] = 1;
  }
?>

Standardized API for static variables and resetting them

(Issue) A central facility is now available for holding static variable - these are typically used to cache expensive data during one page load. The advantage of using a central store is that some or all of the cached data can be cleared in a consistent fashion. For example, this will allow running Drupal a with a "clean slate" each time a test run starts, even if it's within the same page load. In addition, caches can be cleared when an object is saved - for example when a node is saved, the stored cache of previously loaded nodes can be cleared to avoid re-loading stale data.

This API contains only two functions , drupal_static() and drupal_static_reset(). Because drupal_static() returns a variable by reference, it must be invoked using a & like:

  $files = &drupal_static(__FUNCTION__, array());

The first parameter is the name of a variable - it should be the name of the calling function, or the function name with an appended suffix. The second value is the default value - this is optional and will be NULL if none is supplied. The easiest way to pass in the function name for the first parameter is to use the PHP magic constant __FUNCTION__ which (while ugly) is automatically replaced with the name of the function it is within. Use of this constant is strongly encouraged to avoid errors while converting. The function name (or class+method name) is used as the variable name since it is guaranteed to be unique within the code base.

An important standard is that if you have a variable name more complex than just __FUNCTION__, use a ':' to separate the suffix (this will avoid colliding with any other valid function name). e.g. :

&drupal_static(__FUNCTION__ . ':second_var');

If you have multiple variables within one function that should all be cleared or populated together, you should try to name one of them with just __FUNCTION__ and arange your code such that if that one variable is cleared, all the others in the function are cleared as well. For example, see: http://api.drupal.org/api/function/taxonomy_get_tree/7

Example conversion:

Drupal 6.x:

<?php
function menu_rebuild() {
 
variable_del('menu_rebuild_needed');
 
menu_cache_clear_all();
 
$menu = menu_router_build(TRUE);
 
_menu_navigation_links_rebuild($menu);

  ...
}

function
menu_router_build($reset = FALSE) {
  static
$menu;

  if (!isset(
$menu) || $reset) {
    ...
  }

  return
$menu
}
?>

Drupal 7.x:

<?php
function menu_rebuild() {
 
variable_del('menu_rebuild_needed');
 
menu_cache_clear_all();
 
drupal_static_reset('menu_router_build');
 
$menu = menu_router_build();
 
_menu_navigation_links_rebuild($menu);

  ...
}

function
menu_router_build() {
 
$menu = &drupal_static(__FUNCTION__);

  if (!isset(
$menu)) {
    ...
  }

  return
$menu
}
?>

Note that the $reset parameter is removed in the 7.x version.

The function drupal_set_html_head() has been renamed to drupal_add_html_head().

(issue) In Drupal 6, the drupal_set_html_head() function, is misleading: it does not SET, but ADD HMTL header data. In Drupal 7, it has been renamed to drupal_add_html_head().

Inline cascading stylesheets from drupal_add_css()

(issue) Cascading stylesheets can now be added inline from a call to drupal_add_css(). Stylesheets added in this way are not aggregated or cached, so it is usually best practice to use an actual file.

Drupal 6.x:

<?php
$color
= variable_get('backgroundcolor', '#FFFFFF');
drupal_set_html_head("<style type='text/css'>body {background-color: $color}</style>");
?>

Drupal 7.x:

<?php
$color
= variable_get('backgroundcolor', '#FFFFFF');
drupal_add_css("body {background-color: $color}", 'inline');
?>

Attached JavaScript and CSS for forms

(issue) Individual form elements now have the ability to define what JavaScript and cascading stylesheets are associated with them. This is stated in the #attached_js and #attached_css property.

Drupal 6.x:

<?php
function example_admin_settings() {
 
// Add example.admin.css
 
drupal_add_css(drupal_get_path('module', 'example') .'/example.admin.css');
 
// Add some inline JavaScript
 
drupal_add_js('alert("You are visiting the example form.");', 'inline');
 
// Add a JavaScript setting.
 
drupal_add_js(array('mymodule' => 'example'), 'setting');
 
$form['example'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Example');
  );
  return
$form;
}
?>

Drupal 7.x:

<?php
function example_admin_settings() {
 
$form['#attached_css'] = array(
   
// Add example.admin/css.
   
drupal_get_path('module', 'example') . '/example.admin.css'
 
),
 
$form['#attached_js'] = array(
   
// Add some inline JavaScript.
   
'alert("You are visiting the example form.");' => 'inline',
   
// Add a JavaScript setting. Note that when the key is a number, the 'data' property will be used.
   
array(
     
'data' => array('mymodule' => array(...)),
     
'type' => 'setting'
   
),
  );
 
$form['example'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Example');
  );
  return
$form;
}
?>

Make sticky tableheaders optional

(issue) In previous Drupal versions table headers (if provided) would "stick" to the top of the screen when scrolling down. This is still enabled by default but the option has been added to disable this when theming a table.

Drupal 6.x

<?php
$table
= theme('table', $headers, $rows, array(), NULL, array());
?>

Drupal 7.x

<?php
// Disable sticky tableheaders
$table = theme('table', $headers, $rows, array(), NULL, array(), FALSE);
// Enable sticky tableheaders
$table = theme('table', $headers, $rows, array(), NULL, array());
$table = theme('table', $headers, $rows, array(), NULL, array(), TRUE);
?>

Save nodes and users with specified IDs

(issue) When importing nodes or users, it is sometimes useful to preserve the ID from the legacy system. This is now possible by setting a nid or uid and setting the is_new property when calling node_save() or user_save().

Parameters swapped in book_toc()

(issue)The non-optional parameter $depth followed the optional parameter $exclude. This has been corrected, the two parameters have swapped places.

Drupal 6.x

<?php
$toc
= book_toc($bid, array(), 9);
?>

Drupal 7.x

<?php
$toc
= book_toc($bid, 9);
$toc = book_toc($bid, 9, array());
?>

drupal_execute() renamed to drupal_form_submit()

(issue) The function name drupal_execute() is fairly ambiguous about its function. The function has been renamed to drupal_form_submit() for better clarity.

node_invoke_nodeapi() removed

(issue) The function node_invoke_nodeapi() has been removed. Use module_invoke_all('node_' . $hook, $node); where you used node_invoke_nodeapi($node, $hook);.

Removed $op "rss item" from hook_nodeapi() in favor of NODE_BUILD_RSS

(issue) $op rss item of Drupal 6 hook_nodeapi() was removed in favor of the build mode NODE_BUILD_RSS. Modules wishing to add RSS elements or namespaces to the node feed items should simply use the $node->rss_elements and $node->rss_namespaces attributes of the $node object, which are available with the NODE_BUILD_RSS build mode.

Example from taxonomy.module:
In Drupal 6:

<?php
/**
* Implementation of hook_nodeapi().
*/
function taxonomy_nodeapi($node, $op, $arg = 0) {
  switch (
$op) {
    case
'rss item':
     
$output = array();
      foreach (
$node->taxonomy as $term) {
       
$output[] = array(
         
'key'   => 'category',
         
'value' => check_plain($term->name),
         
'attributes' => array('domain' => url('taxonomy/term/'. $term->tid, array('absolute' => TRUE)))
        );
        return
$output;
      }
      break;
  }
}
?>

In Drupal 7:

<?php
/**
* An implementation of hook_node_view().
*/
function taxonomy_node_view($node) {
  if (
$node->build_mode == NODE_BUILD_RSS) {
    foreach (
$node->taxonomy as $term) {
     
$node->rss_elements[] = array(
       
'key' => 'category',
       
'value' => $term->name,
       
'attributes' => array('domain' => url(taxonomy_term_path($term), array('absolute' => TRUE))),
      );
    }
  }
}
?>

Adding namespaces is done in the same manner outlined above, but using $node->rss_namespaces. Example:

<?php
$node
->rss_namespaces['xmlns:example'] = 'http://example.com/example-namespace';
?>

drupal_eval() renamed to php_eval

(issue) The remaining PHP handling call drupal_eval() was moved to the PHP module. The function has been renamed to php_eval() to obey the coding standards. Since this is an optional module, your code should no longer assume that php_eval is always available, and should be wrapped in a module_exists('php') check.

In Drupal 6:

<?php
drupal_eval
('<?php print "Hello World"; ? >');
?>

In Drupal 7:

<?php
if (module_exists('php') {
 
php_eval('<?php print "Hello World"; ? >');
}
?>

"use PHP for settings" permission should be used for all PHP settings rights (replaces "use PHP for block visibility")

(issue) The block module-specific "use PHP for block visibility" was replaced by a more generic permission, handled by the PHP module: "use PHP for settings". If you re-used this permission in your code make sure to use the new permission title. If your module had it's own PHP visibility permission, replace it with the generic one to simplify the site administration.

Changes to HTTP header functions

(issue) drupal_set_header() now takes the header name and header value as two separate arguments rather than as one complete header line. The function now also supports appending to and removing existing headers.

Drupal 6.x:

<?php
drupal_set_header
('Content-Type: text/plain');
?>

Drupal 7.x:

<?php
drupal_set_header
('Content-Type', 'text/plain');
?>

When setting the HTTP status code, the server protocol (e.g. "HTTP/1.1") should no longer be included.

Drupal 6.x:

<?php
drupal_set_header
($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal server error');
?>

Drupal 7.x:

<?php
drupal_set_header
('500 Internal server error');
?>

drupal_get_headers() has been renamed to drupal_get_header() and now also supports returning only the header with a given name.

The array returned from hook_file_download() should now be a name/value array instead of an array of complete header lines.

Drupal 6.x:

<?php
function mymodule_file_download($filepath) {
  if (
_mymodule_access($filepath)) {
    return array(
     
'Content-Type: text/plain',
    );
  }
}
?>

Drupal 7.x:

<?php
function mymodule_file_download($filepath) {
  if (
_mymodule_access($filepath)) {
    return array(
     
'Content-Type' => 'text/plain',
    );
  }
}
?>

drupal_get_form() returns and unrendered array

(issue) drupal_get_form() no longer renders your form to an HTML string. Instead, it returns a structured array as expected by drupal_render(). Your module should strive to keep this array structure, and return an array in your menu callback. You are welcome to add/modify the array as you wish. Only under severe distress should you call drupal_render(drupal_get_form()) in order to mimic previous versions of Drupal. Returning an array lets developers make easy changes during hook_page_alter().

Add doxygen @file tag to all install files

(issue) To standardize code documentation, add doxygen @file to all .install files :

/**
* @file
* Install, update and uninstall functions for the XXX module.
*/

Add node_delete_multiple()

(issue). When you need to delete multiple nodes at a time, use the new node_delete_multiple() function which is faster than calling node_delete() many times. node_delete() still exists
for the single delete situation.

Renamed drupal_set_content() and drupal_get_content()

(issue). In Drupal 6, you could add content to a region with drupal_set_content() and request the full contents of a region with drupal_get_content(). These were renamed in Drupal 7 to drupal_add_region_content() and drupal_get_region_content(). Apart from renaming them, functionality is the same.

Drupal 6:

<?php
// Add our own text to the footer.
drupal_set_content('footer', 'Adding custom text to footer');
// Get the complete footer contents.
$full_footer = drupal_get_content();
?>

Drupal 7:

<?php
// Add our own text to the footer.
drupal_add_region_content('footer', 'Adding custom text to footer');
// Get the complete footer contents.
$full_footer = drupal_get_region_content();
?>

Instead of theme('page', ...), think of drupal_set_page_content()

(issue). In Drupal 6, theme('page', ...) had a dual purpose. It was used to set the main page content text of the page and set some page display options (such as to hide the sidebars).

Drupal 7 made the page content an actual block, so setting the page content is done with the new drupal_set_page_content(). This can now take a string (rendered HTML) or a renderable array. By default, Drupal runs the output of page callbacks through rendering, so returning a string or array from your page callback is preferred. Drupal will call drupal_set_page_content() later anyway.

The only case when you need to call drupal_set_page_content() is when you need to alter page options. In that case, use element_info('page') to request the default page attributes (an array in Drupal 7), and modify them as needed. Set the page content with drupal_set_page_content() and return with that array from your page callback.

To reiterate, the basic use case did not change:

Drupal 6:

<?php
function mymodule_page_callback() {
 
// Assemble page output here...

  // Return page output for Drupal.
 
return $output;
}
?>

Drupal 7:

<?php
function mymodule_page_callback() {
 
// Assemble page output here...

  // Return page output for Drupal.
 
return $output;
}
?>

If you've used theme('page', ...) as a wrapper on your return value, but only passed on the output (not any of the other parameters), just remove it. This was already the suggested behavior in previous Drupal versions, so it is time to get rid of theme('page').

Drupal 6:

<?php
function mymodule_page_callback() {
 
// Assemble page output here...

  // Return page output for Drupal.
 
return theme('page', $output);
}
?>

Drupal 7:

<?php
function mymodule_page_callback() {
 
// Assemble page output here...

  // Return page output for Drupal.
 
return $output;
}
?>

If you need to alter page attributes, this is how you do it:

Drupal 6:

<?php
function mymodule_page_callback() {
 
// Assemble page output here...

  // Return page output for Drupal, hide blocks.
 
return theme('page', $output, FALSE);
}
?>

Drupal 7:

<?php
function mymodule_page_callback() {
 
// Assemble page output here...

  // Set page content for Drupal.
 
drupal_set_page_content($output);
 
// Return page attributes, hide blocks.
  // Similarly, use #show_messages for the migration
  // of the third argument of Drupal 6's theme('page', ...).
 
$page = element_info('page');
 
$page['#show_blocks'] = FALSE;
  return
$page;
}
?>

In short, always return the main page content in a string or renderable array unless you need to modify page attributes. In this later case, use drupal_set_page_content() and element_info('page') and return the modified attributes.

Node access hooks now have drupal_alter() functions

Node access modules establish rules for user access to content. Node access records are stored in the {node_access} table and define which permissions are required to access a node.

Node access records are created with hook_node_access_records() and enforced by hook_node_grants().

In versions prior to Drupal 7, you could not modify the node access records or grants set by other modules. Now, to facilitate custom business logic, you may use a drupal_alter() hook to modify the records and grants passed by other modules.

hook_node_access_records_alter(&$grants, $node) runs after all node access records have been gathered, but before they are written to the database. Developers may modify the $grants array by reference to change the records stored in {node_access}.

hook_node_grants_alter(&$grants, $account, $op) runs after all user access grants have been gathered, but before those grants are used to restrict user access to node content. Developers may modify the $grants array by reference to change the queries run against the {node_access} table.

See the API documentation for example uses of these functions. You may also read the background for this feature.

Hide empty menu categories with access callback

(issue) Menu categories may be hidden when a user does not have permission to access any sub menu items by using system_admin_menu_block_access() as the access callback. For example, the following menu item represents an administration menu category with a number of sub menu items. If a user does not have access to any of the sub menu items the category item will still display.

<?php
$items
['admin/content'] = array(
 
'title' => 'Content management',
 
'description' => "Manage your site's content.",
 
'page callback' => 'system_admin_menu_block_page',
 
'access arguments' => array('access administration pages'),
);
?>

Once the menu category item has been converted to use the system_admin_menu_block_access() access callback it will be hidden when a user does not have access to any sub menu items.

<?php
$items
['admin/content'] = array(
 
'title' => 'Content management',
 
'description' => "Manage your site's content.",
 
'page callback' => 'system_admin_menu_block_page',
 
'access callback' => 'system_admin_menu_block_access',
 
'access arguments' => array('admin/content', 'access administration pages'),
);
?>

The first access arguments parameter is the url of the menu category item and the second is the permission the user needs to see the menu category item.

Commenting style - use 'Implement hook_X().' when documenting hooks

(issue) Commenting style for documenting hook_X() functions has changed.

Drupal 6:

<?php
/**
* Implementation of hook_help().
*/
function blog_help($section) {
?>

Drupal 7:

<?php
/**
* Implement hook_help().
*/
function blog_help($section) {
?>

node_get_types($op) replaced by node_type_get_$op()

(issue) Instead of a single function with several different return types, there are now several functions available.

Drupal 6:

<?php
// Load all node types and reset the static cache.
$types = node_get_types('types', NULL, TRUE);

// Get the node type name of a specific node.
$name = node_get_types('name', $node);
?>

Drupal 7:

<?php
// Load all node types and reset the static cache.
node_type_clear();
$types = node_type_get_types();

// Get the node type name of a specific node.
$name = node_type_get_name($node);
?>

As seen in the example, there is now a separate function, node_type_clear() to clear the static cache. For more documentation, see the API documentation.

Added hook_block_list_alter()

(issue)Modules can now add their own rules for block visibility to core's page, user and PHP defaults by manipulating the list of blocks before it is rendered. If your module currently uses db_rewrite_sql() or hook_query_alter() to alter the blocks list, you should consider converting to use this hook instead.

See the API documentation for details.

Renamed module_rebuild_cache() and system_theme_data() to system_get_module_data() and system_get_theme_data()

(issue)Those functions have been renamed to have a more consistent API and share internally used functions.

See the API documentation for details.

Added string context support to t() and format_plural(), changed parameters

(issue) Before Drupal 7, the localization system was only capable of mapping the same character sequence to the same translation for a given language, regardless of the usage context. This results in errors in translations when simple strings like "view" or "track" are translated. "Track" for example can be used as a label when tracking users or a label for a music track in a media module. While English has the same string for both cases, other languages need different translations. The same issue applies to "May" as a month name in Drupal core. English uses "May" for both the short and long name of the month, so it was impossible to translate it to a short and long version in other languages. Therefore Drupal 6 introduced the !long-month-name prefix for long month names, but that was a one-off hack.

In Drupal 7, optional translation contexts were added. To make the optional parameters to t() and format_plural() easier to use, this also resulted in the existing $langcode parameter being folded into an $options array.

In Drupal 6:

<?php
// Translate to German.
t('Welcome to our site', array(), 'de');
// Translate to German with replacement.
t('!user, welcome to our site', array('!user' => theme('username', $user)), 'de');
// Translate May in the long version to current page language.
t('!long-month-name May');
// Translate May in the short version to current page language.
t('May');
// Translate May in the long version to German.
t('!long-month-name May', array(), 'de');
// Translate May in the short version to German.
t('May', array(), 'de');
?>

In Drupal 7:

<?php
// Translate to German.
t('Welcome to our site', array(), array('langcode' => 'de'));
// Translate to German with replacement.
t('!user, welcome to our site', array('!user' => theme('username', $user)), array('langcode' => 'de'));
// Translate May in the long version to current page language.
t('May', array(), array('context' => 'Long month name'));
// Translate May in the short version to current page language.
t('May');
// Translate May in the long version to German.
t('May', array(), array('context' => 'Long month name', 'langcode' => 'de'));
// Translate May in the short version to German.
t('May', array(), array('langcode' => 'de'));
?>

To recap, $langcode becomes an $options array with the possible keys 'langcode' and 'context'. The 'context' is freely defined by your module, Drupal core only defines the 'Long month name' context which is used for the long month names. Because setting a custom 'context' disables sharing translations of your module strings with other modules not using the same context, you should think twice before introducing contexts.

AHAH Processing has changed; new 'callback' member of the array, and the callback must be rewritten

For #ahah members on a submit- or button-type form element, AHAH processing is now far easier. Instead of setting up a discrete path that is to be called and a callback function to call it, just use the 'callback' configuration element.

Note that the old 'path' technique is still supported, but that the callback will probably have to be rewritten anyway.

Drupal 6:

<?php
$item
['something'] = array(
 
'#type' => 'submit',
 
'#ahah' => array(
   
'path' => 'some/callback',    // A path, not a function
   
'wrapper' => 'wrapper-to-replace',
  ),
);
?>

Drupal 7:

<?php

$item
['something'] = array(
 
'#type' => 'submit',
 
'#ahah' => array(
   
'callback' => 'my_callback_function',   // not a path, a function
   
'wrapper' => 'wrapper-to-replace',
  ),
);
?>

In Drupal 7, if you're working with submit or button types, you do not have to declare the callback path in hook_menu(), and the callback function is far simpler.

Drupal 6:
(For full information on the callback in D6, see See http://drupal.org/node/331941)

<?php
// This function must be pointed to in hook_menu in D6
function my_callback_function() {
 
// ... Much work to build the form ....

 
drupal_process_form($form_id, $form, $form_state);
 
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);

 
$output = drupal_render($form);

 
// Final rendering callback.
 
print drupal_json(array('status' => TRUE, 'data' => $output));
  exit();
}
?>

Drupal 7:

<?php
// No path need be set up to point to the function, and $form and $form_state are provided
function my_callback_function($form,$form_state) {
 
$output = drupal_render($form);

 
drupal_json(array('status' => TRUE, 'data' => $output));
}
?>

Note, however, that for all form elements beside submit and button types, the old handling in the callback function (and the setup of a path to point to the callback) is still required. However, the code in the callback function must change; it can be based on form_ahah_callback() in includes/form.inc.

Alternative cache implementations changed.

Previously you put cache_get, cache_set and cache_clear_all in a file and changed the cache_inc variable to point to this file. You need to implement the get, set and clear methods which get the same arguments as they did in D6 minus the bin. The bin was called table in Drupal 6. So here are the steps to convert:

  1. Wrap your implementation in class MyCacheImplementation implements DrupalCacheInterface { ... }
  2. Change the function name to get, set, clear.
  3. Remove the $table argument from each function header.
  4. Add a new function
    <?php
    function __construct($bin) {
       
    $this->bin = $bin;
      }
    ?>
    .
  5. Search-and-replace $table with $this->bin.

$teaser parameter changed to $build_mode in node building functions and hooks, $node->build_mode property removed.

(issue) The $node->build_mode property, set before viewing a node, has been removed in favor of a new $build_mode string parameter for the various functions and hooks related to node view, in place of the previous $teaser boolean parameter.

Drupal 6:

<?php
$node
->build_mode = NODE_BUILD_NORMAL;
$output = node_view($node, TRUE);
?>

Drupal 7:

<?php
$content
= node_build($node, 'teaser');
$output = drupal_render($content);
?>

Drupal 6:

<?php
/**
* Implement hook_nodeapi().
*/
function comment_nodeapi($op, $node, $teaser) {
  switch (
$op) {
    ...
    case
'view':
      if (
$node->build_mode == NODE_BUILD_RSS) {
        ...
      }
      break;
    ...
  }
}
?>

Drupal 7:

<?php
/**
* Implement hook_node_view().
*/
function comment_node_view($node, $build_mode) {
  if (
$build_mode == 'rss') {
    ...
  }
}
?>

The affected functions and hooks are: node_build() (previously node_view() in Drupal 6 - see here), hook_view(), hook_node_view() (previously node_nodeapi('view') in Drupal 6 - see here), hook_node_build_alter() (new in Drupal 7), hook_link()

An additional $build_mode variable is available in node templates. For easier theme portability, the previous $teaser variable is still declared ( == ('$build_mode = 'teaser')).

Core-defined build modes have been moved from numeric constants to plain strings, to make it easier for contributed modules to expose additional build modes (in hook_field_build_modes()):
NODE_BUILD_NORMAL (0) is now either 'full' or 'teaser'
NODE_BUILD_PREVIEW (1) was replaced by a $node->in_preview boolean flag.
NODE_BUILD_SEARCH_INDEX (2) is now 'search_index'
NODE_BUILD_SEARCH_RESULT (3) is now 'search_result'
NODE_BUILD_RSS (4) is now 'rss'
NODE_BUILD_PRINT(5) is now 'print'

Contributed modules are encouraged to declare new build modes when they need a custom presentation of a node. Site admins will then be able to manage which fields appear and how those fields are formatted using the Fields UI. For example, an 'Email this page to a friend' module would declare an 'email to friend' build module using hook_field_build_modes().

comment_save() now supports programatic saving.

All form-related logic has been removed from comment_save(), making it possible to use the function to save comments programatically, similar to node_save(). The function arguments and their structure remain unchanged, but be aware that no validation is performed. Sensible defaults are provided for the following values: mail, homepage, name, status, timestamp -- all other data related to the inserting/updating of a comment is required.

comment_validate() has been removed.

Use comment_form_validate() for all form-related validation of comments.

Login change for distributed authentication.

Modules which perform external authentication should now set $form_state['uid'] to the ID of the user who is logging in. They should not load global $user anymore. If wrong credentials are presented, modules continue to do nothing - core will return an invalid credentials message.

jQuery UI (1.7) was added into core.

(issue) jQuery UI 1.7 was added into core. You can find the jQuery UI files in misc/ui and can add Javascript and CSS files from there to your pages with the regular drupal_add_js() and drupal_add_css() calls, no special function calls required. If you are migrating a module or custom code which was previously dependent on the jquery_ui contributed module, the difference in usage is:

Drupal 6 with jquery_ui module:

<?php

jquery_ui_add
(array('ui.draggable', 'ui.droppable'));
?>

Drupal 7 core:

<?php
drupal_add_js
('misc/ui/ui.core.js', array('weight' => JS_LIBRARY + 5));
drupal_add_js('misc/ui/ui.draggable.js', array('weight' => JS_LIBRARY + 6));
drupal_add_js('misc/ui/ui.droppable.js', array('weight' => JS_LIBRARY + 7));
?>

Note that you need to make sure that the weights are set appropriately for dependent elements and that component dependencies are included on the page.

Drupal ships with the minified versions of jQuery UI files, just like we do in case of jQuery itself.

comment_node_url() has been removed

(issue) Use 'node/' . $comment->nid to build URLs instead.

#theme recommended instead of calling theme() directly.

(issue)Now that menu callbacks should return arrays, we encourage renderable elements to use #theme to specify a theme function instead of using #markup => theme('foo', ...). This delays theming until later which gives hook_page_alter() a chance to make changes. Note that you may pass parameters to theme() by creating #properties that match the name of the theme function parameter. For example:

<?php
//OLD
$node->content['extra_picture'] = array(
 
'#markup' => theme('image', 'http://foo.com/test.jpg', NULL, t('A title'), array('class' => 'external-image'), FALSE),
 
'#weight' => 5,
);

//NEW
$node->content['my_extras'] = array(
 
'#theme' => 'image',
 
'#path' => 'http://foo.com/test.jpg',
 
'#title' => t('A title'),
 
'#attributes' => array('class' => 'external-image'),
 
'#getsize' => FALSE,
 
'#weight' => 5,
);
?>

hook_perm() renamed to hook_permission()

(issue) For clarity, hook_perm() has been renamed hook_permission().

Coding standards changes

drewish - October 15, 2008 - 18:56

I just noticed that we don't have upgrade instructions for changes to the coding standards. The two that I can think of are

Concatenation

D6:

<?php
$path
= 'node/'. $node->nid .'/edit';
?>

D7:

<?php
$path
= 'node/' . $node->nid . '/edit';
?>

ElseIf

D6:

<?php
if (FALSE) {
 
// Something
}
else if (
$foo) {
 
// Something else
}
?>

D7:

<?php
if (FALSE) {
 
// Something
}
elseif (
$foo) {
 
// Something else
}
?>

A few more things that we've

Dave Reid - December 27, 2008 - 20:32

A few more things that we've decided on:

References: Keep the '&' next to the variable being referenced.

<?php
$reference_var
= &$original_var;

function
myfunction(&$arg_reference);
?>

Type hinting: Use type hinting wherever possible. If your parameter is an array, use the lower-case 'array' like we do when we declare an array. For the standard PHP class, use 'stdClass'. For all other classes, use the class name. See http://www.php.net/language.oop5.typehinting for more information.

<?php
function myfunction(array $arg1 = array(), stdClass $arg2 = NULL, MyClassName $arg3) { ... }
?>

Regarding references

alienbrain - May 10, 2009 - 12:14

We don't seem to be keeping the '&', check http://drupal.org/node/457532

I only removed it from

Berdir - June 4, 2009 - 08:04

I only removed it from objects, because we don't need it anymore there. We stil need and keep it for arrays and other variables.

hook_node_[op]()

yched - June 22, 2009 - 23:36

Remove $op from hook_nodeapi and hook_user mentions the hook_nodeapi($op) -> hook_nodeapi_[op]() change, but we later did hook_nodeapi_[op]() -> hook_node_[op](), which I don't see mentioned anywhere.

 
 

Drupal is a registered trademark of Dries Buytaert.