Index: includes/module.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/module.inc,v retrieving revision 1.207 diff -u -p -r1.207 module.inc --- includes/module.inc 24 Nov 2010 16:47:44 -0000 1.207 +++ includes/module.inc 24 Nov 2010 18:55:22 -0000 @@ -588,11 +588,24 @@ function module_disable($module_list, $d * The name of the hook (e.g. "help" or "menu"). * * @return - * TRUE if the module is both installed and enabled, and the hook is - * implemented in that module. + * The hook implementation's function name, if the module is enabled and the + * hook exists, or FALSE. */ function module_hook($module, $hook) { - return function_exists($module . '_' . $hook); + $function = $module . '_' . $hook; + if (function_exists($function)) { + return $function; + } + // If the hook implementation does not exist, check whether it may live in an + // optional include file registered via hook_hook_info(). + $hook_info = module_hook_info(); + if (isset($hook_info[$hook]['group'])) { + module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); + if (function_exists($function)) { + return $function; + } + } + return FALSE; } /** @@ -660,7 +673,9 @@ function module_implements($hook, $sort $list = module_list(FALSE, FALSE, $sort); foreach ($list as $module) { $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); - if (module_hook($module, $hook)) { + // Since module_hook() may needlessly try to load the include file again, + // function_exists() is used directly here. + if (function_exists($module . '_' . $hook)) { $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; } } @@ -678,9 +693,11 @@ function module_implements($hook, $sort module_load_include('inc', $module, "$module.$group"); } // It is possible that a module removed a hook implementation without the - // implementations cache being rebuilt yet, so we check module_hook() on - // each request to avoid undefined function errors. - if (!module_hook($module, $hook)) { + // implementations cache being rebuilt yet, so we check whether the + // function exists on each request to avoid undefined function errors. + // Since module_hook() may needlessly try to load the include file again, + // function_exists() is used directly here. + if (!function_exists($module . '_' . $hook)) { // Clear out the stale implementation from the cache and force a cache // refresh to forget about no longer existing hook implementations. unset($implementations[$hook][$module]); @@ -696,9 +713,17 @@ function module_implements($hook, $sort * Retrieve a list of what hooks are explicitly declared. */ function module_hook_info() { - $hook_info = &drupal_static(__FUNCTION__, array()); + // This function is indirectly invoked from bootstrap_invoke_all(), in which + // case not all subsystems and modules are loaded yet. Therefore, it does not + // make sense to support hook groups and lazy-loaded include files prior to + // full bootstrap. + if (drupal_bootstrap(NULL, FALSE) != DRUPAL_BOOTSTRAP_FULL) { + return array(); + } + $hook_info = &drupal_static(__FUNCTION__); - if (empty($hook_info)) { + if (!isset($hook_info)) { + $hook_info = array(); $cache = cache_get('hook_info', 'cache_bootstrap'); if ($cache === FALSE) { // Rebuild the cache and save it. @@ -764,10 +789,11 @@ function module_invoke() { $module = $args[0]; $hook = $args[1]; unset($args[0], $args[1]); - if (module_hook($module, $hook)) { - return call_user_func_array($module . '_' . $hook, $args); + if ($function = module_hook($module, $hook)) { + return call_user_func_array($function, $args); } } + /** * Invoke a hook in all enabled modules that implement it. * Index: modules/field/field.attach.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v retrieving revision 1.97 diff -u -p -r1.97 field.attach.inc --- modules/field/field.attach.inc 20 Nov 2010 19:57:01 -0000 1.97 +++ modules/field/field.attach.inc 24 Nov 2010 19:03:42 -0000 @@ -188,8 +188,13 @@ function _field_invoke($op, $entity_type foreach ($instances as $instance) { $field_name = $instance['field_name']; $field = field_info_field($field_name); - $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; - if (function_exists($function)) { + if ($options['default']) { + $function = 'field_default_' . $op; + } + else { + $function = module_hook($field['module'], 'field_' . $op); + } + if ($function) { // Determine the list of languages to iterate on. $available_languages = field_available_languages($entity_type, $field); $languages = _field_language_suggestion($available_languages, $options['language'], $field_name); @@ -298,8 +303,13 @@ function _field_invoke_multiple($op, $en $field_id = $instance['field_id']; $field_name = $instance['field_name']; $field = $field_info[$field_id]; - $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; - if (function_exists($function)) { + if ($options['default']) { + $function = 'field_default_' . $op; + } + else { + $function = module_hook($field['module'], 'field_' . $op); + } + if ($function) { // Add the field to the list of fields to invoke the hook on. if (!isset($fields[$field_id])) { $fields[$field_id] = $field; @@ -325,7 +335,12 @@ function _field_invoke_multiple($op, $en // For each field, invoke the field hook and collect results. foreach ($fields as $field_id => $field) { $field_name = $field['field_name']; - $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; + if ($options['default']) { + $function = 'field_default_' . $op; + } + else { + $function = module_hook($field['module'], 'field_' . $op); + } // Iterate over all the field translations. foreach ($grouped_items[$field_id] as $langcode => $items) { $results = $function($entity_type, $grouped_entities[$field_id], $field, $grouped_instances[$field_id], $langcode, $grouped_items[$field_id][$langcode], $a, $b); Index: modules/field/field.default.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v retrieving revision 1.41 diff -u -p -r1.41 field.default.inc --- modules/field/field.default.inc 21 Nov 2010 19:09:18 -0000 1.41 +++ modules/field/field.default.inc 24 Nov 2010 19:04:41 -0000 @@ -157,8 +157,7 @@ function field_default_prepare_view($ent foreach ($modules as $module) { // Invoke hook_field_formatter_prepare_view(). - $function = $module . '_field_formatter_prepare_view'; - if (function_exists($function)) { + if ($function = module_hook($module, 'field_formatter_prepare_view')) { $function($entity_type, $grouped_entities[$module], $field, $grouped_instances[$module], $langcode, $grouped_items[$module], $grouped_displays[$module]); } } @@ -203,8 +202,7 @@ function field_default_view($entity_type if ($display['type'] !== 'hidden') { // Calling the formatter function through module_invoke() can have a // performance impact on pages with many fields and values. - $function = $display['module'] . '_field_formatter_view'; - if (function_exists($function)) { + if ($function = module_hook($display['module'], 'field_formatter_view')) { $elements = $function($entity_type, $entity, $field, $instance, $langcode, $items, $display); if ($elements) { Index: modules/field/field.form.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.form.inc,v retrieving revision 1.54 diff -u -p -r1.54 field.form.inc --- modules/field/field.form.inc 20 Nov 2010 19:57:01 -0000 1.54 +++ modules/field/field.form.inc 24 Nov 2010 19:05:39 -0000 @@ -61,8 +61,7 @@ function field_default_form($entity_type // make it the $delta value. else { $delta = isset($get_delta) ? $get_delta : 0; - $function = $instance['widget']['module'] . '_field_widget_form'; - if (function_exists($function)) { + if ($function = module_hook($instance['widget']['module'], 'field_widget_form')) { $element = array( '#entity_type' => $instance['entity_type'], '#bundle' => $instance['bundle'], @@ -160,8 +159,7 @@ function field_multiple_value_form($fiel $field_elements = array(); - $function = $instance['widget']['module'] . '_field_widget_form'; - if (function_exists($function)) { + if ($function = module_hook($instance['widget']['module'], 'field_widget_form')) { for ($delta = 0; $delta <= $max; $delta++) { $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; $element = array( @@ -336,8 +334,7 @@ function field_default_form_errors($enti $field_state = field_form_get_state($form['#parents'], $field['field_name'], $langcode, $form_state); if (!empty($field_state['errors'])) { - $function = $instance['widget']['module'] . '_field_widget_error'; - $function_exists = function_exists($function); + $function = module_hook($instance['widget']['module'], 'field_widget_error'); // Locate the correct element in the the form. $element = drupal_array_get_nested_value($form_state['complete form'], $field_state['array_parents']); @@ -348,7 +345,7 @@ function field_default_form_errors($enti // For a multiple-value widget, all errors are passed to the main widget. $error_element = $multiple_widget ? $element : $element[$delta]; foreach ($delta_errors as $error) { - if ($function_exists) { + if ($function) { $function($error_element, $error, $form, $form_state); } else { Index: modules/field/field.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.module,v retrieving revision 1.90 diff -u -p -r1.90 field.module --- modules/field/field.module 21 Nov 2010 19:09:18 -0000 1.90 +++ modules/field/field.module 24 Nov 2010 19:06:42 -0000 @@ -110,6 +110,102 @@ define('FIELD_LOAD_REVISION', 'FIELD_LOA class FieldUpdateForbiddenException extends FieldException {} /** + * Implements hook_hook_info(). + */ +function field_hook_info() { + $field_hooks = array( + 'field_access', + 'field_attach_create_bundle', + 'field_attach_delete', + 'field_attach_delete_bundle', + 'field_attach_delete_revision', + 'field_attach_form', + 'field_attach_insert', + 'field_attach_load', + 'field_attach_prepare_translation_alter', + 'field_attach_preprocess_alter', + 'field_attach_presave', + 'field_attach_purge', + 'field_attach_rename_bundle', + 'field_attach_submit', + 'field_attach_update', + 'field_attach_validate', + 'field_attach_view_alter', + 'field_available_languages_alter', + 'field_create_field', + 'field_create_instance', + 'field_delete', + 'field_delete_field', + 'field_delete_instance', + 'field_delete_revision', + 'field_display_alter', + // @todo http://drupal.org/node/968264 + 'field_display_ENTITY_TYPE_alter', + 'field_extra_fields', + 'field_extra_fields_alter', + 'field_extra_fields_display_alter', + 'field_formatter_info', + 'field_formatter_info_alter', + 'field_formatter_prepare_view', + 'field_formatter_settings_form', + 'field_formatter_settings_summary', + 'field_formatter_view', + 'field_info', + 'field_info_alter', + 'field_info_max_weight', + 'field_insert', + 'field_instance_settings_form', + 'field_is_empty', + 'field_language_alter', + 'field_load', + 'field_prepare_translation', + 'field_prepare_view', + 'field_presave', + 'field_purge_field', + 'field_purge_field_instance', + 'field_read_field', + 'field_read_instance', + 'field_settings_form', + 'field_storage_create_field', + 'field_storage_delete', + 'field_storage_delete_field', + 'field_storage_delete_instance', + 'field_storage_delete_revision', + 'field_storage_details', + 'field_storage_details_alter', + 'field_storage_info', + 'field_storage_info_alter', + 'field_storage_load', + 'field_storage_pre_insert', + 'field_storage_pre_load', + 'field_storage_pre_update', + 'field_storage_purge', + 'field_storage_purge_field', + 'field_storage_purge_field_instance', + 'field_storage_query', + 'field_storage_update_field', + 'field_storage_write', + 'field_update', + 'field_update_field', + 'field_update_forbid', + 'field_update_instance', + 'field_validate', + 'field_widget_error', + 'field_widget_form', + 'field_widget_info', + 'field_widget_info_alter', + 'field_widget_properties_alter', + // @todo http://drupal.org/node/968264 + 'field_widget_properties_ENTITY_TYPE_alter', + 'field_widget_settings_form', + ); + $hooks = array_fill_keys($field_hooks, array( + 'group' => 'field', + )); + return $hooks; +} + +/** * Implements hook_flush_caches(). */ function field_flush_caches() { @@ -286,8 +382,7 @@ function field_get_default_value($entity * the array keys to ensure sequential deltas. */ function _field_filter_items($field, $items) { - $function = $field['module'] . '_field_is_empty'; - function_exists($function); + $function = module_hook($field['module'], 'field_is_empty'); foreach ((array) $items as $delta => $item) { // Explicitly break if the function is undefined. if ($function($item, $field)) { Index: modules/file/file.field.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/file/file.field.inc,v retrieving revision 1.41 diff -u -p -r1.41 file.field.inc --- modules/file/file.field.inc 22 Nov 2010 04:42:09 -0000 1.41 +++ modules/file/file.field.inc 24 Nov 2010 19:00:31 -0000 @@ -42,15 +42,18 @@ function file_field_settings_form($field $form['display_field'] = array( '#type' => 'checkbox', - '#title' => t('Enable Display field'), + '#title' => t('Allow users to choose if files should be shown when viewing content'), '#default_value' => $settings['display_field'], - '#description' => t('The display option allows users to choose if a file should be shown when viewing the content.'), ); $form['display_default'] = array( '#type' => 'checkbox', - '#title' => t('Files displayed by default'), + '#title' => t('Display files by default'), '#default_value' => $settings['display_default'], - '#description' => t('This setting only has an effect if the display option is enabled.'), + '#states' => array( + 'visible' => array( + ':input[name*="display_field"]' => array('checked' => TRUE), + ), + ), ); $scheme_options = array(); @@ -486,6 +489,12 @@ function file_field_widget_form(&$form, // Allows this field to return an array instead of a single value. '#extended' => TRUE, ); + // file_field_widget_process() is defined in this file, so ensure that it is + // loaded when the form is processed. + $form_state['build_info']['files']['file'] = array( + 'module' => 'file', + 'name' => 'file.field', + ); if ($field['cardinality'] == 1) { // If there's only one field, return it as delta 0. Index: modules/file/file.module =================================================================== RCS file: /cvs/drupal/drupal/modules/file/file.module,v retrieving revision 1.47 diff -u -p -r1.47 file.module --- modules/file/file.module 23 Nov 2010 05:51:16 -0000 1.47 +++ modules/file/file.module 24 Nov 2010 19:00:31 -0000 @@ -6,9 +6,6 @@ * Defines a "managed_file" Form API field and a "file" field for Field module. */ -// Load all Field module hooks for File. -require_once DRUPAL_ROOT . '/modules/file/file.field.inc'; - /** * Implements hook_help(). */ Index: modules/image/image.field.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/image/image.field.inc,v retrieving revision 1.34 diff -u -p -r1.34 image.field.inc --- modules/image/image.field.inc 31 Oct 2010 12:12:00 -0000 1.34 +++ modules/image/image.field.inc 24 Nov 2010 19:10:58 -0000 @@ -6,6 +6,10 @@ * Implement an image field, based on the file module's file field. */ +// Image field depends on file field, so all field hook implementations of File +// module need to be loaded. +require_once DRUPAL_ROOT . '/modules/file/file.field.inc'; + /** * Implements hook_field_info(). */ @@ -313,6 +317,12 @@ function image_field_widget_form(&$form, // Add all extra functionality provided by the image widget. $elements[$delta]['#process'][] = 'image_field_widget_process'; } + // image_field_widget_process() is defined in this file, so ensure that it is + // loaded when the form is processed. + $form_state['build_info']['files']['image'] = array( + 'module' => 'image', + 'name' => 'image.field', + ); if ($field['cardinality'] == 1) { // If there's only one field, return it as delta 0. Index: modules/image/image.module =================================================================== RCS file: /cvs/drupal/drupal/modules/image/image.module,v retrieving revision 1.54 diff -u -p -r1.54 image.module --- modules/image/image.module 18 Nov 2010 05:36:27 -0000 1.54 +++ modules/image/image.module 24 Nov 2010 19:09:46 -0000 @@ -31,9 +31,6 @@ define('IMAGE_STORAGE_EDITABLE', IMAGE_S */ define('IMAGE_STORAGE_MODULE', IMAGE_STORAGE_OVERRIDE | IMAGE_STORAGE_DEFAULT); -// Load all Field module hooks for Image. -require_once DRUPAL_ROOT . '/modules/image/image.field.inc'; - /** * Implement of hook_help(). */