Converting 6.x modules to 7.x
Overview of Drupal API changes in 7.x
Annotation
- [X] indicates a change that has been cross-referenced in the Categorical change list.
UNSTABLE-1
- Drupal 7 requires PHP 5.2 or higher
- [X] Permissions are required to have titles and descriptions
- [X] Permissions are no longer sorted alphabetically
- [X] _comment_load() is now comment_load()
- [X] Module .info files must now specify all loadable code files explicitly
- Rolled back
The hook_menu() and hook_theme() "file" and "file path" keys have been removed - [X] New permission tables
- [X] Use '#markup' not '#value' for markup
- [X] Comment status values in the database have flipped so they match node status
- [X] Rebuild functions have changed names
- [X] Use defined constant REQUEST_TIME instead of time()
- [X] referer_uri() has been removed
- [X] Some
#processfunctions have been renamed - [X] A completely new database API has been added
- [X] file_validate_extensions() enforces check for uid=1
- [X] file_scan_directory() and drupal_system_listing() use preg regular expressions
- [X] Easier check for node form during hook_form_alter()
- [X] Update functions in .install files must include a Doxygen style comment
- [X] New #text_format property to assign text format selection to fields. Changes 'body' field location in node, comment, block, etc
- [X] Replace drupal_clone() with clone
UNSTABLE-2
- [X] Permissions are required to have titles and descriptions
- [X] Remove $op from hook_nodeapi, hook_node_type, hook_user, and hook_block
- [X] In hook_node_info() change 'module' back to 'base' and change 'node' to 'node_content'
- [X] Use absolute path (constructed from DRUPAL_ROOT) when including a file
- [X] File operations that don't affect the database have been renamed
- [X] "administer nodes" permission split into "administer nodes" and "bypass node access"
- [X] New hooks: hook_modules_installed, hook_modules_enabled, hook_modules_disabled, and hook_modules_uninstalled
- [X] drupal_uninstall_module() is now drupal_uninstall_modules()
UNSTABLE-3
- [X] drupal_set_title() uses check_plain() by default
- [X] Changed parameters for drupal_add_js() and drupal_add_css()
- [X] Changed Drupal.behaviors to objects having the methods 'attach' and 'detach'
- [X] Taxonomy CRUD functions renamed and refactored
- [X] New taxonomy hooks for term and vocabulary
- [X] Removed file_set_status()
- [X] Replace 'core', 'module' and 'theme' with 'file' in drupal_add_js()
- [X] Parameters to check_markup() have changed
UNSTABLE-4
- [X] Schema descriptions are no longer translated
- [X] file_scan_directory() now uses a preg regular expression for the nomask parameter
- [X] Use module_implements not module_list when calling hook implementations
- [X] New hook_js_alter to alter JavaScript
- [X] Changed log out path from 'logout' to 'user/logout' for consistency
- [X] node_load() and node_load_multiple()
- [X] taxonomy_term_load() and taxonomy_term_load_multiple()
- [X] file_load_multiple()
- [X] Taxonomy CRUD functions renamed and refactored
- [X] New taxonomy hooks for term and vocabulary
- [X] Code documentation added to module.api.php
- [X] taxonomy_get_tree()
- [X] Move node, taxonomy, and comment links into $node->content. Deprecate hook_link().
- [X] Remove $op from hook_nodeapi, hook_node_type, hook_user, and hook_block
- [X] Parameters for actions_synchronize() have changed
- [X] Parameters for drupal_http_request() have changed
- [X] db_rewrite_sql() replaced with hook_query_alter()
- [X] Removed FILE_STATUS_TEMPORARY
- [X] Renamed user_delete() to user_cancel(), likewise renamed hook_user_delete() to hook_user_cancel()
- [X] Taxonomy db table names have changed to begin with 'taxonomy_'
UNSTABLE-5
- [X] User pictures are now managed files
- [X] drupal_set_session() replaces $_SESSION [NEEDS UPDATE]
- [X] Ability to reset JavaScript and CSS
- [X] Moved statistics settings from admin/reports/settings to admin/settings/statistics and added a new 'administer statistics' permission
- [X] Default parameter when getting variables
- [X] Menu page callbacks and blocks should return an array and hook_page_alter()
- [X] Block module now optional
- [X] Element #type property no longer treated as a theme function in drupal_render()
- [X] Use drupal_render_children() to render an element's children
- [X] Replace node_view() with node_build()
UNSTABLE-6
- [X] JavaScript should be compatible with other libraries than jQuery
- [X] file_scan_directory()'s optional parameters are now an array
- [X] External JavaScript can now be referenced through drupal_add_js()
- [X] user_load_multiple() and hook_user_load()
- [X] jQuery 1.3.x
- [X] Settings passed locally to JavaScript Behaviors
- [X] file_scan_directory() now uses same property names as file_load()
UNSTABLE-7
- [X] Moved filter module administrative URLs from admin/settings/filters/* to admin/settings/filter/*
- [X] Added taxonomy_vocabulary_load_multiple()
- [X] Added a new top-level 'international' admin menu item
- [X] Changed hook_menu_link_alter() (removed the $menu parameter)
- [X] Standardized API for static variables and resetting them
- [X] The function drupal_set_html_head() has been renamed to drupal_add_html_head()
- [X] Inline cascading stylesheets from drupal_add_css()
- [X] Attached JavaScript and CSS for forms
- [X] Make sticky tableheaders optional
- [X] Save new users and nodes with specified IDs
- [X] Parameters swapped in book_toc()
- [X] drupal_execute() renamed to drupal_form_submit()
- [X] node_invoke_nodeapi() removed
- [X] Removed $op "rss item" from hook_nodeapi() in favor of NODE_BUILD_RSS
- [X] drupal_eval() renamed to php_eval
- [X] "use PHP for settings" permission should be used for all PHP settings rights (replaces "use PHP for block visibility")
- [X] Changes to HTTP header functions
- [X] drupal_get_form() returns an unrendered array
- [X] Add Doxygen @file tag to all install files
- [X] Add node_delete_multiple()
- [X] Renamed drupal_set_content() and drupal_get_content()
- [X] Instead of theme('page', ...), think of drupal_set_page_content()
UNSTABLE-8
- [X] Node access hooks now have drupal_alter() functions
- [X] Hide empty menu categories with access callback
- [X] Commenting style - use 'Implement hook_foo().' when documenting hooks
- [X] node_get_types($op) replaced by node_type_get_$op()
- [X] Added hook_block_info_alter()
- Renamed module_rebuild_cache() to system_rebuild_module_data(), renamed system_theme_data() to system_rebuild_theme_data(), and added system_get_info()
- [X] Added string context support to t() and format_plural(), changed parameters
- [X] Alternative cache implementations changed
- [X] $teaser parameter changed to $build_mode in node building functions and hooks, $node->build_mode property removed
- [X] comment_save() now supports programmatic saving
- [X] comment_validate() has been removed
- [X] Login validation change for distributed authentication modules
- [X] jQuery UI (1.7) was added into core
- [X] comment_node_url() has been removed
- [X] #theme recommended for specifying theme function
- [X] hook_perm() renamed to hook_permission()
- [X] Ability to add multiple JavaScript/CSS files at once
- [X] Removed taxonomy module support for multiple tids and depth in term paths
- [X] file_check_directory() will now recursively create directories
- [X] Added comment_load_multiple() and hook_comment_load()
- [X] hook_node_access_records() now applies to unpublished nodes; 'view own unpublished content' permission added
- [X] New tar archive library added
UNSTABLE-9
- [X] hook_footer() was removed, $closure became $page_bottom, $page_top added
- [X] Schema descriptions are now plain text instead of HTML
- [X] Related terms functionality was removed from taxonomy.module
- [X] Do not use SELECT COUNT(*) to check for existence of rows
- [X] Added drupal_set_time_limit()
- [X] Module .info files can now optionally specify the version number of the module it depends on
- [X] hook_nodeapi_xxx() becomes hook_node_xxx()
- [X] .module file available during install
- [X] Parameters to function user_authenticate() changed
- [X] JavaScript variable Drupal.jsEnabled has been removed
xmlrpc() wrapper function removed- [X] Foreign keys added to core database table schema
- [X] Removed several unnecessary arguments to various hook_user_$op hooks and removed hook_profile_alter
- [X] Many paths to admin screens have changed
- [X] Remove $op from hook_nodeapi, hook_node_type, hook_user, and hook_block
- [X] drupal_add_css() now supports external CSS files
- [X] New hook_comment_presave() for comments
- [X] Weighting of stylesheets
- [X] AHAH/AJAX Processing has changed; #ajax, new 'callback' member of the array, and the callback must be rewritten
- [X] hook_access() removed in favor of hook_node_access()
- [X] hook_filter() and hook_filter_tips() replaced by hook_filter_info()
- [X] Convert class attributes to array in favor of a string
- [X] Schema API now supports date and time types natively
- [X] Added API functions for creating, loading, updating, and deleting user roles and permissions
- [X] New hook: hook_file_url_alter()
- [X] jQuery Once method for applying JavaScript behaviors once
- [X] Database schema (un)installed automatically
- [X] User 1 is now called site maintenance account
- [X] CRUD hooks for menu links: hook_menu_link_insert(), hook_menu_link_update(), hook_menu_link_delete()
- [X] Default text formats have been revamped
- [X] Text formats access is now controlled by permissions, and filter_access() parameters have changed
- [X] The parameters to filter_formats() have changed
- [X] Rename drupal_to_js() and drupal_json() to drupal_json_encode() and drupal_json_output()
- [X] theme_links() has a new parameter $heading for accessibility
- [X] API for modules providing search has changed
- [X] All taxonomy functions relating to nodes have been removed or refactored.
- [X] Added hook_entity_load().
UNSTABLE-10
- [X] Trigger and Actions API overhaul
- [X] theme() now takes only takes two arguments.
- [] drupal_alter() now takes at most 3 parameters by reference.
- [] The $ret parameter has been removed from all Schema operations.
- [] The signature of the callback from drupal_get_form() changed to add $form
- [] Replaced taxonomy_term_path(), hook_term_path(), language_url_rewrite(), and custom_url_alter_outbound() with hook_url_outbound_alter()
- [] Replaced custom_url_rewrite_inbound() with hook_url_inbound_alter()
- [] Renamed menu_path_is_external() to url_is_external()
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_rolesDrupal 7 schema:
user
user_roleDrupal 7 requires PHP 5.2 or higher
Drupal 7 now requires PHP 5.2 or higher, so you can safely use and rely on PHP 5.2 functions and features in your modules. In particular, PHP 5 always passes objects into functions by reference; this includes objects inside arrays (the array is not passed by reference, but the objects inside the array are references). This means that you can omit the & to indicate passing by reference in function arguments if the argument is either an object or an array of objects. However, if the argument is an array of objects and you need to add or remove elements to the array rather than just modify objects within the array, you will still need the & so that the array will be passed by reference.
Example - Drupal 6:
mymodule_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if( $op == 'load' ) {
$node->mymodule_special = 'my value';
}
}Drupal 7:
mymodule_node_load($nodes, $types) {
foreach( $nodes as $node ) {
$node->mymodule_special = 'my value';
}
}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:
- administer-related permissions
- access-related permissions
- other global-level permissions
- create-related permissions
- edit-related permissions
- delete-related permissions
- 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) and partial roll-back. Drupal now supports a dynamic-loading code registry for classes and interfaces. 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.installWhen a module is enabled, Drupal will rescan all declared files and index all classes and interfaces it finds. That means any class or interface code 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.
The hook_menu() and hook_theme() "file" and "file path" keys have been removed
Roll-back patch
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) . ' <' . check_plain($user->mail) . '>',
);
?>In Drupal 7:
<?php
$form['from'] = array(
'#type' => 'item',
'#title' => t('From'),
'#markup' => check_plain($user->name) . ' <' . check_plain($user->mail) . '>',
);
?>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 0The 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 occurred:
| 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 type = '%s'", 5, 'page');
// Drupal 7
$result = db_query("SELECT nid, title FROM {node} WHERE uid = :uid AND type = :type", array(
':uid' => 5,
':type' => 'page',
));
?>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().
In addition tablesort_sql() has gone away and is replaced by an extender to the query object. See Table Sorting in the new database documentation.
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_default_format(),
'#required' => TRUE,
);
?>(Note that the switch from FILTER_FORMAT_DEFAULT to filter_default_format() is for a separate reason, discussed below.)
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_node_type, hook_user, and hook_block
(hook_nodeapi issue, hook_user issue, released in UNSTABLE-2) (hook_block issue, released in UNSTABLE-4) (hook_node_type issue)
In Drupal 7, several hooks that previously allowed modules to do a group of related operations, with a $op argument, were split into individual hooks for each operation. Gábor posted a note in the developer mailing list about this if you'd like to read more about it.
The hooks that were changed in this way:
- hook_nodeapi() in Drupal 6 became (see Node API page) hook_node_delete(), hook_node_insert(), hook_node_load(), hook_node_prepare(), hook_node_prepare_translation(), hook_node_search_result(), hook_node_presave(), hook_node_update(), hook_node_update_index(), hook_node_validate(), and hook_node_view().
- hook_node_type() in Drupal 6 became (see Node API page) hook_node_type_delete(), hook_node_type_insert(), andhook_node_type_update().
- hook_user() in Drupal 6 became (see User API page) hook_user_categories(), hook_user_insert(), hook_user_load(), hook_user_login(), hook_user_logout(), hook_user_presave(), hook_user_update(), and hook_user_view().
- hook_block() in Drupal 6 became (see Block API page) hook_block_info(), hook_block_configure(), hook_block_save(), and hook_block_view().
- The "form" and "register" operations have been removed. Use hook_form_alter instead.
Here are some examples - 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_node_type().
*/
function mymodule_node_type($op, $info) {
switch ($op){
case 'delete':
// delete, insert or update according to $op
}
}
/**
* 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_node_load().
*/
function book_node_load($nodes, $types) {
// 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
}
/**
* Implementation of hook_node_type_delete().
*/
function mymodule_node_type_delete($info) {
variable_del('comment_' . $info->type);
}
/**
* Implementation of hook_node_type_update().
*/
function hook_node_update($node) {
db_update('mytable')
->fields(array('extra' => $node->extra))
->condition('nid', $node->nid)
->execute();
}
/**
* Implementation of hook_node_type_insert().
*/
function hook_node_insert($node) {
db_insert('mytable')
->fields(array(
'nid' => $node->nid,
'extra' => $node->extra,
))
->execute();
}
?>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' must be removed or else css will not be added!
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');
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 than 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 its 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 than in what order drupal_add_js() was called. Rather than 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 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() now uses a preg regular expression for the nomask parameter
(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_node_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().
- To load a vocabulary, keep using taxonomy_vocabulary_load().
- (Issue) To save vocabulary, you should now use taxonomy_vocabulary_save() which has replaced taxonomy_save_vocabulary(). Note that the new function takes a vocabulary object as a parameter instead of an array.
- (Issue) To delete a vocabulary, you should now use taxonomy_vocabulary_delete() which has replaced taxonomy_del_vocabulary().
New taxonomy hooks for term and vocabulary
(Issue) New hooks are provided for taxonomy terms to allow modules to add information to the term object.
- hook_taxonomy_term_load
- hook_taxonomy_term_insert
- hook_taxonomy_term_update
- hook_taxonomy_term_delete
- hook_taxonomy_vocabulary_load
- hook_taxonomy_vocabulary_insert
- hook_taxonomy_vocabulary_update
- hook_taxonomy_vocabulary_delete
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.
Move node, taxonomy, and comment links into $node->content. Deprecate hook_link().
(issue) Node and taxonomy links are no longer emitted by hook_link() and hook_link_alter(). Instead, links are added to $node->content during hook_node_view(). Similarly, comment links are now injected into $comment->content during hook_comment_view(). Links and all of $node->content may be re-arranged/changed during the new hook: hook_node_view_alter() and new hook: hook_comment_view_alter(). 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(
'#theme' => 'links',
'#links' => $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
[NEEDS UPDATE] Per discussion in IRC with Damien:
- from a DX perspective, yes, no change
- but the reverse proxy changes are still there, including the lazy session start
- so we probably need to keep a paragraph about that
- we still do the same thing, but developers can access $_SESSION directly
- what we need here is a paragraph that tell module developers not to over use the session, and to clean up session data as soon as possible
(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, which 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');
?>Menu page callbacks and blocks should return an array and hook_page_alter()
(issue) Menu page 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() to change the contents of the array describing the page. 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 #type property no longer treated as a theme function in drupal_render()
(issue) In previous versions of Drupal, drupal_render() used the value of an element's #type property as a theme function, and used the #theme property (if it was present) to override the default rendering of the element's children. Now, there are two explicit properties that control HTML generation: #theme and #theme_wrapper. #theme is the same as in Drupal 6: it can be used to override the normal rendering of the element's children. #theme_wrapper is called afterwards, and can be used to wrap markup around the rendered children. drupal_render() no longer uses the #type property to control theming. In addition, both #theme and #theme_wrapper can be overridden in alter hooks without changing an element's #type.
Use drupal_render_children() to render an element's children
In Drupal 6, when an element was passed into drupal_render() with a custom #theme property, that theme function was expected to call drupal_render($element) a second time to render the element's children. In Drupal 7, calling drupal_render() on an element in that element's own theme function will cause an endless loop. The drupal_render_children($elements) function should be called instead.
Also see the API docs for drupal_render()
Drupal 6.x:
<?php
function theme_my_custom_element($element) {
// Render one specific child of the current element
$output = '<blink>' . drupal_render($element['child1']) . '</blink>';
// Render all the remaining children of the current element
$output .= drupal_render($element);
}
?>Drupal 7.x:
<?php
function theme_my_custom_element($element) {
// Render one specific child of the current element
$output = '<blink>' . drupal_render($element['child1']) . '</blink>';
// Render all the remaining children of the current element
$output .= drupal_render_children($element);
}
?>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 without 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 Drupal's 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()
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.
Changed hook_menu_link_alter() (removed the $menu parameter)
(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:
<?php
$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.
If you need more than one static variable in the same function, you should store them in an array.
<?php
$cache = &drupal_static(__FUNCTION__, array(
'var1' => 'foo',
'var2' => 'bar',
);
?>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 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:
Note: for a while, it was suggested to use #attached_css and #attached_js. Those functions are outdated and no longer work. Please use the examples below (['#attached']['css'] & ['#attached']['js'])
<?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.");' => array('type' => '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 new users and nodes 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 its 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 an 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, you can use the following approach. This can be used to alter the page output for one of your module's page callbacks. For example, to have one of your module's page callbacks not display any sidebars:
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, and add a custom attribute to
// look for in hook_page_alter().
$page = element_info('page');
$page['#mymodule_hide_blocks'] = TRUE;
return $page;
}
function mymodule_page_alter() {
// If the custom attribute is present, hide the blocks.
if (isset($page['#mymodule_hide_blocks'])) {
unset($page['sidebar_first'], $page['sidebar_second']);
}
}
?>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 now be hidden via the function system_admin_menu_block_access() when a user does not have permission to access any sub menu items. Once the menu category item has been updated to use access arguments menu categories with no accessible sub menu items will be hidden from users. If the new option is omitted administration menu items will behave as they did in Drupal 6 (categories will display even if no sub menu items are accessible to the user).
In Drupal 7:
<?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 parameters for access arguments are as follows:
- url of the menu category item
- the permission the user needs to see the menu category item
Commenting style - use 'Implement hook_foo().' 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_info_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() to system_rebuild_module_data(), renamed system_theme_data() to system_rebuild_theme_data(), and added system_get_info()
Issues:
#147000: Regression: Unify and rewrite module_rebuild_cache() and system_theme_data()
#561452: Add API function to get module/theme info without rebuilding it.
The existing functions for rebuilding and returning information about modules and themes have been renamed to have a more consistent API and share internally used functions.
In addition, a new, simple function has been added to return stored information about active modules or themes from the database (without forcing a full rebuild of that information via a scan of the filesystem).
See the API documentation for system_rebuild_module_data(), system_rebuild_theme_data(), and system_get_info() 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/AJAX Processing has changed; #ajax, new 'callback' member of the array, and the callback must be rewritten
There are several changes here:
- #ahah (D6) is now #ajax (D7).
- The 'path' is now replaced in most cases by 'callback' (They are mutually exclusive.)
- Wrapper replacement has changed.
AHAH processing is now called AJAX processing, and the Forms API marker changes from #ahah to #ajax.
In addition, AJAX/AHAH processing is now far easier. Instead of setting up a discrete menu path and a callback function to be called by it, just use the 'callback' configuration element.
Note that the old 'path' technique is still supported by #ajax, but the menu function it would call must be rewritten if you stay with 'path'.
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',
'#ajax' => array(
'callback' => 'my_callback_function', // a function, not a path
'wrapper' => 'wrapper-to-replace',
),
);
?>In Drupal 7, 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 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) {
// Often you would select a portion of the form to match #ajax['wrapper']
// rather than processing the whole form.
return $form; // Either HTML or a renderable array may be returned.
}
?>Finally, wrapper replacement has changed. In Drupal 6, the javascript code replace only the contents of the wrapper element. In Drupal 7, it replaces the entire element. So for a wrapper with ID 'my-wrapper'
<div id="my-wrapper">some contents</div>in Drupal 7, the replacement html returned by the callback or path function would be the entire div, whereas in Drupal 6 it would have been just the replacement text for "some contents".
The Drupal 7 version of the examples module now has an AJAX example showing in depth how AJAX can be used with form elements.
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:
- Wrap your implementation in
class MyCacheImplementation implements DrupalCacheInterface { ... } - Change the function name to get, set, clear.
- Remove the $table argument from each function header.
- Add a new function .
<?php
function __construct($bin) {
$this->bin = $bin;
}
?> - 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 mode using hook_field_build_modes().
comment_save() now supports programmatic saving
All form-related logic has been removed from comment_save(), making it possible to use the function to save comments programmatically, 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
(issue) Use comment_form_validate() for all form-related validation of comments.
Login validation change for distributed authentication modules
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.accordion', 'ui.dialog'));
?>Drupal 7 core:
<?php
drupal_add_library('system', 'ui.accordion');
drupal_add_library('system', 'ui.dialog');
?>Read more about drupal_add_library below. 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 'comment/' . $comment->cid to build URLs instead.
#theme recommended for specifying theme function
(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. Note that this feature applies to theme functions, not theme templates. 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().
Ability to add multiple JavaScript/CSS files at once
(issue) You can add related sets of JavaScript and CSS at once now with the drupal_add_library() function. The first argument is the name of the module that registers the library (via hook_library), while the second argument is the name of the library you want to add.
Drupal 6:
<?php
// Add Farbtastic color picker.
drupal_add_js('misc/farbtastic/farbtastic.js');
drupal_add_css('misc/farbtastic/farbtastic.css');
?>Drupal 7:
<?php
// Add Farbtastic color picker.
drupal_add_library('system', 'farbtastic');
?>These sets of JavaScript/CSS libraries must be registered through hook_library:
<?php
function system_library() {
$libraries['farbtastic'] = array(
'title' => 'Farbtastic',
'website' => 'http://code.google.com/p/farbtastic/',
'version' => '1.2',
'js' => array(
'misc/farbtastic/farbtastic.js' => array(),
),
'css' => array(
'misc/farbtastic/farbtastic.css' => array('preprocess' => FALSE),
),
);
return $libraries;
}
?>The library registry can also be altered through hook_library_alter. This becomes useful if you are updating the library to a newer version.
Removed taxonomy module support for multiple tids and depth in term paths
(issue). Support for multiple tids and depth arguments for taxonomy term paths has been removed from core. This feature is still provided by Views, so if your module relies on those paths, you should add Views as a dependency and/or provide default views providing the same behaviour.
file_check_directory() will now recursively create directories
(issue). Previously file_check_directory() would only create a directory if all its parent directories already existed. PHP 5 added the feature to mkdir() to make it create directories recursively. Which we can now use since Drupal 7 requires PHP 5.
Drupal 6:
<?php
// Recursively add new directories if the parent does not exist.
$full_path = '';
foreach (explode('/', $directory) as $path) {
$full_path .= $path . '/';
file_check_directory($full_path, FILE_CREATE_DIRECTORY);
}
?>Drupal 7:
<?php
// Recursively add new directories if the parent does not exist.
file_check_directory($directory, FILE_CREATE_DIRECTORY);
?>Added comment_load_multiple() and hook_comment_load()
(issue) . Comment module was converted from using direct queries in comment_render() to a new comment_load_multiple() central function for loading comments. If loading comments outside comment_render(), you should use comment_load() or comment_load_multiple() to ensure all hooks are correctly fired. See the API documentation for further details.
hook_node_access_records() now applies to unpublished nodes; 'view own unpublished content' permission added
(issue) In Drupal 6, any node grants provided by hook_node_access_records() would only apply to published nodes. For unpublished nodes, users were granted view access if they were the author.
In Drupal 7, hook_node_access_records() now applies to both published and unpublished nodes, giving the developer more flexibility to manage node access. Also, the 'view own unpublished content' was added to still grant users view access to their own unpublished content. This permission is granted to authenticated users by default in a Drupal 7 installation or upgrade.
If developers still want their implementation of hook_node_access_records() to apply only to published nodes in Drupal 7, they need only add these three lines of code to the top of the function.
<?php
if (!$node->status) {
return;
}
?>hook_footer() was removed, $closure became $page_bottom, $page_top added
(issue 1), (issue 2) Drupal 6 used hook_footer() to add content to a special variable named $closure which was mandatory to put in themes to the bottom of the HTML body output. Drupal 7 generalizes this under hook_page_alter() and hidden regions.
Drupal 7 now comes with two hidden default regions internally named page_bottom and page_top. They are hidden in that they will not show up in the blocks administration interface, but they can receive programmatic data. The corresponding template variables are $page_bottom and $page_top, and $page_bottom became the successor to $closure. Since you can use the hook_page_alter() function in Drupal 7 to add content to regions, hook_footer() and consequently theme_closure() were removed.
Drupal 6:
<?php
/**
* Implementation of hook_footer().
*/
function example_footer($main = 0) {
if (variable_get('dev_query', 0)) {
return '<div style="clear:both;">'. devel_query_table() .'</div>';
}
}
?>Drupal 7:
<?php
/**
* Implement hook_page_alter().
*/
function example_page_alter(&$page) {
if (variable_get('dev_query', 0)) {
$page['page_bottom']['devel']= array(
'#type' => 'markup',
'#markup' => '<div style="clear:both;">' . devel_query_table() . '</div>',
);
}
}
?>Note that you need to return a renderable array construct and not simply plain HTML. Ideally, you'd not pre-render the output of your code and return a fully renderable structure instead.
Although hook_footer() did not have a counterpart to add content to the top of the markup output, Drupal 7 adds the special $page_top region, which serves this purpose and can be used just like $page_bottom.
Schema descriptions are now plain text instead of HTML
(issue) Descriptions of the schema API (tables and fields) in module.install are now non-markup plain text, because HTML is not supported by tools like phpMyAdmin.
6.x:
<?php
function foo_schema() {
$schema['foo_url'] = array(
'description' => t('Stores URLs that appear in <a href=....> tags.'),
'fields' => array(
'url' => array(
'description' => t('The URL.'),
),
),
);
return $schema;
}
?>
7.x:
<?php
function foo_schema() {
$schema['foo_url'] = array(
'description' => 'Stores URLs that appear in <a href=....> tags.',
'fields' => array(
'url' => array(
'description' => 'The URL.',
),
),
);
return $schema;
}
?>As mentioned above, the strings no longer needs to be translated using t().
Related terms was removed from taxonomy.module
(issue)
All code referring to related terms has been removed from taxonomy.module. The taxonomy_term_relations table has been left in place to allow for an upgrade path for data. This functionality has been superceded by the Field API, which allows fields to be attached to terms, and provides a term reference field in core.
Do not use SELECT COUNT(*) when checking for existence of rows in a table
(issue)
Drupal 7 now creates InnoDB tables by default, which do not perform well when performing SELECT COUNT(*) FROM table; queries. If you only need to check the existence of rows in a table, you should use
Drupal 6:
<?php
$has_rows = db_result(db_query("SELECT COUNT(*) FROM {mytable}"));
?>Drupal 7:
<?php
$has_rows = (bool) db_query_range('SELECT 1 FROM {mytable}', 0, 1)->fetchField();
?>If you do in fact need to know the number of rows in a table or in a given result set, you should still use COUNT(*) as before.
Added drupal_set_time_limit()
(issue)
This function is a wrapper around set_time_limit(), it has been created to centralize this operation. It avoids the repetition of the code to check the basic requirements and also suppresses all the warnings and errors which could be caused by this function (set_time_limit is sensitive to the environment settings like safe_mode, disabled functions and various security extensions). See the API documentation for further details.
Module .info files can now optionally specify the version number of the module it depends on
Starting with Drupal 7, modules, in their .info files, can optionally specify the version numbers of the modules they depend on, with optional greater than or lesser than operators. For example, dependencies[] = views (2.x) means that the module requires the Views module on the major version 2, with any minor version. dependencies[] = views (>2.1) means that the module requires Views with a version number greater than 2.1. "Greater than or equal to" and "lesser than or equal to" is also supported. No operator means that equals is assumed, but "=" and "==" are also allowed. Modules can also optionally specify the Drupal core branch in the version number. See http://drupal.org/node/542202#dependencies for details.
hook_nodeapi_xxx() becomes hook_node_xxx()
$op was removed from hook_nodeapi(), and the hook split into multiple hooks. Later in development, hook_nodeapi_xxx() was also renamed to hook_node_xxx(). All information is in the updated discussion about the hook_nodeapi() $op removal.
.module file available during install
The .module file and its functions are now loaded during the install process, as long as hook_requirements() is satisfied. In Drupal 6, a .install file that wanted to use a function from the .module file would have to load it explicitly, but that is no longer the case. Now, as long as hook_requirements() is satisfied, the .module file is loaded and available during the install process.
Parameters to function user_authenticate() changed
Function user_authenticate() is now a general API function that can be used outside the context of a form submission. It now takes a user name and a plain-text password as parameters, and returns the uid on success, or FALSE on failure.
Drupal 6.x:
<?php
/**
* Try to log in the user locally. $form_state['uid'] is set to
* a user ID if successful.
*
* @param $form_state
* Form submission state with at least 'name' and 'pass' keys.
*/
function user_authenticate(&$form_state) {
...
}
?>Drupal 7.x:
<?php
/**
* Try to validate the user's login credentials locally.
*
* @param $name
* User name to authenticate.
* @param $password
* A plain-text password, such as trimmed text from form values.
* @return
* The user's uid on success, or FALSE on failure to authenticate.
*/
function user_authenticate($name, $password) {
...
}
?>JavaScript variable Drupal.jsEnabled has been removed
In previous versions of Drupal, you could do the following in JavaScript code, to verify that JavaScript was enabled and sufficient for Drupal to do its "behaviors":
if( Drupal.jsEnabled ) {
// something
}In Drupal 7, the Drupal.jsEnabled variable is no longer defined, and there is no work-around -- the assumption is that jQuery things will simply not work if they don't work, so there's no reason to check ahead of time. See issue #444352: Kill the killswitch for discussion.
xmlrpc() wrapper function removed
Issue, and roll-back
In Drupal 6, there was an xmlrpc() function in the common.inc file that would load the xmlrpc.inc file and call an internal function that actually processed the XMLRPC request. In Drupal 7, this wrapper function has been removed, and you need to use drupal_function_exists('xmlrpc'); to make sure the include files is loaded. Example:
Drupal 6.x:
<?php
xmlrpc(...);
?>Drupal 7.x
<?php
drupal_function_exists('xmlrpc');
xmlrpc(...);
?>Foreign keys added to core database table schema
Issue: #111011: Add foreign keys to core
Foreign key sections were added to table schema descriptions for many core Drupal tables. For instance, the following was added to forum_schema() in forum.install to let Drupal know that the nid and vid fields in this table are related to the same fields in the node table:
<?php
'foreign keys' => array(
'nid' => array('node' => 'nid'),
'vid' => array('node' => 'vid'),
),
?>Removed several unnecessary arguments to various hook_user_$op hooks and removed hook_profile_alter
Issue: #491972: Fix horrifying mess of crap in user module hooks
Because $op was removed from hook_user, many hooks contained unnecessary arguments.
Changed hooks:
<?php
function hook_user_categories() {}
function hook_user_login(&$edit, $account) {}
function hook_user_logout($account) {}
function hook_user_view($account) {}
?>See also : http://api.drupal.org/api/search/7/hook_user
Additionally, hook_profile_alter() was doing exactly the same as hook_user_view() and has been removed. Use the latter hook with a higher weight if you want to remove something that has been added by another module.
Many paths to admin screens have changed
Paths to many administrative screens have changed between Drupal 6 and Drupal 7, as part of the information architecture and usability improvement effort. Here is a list (currently incomplete):
- admin/build/modules -> admin/config/modules
- admin/development/testing > admin/config/development/testing
- admin/settings/maintenance-mode > admin/config/development/maintenance
- admin/settings/performance > admin/config/development/performance
See [#549094] for the full list.
drupal_add_css() now supports external CSS files
Issue: #264876: Allow external CSS files through drupal_add_css
The function drupal_add_css() now supports adding external CSS files, if you pass in 'external' as the type argument. Example:
<?php
drupal_add_css('http://example.com/style.css', 'external');
?>New hook_comment_presave() for comments
Now modules may make changes to the comment before it is saved to the database. A new hook: hook_comment_presave() was added. The $comment passed as argument to the hook can be altered. Example:
<?php
function mymodule_comment_presave($comment) {
// Remove leading & trailing spaces from the comment subject.
$comment->subject = trim($comment->subject);
}
?>New tar archive library added
Drupal 7 includes a new third-party library, Archive_Tar from the PEAR project, which you can use to create and extract tar files in your modules. External resources on how to use this module:
Weighting of stylesheets
(issue) Added CSS now supports weight ordering through drupal_add_css(). The numeric "weight" property of the $options array will determine the order in which the stylesheets will appear on the page.
Available constants are:
- CSS_SYSTEM: Any system-layer CSS.
- CSS_DEFAULT: Any module-layer CSS.
- CSS_THEME: Any theme-layer CSS.
Drupal 6.x:
<?php
// Add a CSS file that will appear with the other module CSS files
drupal_add_css('mystylesheet.css', 'module');
// Add a stylesheet that will appear with the other theme layer CSS files
drupal_add_css('mystylesheet.css', 'theme');
?>Drupal 7.x:
<?php
// Add a CSS file that will appear with the other module CSS files (CSS_DEFAULT)
drupal_add_css('mystylesheet.css');
// Add a stylesheet that will appear with the other theme layer CSS files
drupal_add_css('mystylesheet.css', array('weight' => CSS_THEME));
// Add a CSS file that will appear before any other system-level CSS files
drupal_add_css('mystylesheet.css', array('weight' => CSS_SYSTEM - 1));
?>hook_access() removed in favor of hook_node_access()
The node-creating-module-specific hook_access() has been removed and replaced with a the more flexible hook_node_access(). In Drupal 6, the module that created a node type had absolute control over its access hook. For admin-created node types that is the node module with a fixed set of global permissions. In Drupal 7, any module may influence the access a user has to a node regardless of which module created it. By default node.module provides global permissions for any node type. That may be disabled per-node type by setting the variable node_permissions_$typename to 0. A contrib module will be provided that includes an administrative UI.
Additional modules may provide their own hook_node_access() implementations that will layer on top of the core functionality, allowing for more complex node access rules. See the links above for more details.
hook_filter() and hook_filter_tips() replaced by hook_filter_info()
hook_filter() has been removed and replaced with hook_filter_info(), to allow modules to declare the input filters they provide using an associative array (hook_menu-alike registry structure), consistently with others info hooks in core. Issue: #546336: hook_filter_info(): Remove $op from hook_filter()
hook_filter_info() is not required any more, since a 'tips callback' is provided in filters declaration, and used to generate filter tips. Issue: #548308: Remove hook_filter_tips()
Drupal 6.x:
<?php
/**
* Implement hook_filter().
*/
function filter_filter($op, $delta = 0, $format = -1, $text = '') {
switch ($op) {
case 'list':
case 'description':
}
}
/**
* Implement hook_filter_tips().
* Tips callback for php filter.
*/
function filter_filter_tips($delta, $format, $long = FALSE) {
}
?>Drupal 7.
<?php
function hook_filter_info() {
$filters = array();
$filters[0] = array(
'name' => t('Limit allowed HTML tags'),
'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
'process callback' => '_filter_html',
'settings callback' => '_filter_html_settings',
'tips callback' => '_filter_html_tips'
);
}
/**
* Filter callbacks.
*/
function _filter_html($text, $format) {
}
function _filter_html_settings($format) {
}
function _filter_html_tips($format, $long = FALSE) {
}
?>Convert class attributes to array in favor of string
(issue) Classes should always be an array. This is true anytime a class is used, even if only a single class is added.
In Drupal 6
<?php
$attributes['class'] = 'my-class';
// ...
$attributes['class'] .= ' my-second-class';
?>In Drupal 7
<?php
$attributes['class'] = array(
'my-class',
);
// ...
$attributes['class'][] = 'my-second-class';
// Single class should still be arrays.
$attributes2['class'] = array('single-class');
?>Schema API now supports date and time types natively
(issue) The Schema API now supports native SQL "Date" and "Time" field types, in addition to combined datetime fields. Note that these fields do not include timezone information.
Added API functions for creating, loading, updating, and deleting user roles and permissions
New API functions to manipulate user roles added:
| user_role_delete | Delete a user role from database. |
| user_role_load | Fetch a user role from database. |
| user_role_permissions | Determine the permissions for one or more roles. |
| user_role_save | Save a user role to the database. |
| user_role_set_permissions | Assign permissions to a user role. |
Contrib modules and install profiles are encouraged to use the User roles API to create/load/update/delete user roles and assign permissions to roles; instead of directly querying the {role} and {role_permission} tables.
The following hooks were added to inform other modules about updates to user roles in the system:
| hook_user_role_delete | Inform other modules that a user role has been deleted. |
| hook_user_role_insert | Inform other modules that a user role has been added. |
| hook_user_role_update | Inform other modules that a user role has been updated. |
Issue: #300993: User roles and permissions API
New hook: hook_file_url_alter()
(Issue) The hook hook_file_url_alter() has been added, which allows URLs to files to be altered. This makes it possible to serve files from a CDN for example. See the documentation for details.
jQuery Once method for applying JavaScript behaviors once
(issue) When applying certain behaviours to elements through JavaScript, we apply a class to the element to depict whether or not the effect has been applied. Using jQuery Once, the developer experience of applying these effects is improved. Note that there is also the removeOnce method that will only take effect on elements that have already applied the behaviors.
Drupal 6.x:
Drupal.behaviors.mybehavior = function (context) {
$('input.mybehavior:not(.mybehavior-processed)', context).addClass('mybehavior-processed').each(function () {
// Apply the MyBehaviour effect to the elements only once
});
};Drupal 7.x:
Drupal.behaviors.mybehavior = {
attach: function (context, settings) {
$('input.mybehavior', context).once('mybehavior', function () {
// Apply the MyBehaviour effect to the elements only once
});
}
};Database schema (un)installed automatically
(issue) A module no longer should explicitly install or uninstall its database schema in hook_install() or hook_uninstall().
Drupal 6.x:
function example_schema() {
$schema['example'] = array(
'fields' => array(
'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'primary key' => array('nid'),
);
return $schema;
}
function example_install() {
drupal_install_schema('example');
}
function example_uninstall() {
drupal_uninstall_schema('example');
}Drupal 7.x:
function example_schema() {
$schema['example'] = array(
'fields' => array(
'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'primary key' => array('nid'),
);
return $schema;
}The installation of database schema occurs when the module is first enabled. You may then use hook_install() to perform actions on your database tables. The uninstallation of database schema occurs after your implementation of hook_uninstall(), allowing your module to act on its data.
Drupal 6.x:
function example_uninstall() {
$result = db_query("SELECT * FROM {example}");
while ($data = db_fetch_object($result)) {
db_query("DELETE FROM {variable} WHERE name = '%s'", 'example_'. $data->nid);
}
drupal_uninstall_schema('example');
}Drupal 7.x:
function example_uninstall() {
$result = db_query("SELECT * FROM {example}");
while ($data = db_fetch_object($result)) {
db_query("DELETE FROM {variable} WHERE name = '%s'", 'example_', $data->nid);
}
}User 1 is now called site maintenance account
(issue) User 1 is now referred to as site maintenance account. Make sure you use this phrase when you mean this particular account. Avoid administrator or similar phrases, because that should only be used for the new administrator role.
CRUD hooks for menu links: hook_menu_link_insert(), hook_menu_link_update(), hook_menu_link_delete()
(issue) The menu system now fires hooks to inform modules that changes have been made to the menu system. The hooks are standard Drupal, each of which passes the $links associative array generated by menu_link_save().
Default text formats have been revamped
(issue) The default text format provided by the filter module previously served two purposes at once:
- It was the text format that all users automatically had permission to use, and
- It was the text format that was selected by default when, for example, a user created new content.
In Drupal 7, these concepts have been split into two: the fallback text format and the default text format.
As a consequence, the following from Drupal 6.x are no longer available to use:
- The FILTER_FORMAT_DEFAULT constant
- The 'filter_default_format' variable, typically used via
variable_get('filter_default_format', 1) - The filter_resolve_format() function
In their place, you should use one of two new functions in Drupal 7.x:
- filter_fallback_format(): This returns the ID of a safe format (for example, plain text) that can be used as a fallback when filtering text (for example, if content loses the text format that was previously assigned to it).
- filter_default_format(): This returns the ID of a format that should be used as the default choice when presenting a list of possible text formats to a user who has not selected one. It should not be used when filtering text. Example (from the comment module):
<?php
$form['comment'] = array(
'#type' => 'textarea',
'#title' => t('Comment'),
...
'#text_format' => isset($comment->format) ? $comment->format : filter_default_format(),
...
);
?>
In addition, you may have previously stored FILTER_FORMAT_DEFAULT (i.e., "0") in your module's database tables to indicate that a particular piece of content should be filtered using the default format. If so, you will need to write an update function to convert these instances to use the actual format. For example:
<?php
$default_format = variable_get('filter_default_format', 1);
db_update('comment')
->fields(array('format' => $default_format))
->condition('format', 0)
->execute();
?>Note that although the 'filter_default_format' variable is deprecated, Drupal 7 preserves it in the database so that it can be used for this purpose only.
Text formats access is now controlled by permissions, and filter_access() parameters have changed
(issue) Text formats no longer have an associated "roles" element maintained as part of the text format itself; rather, access to each text format is now controlled by a permission that uses the normal Drupal user access system.
Your code should continue to use the filter_access() function to check all access to text formats, but be aware that this function now takes a full format object rather than a format ID as input.
Drupal 6.x:
<?php
if (filter_access($format_id)) {
// Do stuff.
}
?>Drupal 7.x:
<?php
$format = filter_format_load($format_id);
if (filter_access($format)) {
// Do stuff.
}
?>In addition, any code which relied on the existence of a $format->roles property or the existence of a "roles" column in the {filter_format} database table must be changed to use filter_access() instead.
Also note that there are new API functions filter_get_roles_by_format() and filter_get_formats_by_role() which can be used to determine the text formats that each user role has specific permission to access.
The parameters to filter_formats() have changed
(issue) By default (when called with no parameters), this function now returns a list of all text formats on the site, rather than only those formats that the current user has permission to use. You can use the new $account parameter if you need a list of formats for a particular user.
Drupal 6.x:
<?php
// Get a list of formats that the current user has access to.
$formats = filter_formats();
?>Drupal 7.x:
<?php
// Get a list of formats that the current user has access to.
global $user;
$formats = filter_formats($user);
?>Rename drupal_to_js() and drupal_json() to drupal_json_encode() and drupal_json_output()
(issue) Rename drupal_to_js() and drupal_json() to drupal_json_encode() and drupal_json_output()
For example:
Drupal 6.x:
<?php
echo drupal_to_js($var);
?>Drupal 7.x:
<?php
echo drupal_json_encode($var);
?>Drupal 6.x:
<?php
$matches = array(
'result1' => 'result1',
);
drupal_json($matches);
?>Drupal 7.x:
<?php
$matches = array(
'result1' => 'result1',
);
drupal_json_output($matches);
?>theme_links() has a new parameter $heading for accessibility
Issue: #364219: Navigation menus should be preceded by headings of the appropriate level (usually <h2>).
The theme_links() function, which is normally called via theme('links', ...), has a new third parameter $heading that can be used to provide an invisible heading before a list of links, for accessibility. Headings such as these can be used by screen reader and keyboard-only users to navigate to or skip the list of links. Also, headings are required by the standard Web Content Accessibility Guidelines (WCAG) to be present at the beginning of each content section, including navigation sections (see http://www.w3.org/TR/2008/WD-WCAG20-TECHS-20080430/H69.html).
Example - Drupal 6:
theme('links', $main_menu, array('id' => 'main-menu', 'class' => array('links', 'clearfix')));Drupal 7 (simple):
theme('links', $main_menu, array('id' => 'main-menu', 'class' => array('links', 'clearfix')), array('text' => t('Main menu'), 'level' => 'h2', 'class' => array('element-invisible')));http://drupal.org/update/theme/6/7#theme-links-param has more detail about this change, including references.
API for modules providing search has changed
Issue: #394182: DBTNG search.module
The API for search modules (i.e. modules that provide search tabs to the core search.module) has been overhauled in Drupal 7. Key changes:
- In Drupal 6, the operations in hook_search() let a module set up a search tab, add to the search settings admin page, perform a search, respond to search index resets, and return information about indexing status. In Drupal 7, this one hook has been separated out into hook_search_info(), hook_search_admin(), hook_search_execute(), hook_search_reset(), and hook_search_status(). There is also a new hook, hook_search_access(), which allows setting permissions for your particular search module's search tab.
- In Drupal 6, modules were encouraged to use the do_search() function to perform the search. In Drupal 7, this function does not exist. Instead, modules should perform the serach query directly, using the "SearchQuery" extension.
Example - Drupal 6 -- see node_search($op = 'search'):
// Calculate join and special conditions first...
$results = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join1, $conditions1 . (empty($where1) ? '' : ' AND '. $where1), $arguments1, $select2, $join2, $arguments2);
// further processingDrupal 7 -- see node_search_execute():
$query = db_select('search_index', 'i')->extend('SearchQuery')->extend('PagerDefault');
$query->join('node', 'n', 'n.nid = i.sid');
$query
->condition('n.status', 1)
->addTag('node_access')
->searchExpression($keys, 'node');
// add additional modifiers here...
$found = $query
->limit(10)
->execute();
// further processing
Trigger and Actions API overhaul
Issue: #525540: trigger.module and includes/actions.inc need overhaul
The Trigger and Actions APIs were changed extensively between Drupal 6 and Drupal 7, to simplify the API and make it more consistent. The following changes were made:
- Database table {actions} changed - 'description' field is now called 'label'.
- Database table {trigger_assignments} changed - 'op' field was removed, and the 'hook' field now contains the full function name. For instance, in Drupal 6 you might have had hook/op of node/insert, and in Drupal 7, the hook field would be node_insert.
- The arrays returned by hook_action_info(), hook_action_info_alter(), actions_list(), actions_get_all_actions(), actions_actions_map() were changed:
- 'description' element becomes 'label'
- 'hooks' element becomes 'triggers', and is now a flat array of trigger functions rather than a nested array of type/operation.
Example from comment_action_info() -- Drupal 6:
return array(
'comment_unpublish_action' => array(
'description' => t('Unpublish comment'),
'type' => 'comment',
'configurable' => FALSE,
'hooks' => array(
'comment' => array('insert', 'update'),
)
),
);
Drupal 7:
return array(
'comment_unpublish_action' => array(
'type' => 'comment',
'label' => t('Unpublish comment'),
'configurable' => FALSE,
'triggers' => array('comment_insert', 'comment_update'),
),
); - hook_hook_info() is now called hook_trigger_info(), and its return value has been changed and simplified.
Example from the node module implementation - Drupal 6:
return array(
'node' => array(
'nodeapi' => array(
'presave' => array(
'runs when' => t('When either saving a new post or updating an existing post'),
),
'insert' => array(
'runs when' => t('After saving a new post'),
),
'update' => array(
'runs when' => t('After saving an updated post'),
),
'delete' => array(
'runs when' => t('After deleting a post')
),
'view' => array(
'runs when' => t('When content is viewed by an authenticated user')
),
),
),
);Drupal 7:
return array(
'node' => array(
'node_presave' => array(
'label' => t('When either saving new content or updating existing content'),
),
'node_insert' => array(
'label' => t('After saving new content'),
),
'node_update' => array(
'label' => t('After saving updated content'),
),
'node_delete' => array(
'label' => t('After deleting content'),
),
'node_view' => array(
'label' => t('When content is viewed by an authenticated user'),
),
),
); - A new API function has been added, trigger_get_assigned_actions(), which allows your module to retrieve a list of actions assigned to one of its hooks. Drupal 6 had no approved method for finding this out.
- Tests were added to test triggering of actions in node, comment, taxonomy, and user modules.
- Numerous internal functions used to generate administrative pages have changed. In the unlikely event that your module is calling these functions directly, you will need to look at these functions' new definitions to see what has changed.
All taxonomy / node related code has been removed or refactored
Issue: #412518: Convert taxonomy_node_* related code to use field API + upgrade path
You should now use field API functions or views in nearly all cases where taxonomy / node relations were used in taxonomy module, since the only way to attach terms to nodes is via field API. Several functions were completely removed as part of this conversion, so please refer to taxonomy module co or field API documentation for fulldetails.
Added hook_entity_load()
Issue #605442: Add a generic hook_entity_load(). This allows you to act on any entity (nodes, comments, users) loaded via the entity API using a single hook. See the API documentation for details.
theme() only takes two arguments.
The theme() function takes two arguments, where the first is the same as it was but the second one is an associative array.
So if your hook_theme (which did not change, we just use it for demonstration) contained
<?php
'user_list' => array(
'arguments' => array('users' => NULL, 'title' => NULL),
),
?>then previously you had
<?php
theme('user_list', $users, $title);
?>now you need to do
<?php
theme('user_list', array('users' => $users, 'title' => $title));
?>hook_theme() requires that each theme function registers how it integrates with drupal_render().
In Drupal 6, the #theme property of an element could only be set to a theme function that was written to work with a renderable element as its first argument. In Drupal 7, the #theme property can be set to any theme function, but because of this, drupal_render() and theme() need to know whether the theme function expects the element itself, or an arbitrary set of variables to be gathered from the element's properties. Therefore, the theme hook needs to be registered with either a 'variables' key or a 'render element' key. This is best illustrated by showing the following change to node_theme():
Drupal 6:
<?php
function node_theme() {
return array(
...
'node_list' => array(
'arguments' => array('items' => NULL, 'title' => NULL),
),
'node_search_admin' => array(
'arguments' => array('form' => NULL),
),
...
);
}
?>Drupal 7:
<?php
function node_theme() {
return array(
...
'node_list' => array(
'variables' => array('items' => NULL, 'title' => NULL),
),
'node_search_admin' => array(
'render element' => 'form',
),
...
);
}
?>drupal_alter() now takes at most 3 parameters by reference.
Issue #593522: Upgrade drupal_alter(). drupal_alter() Now accepts an alteration type (such as form, menu, etc.), a $data object to alter, and up to two optional context variables. The $data object and both context variables are passed by reference. This differs from Drupal 6 where only the $data object was passed by reference and an unlimited number of parameters were supported. If a particular alter needs more than two context properties, they can be placed into an array and passed together in the second context property. See the API documentation for further examples.
The $ret parameter has been removed from all Schema operations.
Issue #394268: DIE update_sql() DIE! and #570900: Destroy remnants of update_sql(). All schema modification functions no longer take a $ret parameter. The $ret parameter was used exclusively in the update system, which no longer needs them.
Drupal 6:
$ret = array();
db_table_create($ret, 'mytable', $table_definition);Drupal 7:
db_table_create('mytable', $table_definition);Update hooks now return strings or throw exceptions, and update_sql() is no more.
Issue #394268: DIE update_sql() DIE! and #570900: Destroy remnants of update_sql() hook_update_N hooks no longer return a $ret array. Instead, update hooks should return nothing if they have no "success" message to display or return a translated string to be displayed to the user indicating that the update ran successfully.
If an error occurred and the update process should not continue, the update hook should throw a DrupalUpdateException exception, passing a translated string as the error message to the constructor. That string will be shown to the user and the update process will terminate.
If an update hook needs to run batched, it should accept by reference a &$sandbox parameter and set $sandbox['#finished'] to a decimal value less than 1 to indicate how complete the process is.
Additionally, the update_sql() function is no longer necessary. Instead, simply use the database API as normal for any schema or data changes.
<?php
/**
* Descriptive string here to show in the UI.
*/
function system_update_7100(&$sandbox) {
if (empty($sandbox['step'])) {
$sandbox['step'] = 1;
}
if ($sandbox['step'] == 1) {
// Indicates that this function should be called a second time.
$sandbox['#finished'] = 0.5;
// Do other expensive work...
// Something went wrong somewhere...
if ($something_bad) {
throw new DrupalUpdateException(t('Something bad happened.'));
}
}
else {
// Indicate that this function should not be called again, it's done.
unset($sandbox['#finished']);
}
$sandbox['step']++;
return t('All dohicky tables updated with a new index.');
}
?>The signature of the callback from drupal_get_form() changed to add $form
The signature of the callback called by drupal_get_form() was changed to add $form.
Drupal 6:
$form = drupal_get_form('my_form', ...);
...
function my_form(&$form_state, ...) {
}Drupal 7:
$form = drupal_get_form('my_form', ...);
...
function my_form($form, &$form_state, ...) {
}Replaced taxonomy_term_path(), hook_term_path(), language_url_rewrite(), and custom_url_alter_outbound() with hook_url_outbound_alter()
Issue #320331: Turn custom_url_rewrite_inbound and custom_url_rewrite_outbound into hooks If you had the special custom_url_rewrite_inbound() in your site's settings.php or implemented by a module, it has been replaced with the proper hook_url_outbound_alter(). This allows any module to be able to alter the outbound path of url() and l() as they see fit. Please note that if the original path being altered has an URL aliases, the alias will override any alterations made with hook_url_outbound_alter().
Drupal 6:
function custom_url_rewrite_outbound(&$path, &$options, $original_path) {
global $user;
// Change all 'node' to 'article'.
if (preg_match('|^node(/.*)|', $path, $matches)) {
$path = 'article' . $matches[1];
}
// Create a path called 'e' which lands the user on her profile edit page.
if ($path == 'user/' . $user->uid . '/edit') {
$path = 'e';
}
}Drupal 7:
function mymodule_url_outbound_alter(&$path, &$options, $original_path) {
global $user;
// Change all 'node' to 'article'.
if (preg_match('|^node(/.*)|', $path, $matches)) {
$path = 'article' . $matches[1];
}
// Create a path called 'e' which lands the user on her profile edit page.
if ($path == 'user/' . $user->uid . '/edit') {
$path = 'e';
}
}For taxonomy links and modules: taxonomy_term_path() and hook_term_path() have been replaced by hook_url_alter_outbound():
Drupal 6:
function forum_term_path($term) {
return 'forum/' . $term->tid;
}
...
$term = taxonomy_get_term(1);
$link = return l($term->name, taxonomy_term_path($term));Drupal 7:
function forum_url_outbound_alter(&$path, &$options, $original_path) {
if (preg_match('!^taxonomy/term/(\d+)!', $path, $matches)) {
$term = taxonomy_term_load($matches[1]);
if ($term && $term->vocabulary_machine_name == 'forums') {
$path = 'forum/' . $matches[1];
}
}
}
...
$term = taxonomy_term_load(1);
$link = return l($term->name, 'taxonomy/term/' . $term->tid);Also note that language_url_rewrite() has become locale_url_outbound_alter(). If you have an alternate locale module that needs to alter outgoing links, make sure you implement this hook.
You can also use the URL alter module to retain your custom_url_rewrite_outbound() function.
Replaced custom_url_rewrite_inbound() with hook_url_inbound_alter()
Issue #320331: Turn custom_url_rewrite_inbound and custom_url_rewrite_outbound into hooks If you had the special custom_url_rewrite_inbound() in your site's settings.php or implemented by a module, it has been replaced with the proper hook_url_inbound_alter. This allows any module to be able to alter the inbound path of drupal_lookup_path() as they see fit.
Please note that this hook runs in opposite order of hook_url_outbound_alter() so that modules that alter the same path can undo each other's alterations correctly.
Drupal 6:
function custom_url_rewrite_inbound(&$result, $path, $path_language) {
global $user;
// Change all article/x requests to node/x
if (preg_match('|^article(/.*)|', $path, $matches)) {
$result = 'node' . $matches[1];
}
// Redirect a path called 'e' to the user's profile edit page.
if ($path == 'e') {
$result = 'user/' . $user->uid . '/edit';
}
}Drupal 7:
function mymodule_url_inbound_alter(&$path, $original_path, $path_language) {
global $user;
// Change all article/x requests to node/x
if (preg_match('|^article(/.*)|', $path, $matches)) {
$path = 'node' . $matches[1];
}
// Redirect a path called 'e' to the user's profile edit page.
if ($path == 'e') {
$path = 'user/' . $user->uid . '/edit';
}
}You can also use the URL alter module to retain your custom_url_rewrite_inbound() function.
hook_load() signature and return value change
Issue #460320: Standarized, pluggable entity loading (nodes, users, taxonomy, files, comments)
Drupal 6:
hook_load() took a single node as argument, and returned an object with node properties.
Drupal 7:
hook_load() takes an array of nodes as its argument and returns nothing. The hook may make changes directly to the $nodes passed in.
Renamed menu_path_is_external() to url_is_external()
(issue) The Drupal 6 function menu_path_is_external() in includes/menu.inc has been renamed to url_is_external() and moved into includes/common.inc. This function doesn't depend on the menu system at all, it's just a generic helper function to test if a given URL is external or not. It is used by parts of the form API, and to allow this code to work at a low bootstrap level without needing to pull in all of menu.inc, it now lives in includes/common.inc. The new name also makes the function more self-documenting.

Coding standards changes
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:
<?phpif (FALSE) {
// Something
}
else if ($foo) {
// Something else
}
?>
D7:
<?phpif (FALSE) {
// Something
}
elseif ($foo) {
// Something else
}
?>
A few more things that we've
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.
<?phpfunction myfunction(array $arg1 = array(), stdClass $arg2 = NULL, MyClassName $arg3) { ... }
?>
Regarding references
We don't seem to be keeping the '&', check http://drupal.org/node/457532
-- Amr Mostafa
I only removed it from
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]()
Remove $op from hook_nodeapi and hook_user mentions the
hook_nodeapi($op) -> hook_nodeapi_[op]()change, but we later didhook_nodeapi_[op]() -> hook_node_[op](), which I don't see mentioned anywhere.module_list() param change missing
The parameters to module_list() have changed in D7 but it's missing from the above list.
More missing updates
And shouldn't the section for http://drupal.org/node/224333#drupal_behaviors actually refer to
$(this).ajax()rather than$(this).ajaxSubmit()in the example?Also some functions have more than one section and may be better off merged to avoid confusion, e.g. file_scan_directory and hook_nodeapi
Shouldn't http://drupal.org/node/224333#process_functions reference
#processrather than#form?system_settings_form() no longer includes a reset button
This can change any code depending on these two buttons being present. This can happen when the function is used outside the traditional settings form use case, which is often advocated, but also if a D6 module already has a simpletest checking the settings form layout.
box => custom block
Block's 'boxes' are now 'custom blocks'. So table is now block_custom. Functions are like block_custom and vars too. Boxes are now a description of a block.
http://drupal.org/node/561970
http://www.davyvandenbremt.be
http://www.drupalcoder.com
http://twitter.com/davyvandenbremt
#show_blocks is gone
#show_blocks is gone http://drupal.org/node/423992
theme('maintenance_page' also has no show_blocks paramter anymore
http://www.davyvandenbremt.be
http://www.drupalcoder.com
http://twitter.com/davyvandenbremt
This page needs a mention of
This page needs a mention of the changes to admin menu paths, and perhaps a list of the sections below admin/ that are used by core.
Agree
I am having a lot of trouble figuring out what has gone where. Plus, the admin menu is going to get awfully big with everything that has "admin/settings/xxxx" which seem to be going to the admin menu bar now. Where do I put my module's settings pages???
NancyDru
system_settings_form() has an
system_settings_form() has an extra parameter; without it, default values are filled in for you automatically from system variables: http://api.drupal.org/api/function/system_settings_form/7
system_settings_form($form, $automatic_defaults = TRUE)
no node_type_clear()
node_type_clear() was removed from node.module on September 17th, 2009 and has been replaced with drupal_static_reset('_node_types_build');
file_create_path has gone?
There's no file_create_path() any more
What has replaced it?
Furthermore, this makes the examples for file_scan_directory() above wrong.
Errors in code examples: hook_filter() and
hook_filter() and hook_filter_tips() replaced by hook_filter_info()
<?phpfunction hook_filter_info() {
$filters = array();
$filters[0] = array(
'name' => t('Limit allowed HTML tags'),
'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
'process callback' => '_filter_html',
'settings callback' => '_filter_html_settings',
'tips callback' => '_filter_html_tips'
);
}
?>
should be:
<?phpfunction hook_filter_info() {
$filters = array();
$filters[0] = array(
'title' => t('Limit allowed HTML tags'),
'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
'process callback' => '_filter_html',
'settings callback' => '_filter_html_settings',
'tips callback' => '_filter_html_tips'
);
return $filters;
}
?>
RussianWebStudio: improving the web
Don't put changes you find here
If you find a change that is not yet documented, please post it to the Doc Team at http://drupal.org/project/issues/documentation rather than here. They can then get the change made by the appropriate core developer.
NancyDru