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 26 Nov 2010 17:51:41 -0000 @@ -592,7 +592,20 @@ function module_disable($module_list, $d * implemented in that module. */ function module_hook($module, $hook) { - return function_exists($module . '_' . $hook); + $function = $module . '_' . $hook; + if (function_exists($function)) { + return TRUE; + } + // 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 TRUE; + } + } + 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 common.inc, subsystems, and modules are not loaded yet, so it does not + // make sense to support hook groups resp. 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. @@ -768,6 +793,7 @@ function module_invoke() { return call_user_func_array($module . '_' . $hook, $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 26 Nov 2010 17:51:41 -0000 @@ -188,8 +188,14 @@ 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)) { + $function = FALSE; + if ($options['default']) { + $function = 'field_default_' . $op; + } + elseif (module_hook($field['module'], 'field_' . $op)) { + $function = $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 +304,14 @@ 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)) { + $function = FALSE; + if ($options['default']) { + $function = 'field_default_' . $op; + } + elseif (module_hook($field['module'], 'field_' . $op)) { + $function = $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 +337,13 @@ 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; + $function = FALSE; + if ($options['default']) { + $function = 'field_default_' . $op; + } + elseif (module_hook($field['module'], 'field_' . $op)) { + $function = $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 26 Nov 2010 17:51:41 -0000 @@ -157,8 +157,8 @@ 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 (module_hook($module, 'field_formatter_prepare_view')) { + $function = $module . '_field_formatter_prepare_view'; $function($entity_type, $grouped_entities[$module], $field, $grouped_instances[$module], $langcode, $grouped_items[$module], $grouped_displays[$module]); } } @@ -203,8 +203,8 @@ 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 (module_hook($display['module'], 'field_formatter_view')) { + $function = $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 26 Nov 2010 17:51:41 -0000 @@ -61,8 +61,8 @@ 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 (module_hook($instance['widget']['module'], 'field_widget_form')) { + $function = $instance['widget']['module'] . '_field_widget_form'; $element = array( '#entity_type' => $instance['entity_type'], '#bundle' => $instance['bundle'], @@ -160,8 +160,8 @@ function field_multiple_value_form($fiel $field_elements = array(); - $function = $instance['widget']['module'] . '_field_widget_form'; - if (function_exists($function)) { + if (module_hook($instance['widget']['module'], 'field_widget_form')) { + $function = $instance['widget']['module'] . '_field_widget_form'; for ($delta = 0; $delta <= $max; $delta++) { $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; $element = array( @@ -337,7 +337,7 @@ function field_default_form_errors($enti if (!empty($field_state['errors'])) { $function = $instance['widget']['module'] . '_field_widget_error'; - $function_exists = function_exists($function); + $function_exists = 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']); 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 26 Nov 2010 17:51:41 -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,8 @@ function field_get_default_value($entity * the array keys to ensure sequential deltas. */ function _field_filter_items($field, $items) { + module_hook($field['module'], 'field_is_empty'); $function = $field['module'] . '_field_is_empty'; - function_exists($function); foreach ((array) $items as $delta => $item) { // Explicitly break if the function is undefined. if ($function($item, $field)) { Index: modules/field_ui/field_ui.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field_ui/field_ui.admin.inc,v retrieving revision 1.86 diff -u -p -r1.86 field_ui.admin.inc --- modules/field_ui/field_ui.admin.inc 21 Nov 2010 07:28:39 -0000 1.86 +++ modules/field_ui/field_ui.admin.inc 26 Nov 2010 18:10:52 -0000 @@ -1538,9 +1538,9 @@ function field_ui_field_settings_form($f // responsible for not returning settings that cannot be changed if // the field already has data. $form['field']['settings'] = array(); - $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance, $has_data); - if (is_array($additions)) { - $form['field']['settings'] = $additions; + if (module_hook($field['module'], 'field_settings_form')) { + $function = $field['module'] . '_field_settings_form'; + $form['field']['settings'] = $function($field, $instance, $has_data, $form, $form_state); } if (empty($form['field']['settings'])) { $form['field']['settings'] = array( @@ -1814,15 +1814,17 @@ function field_ui_field_edit_form($form, ); // Add additional field instance settings from the field module. - $additions = module_invoke($field['module'], 'field_instance_settings_form', $field, $instance); - if (is_array($additions)) { - $form['instance']['settings'] = $additions; + $form['instance']['settings'] = array(); + if (module_hook($field['module'], 'field_instance_settings_form')) { + $function = $field['module'] . '_field_instance_settings_form'; + $form['instance']['settings'] = $function($field, $instance, $form, $form_state); } // Add additional widget settings from the widget module. - $additions = module_invoke($widget_type['module'], 'field_widget_settings_form', $field, $instance); - if (is_array($additions)) { - $form['instance']['widget']['settings'] = $additions; + $form['instance']['widget']['settings'] = array(); + if (module_hook($widget_type['module'], 'field_widget_settings_form')) { + $function = $widget_type['module'] . '_field_widget_settings_form'; + $form['instance']['widget']['settings'] = $function($field, $instance, $form, $form_state); $form['instance']['widget']['active']['#value'] = 1; } @@ -1863,9 +1865,10 @@ function field_ui_field_edit_form($form, // Add additional field type settings. The field type module is // responsible for not returning settings that cannot be changed if // the field already has data. - $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance, $has_data); - if (is_array($additions)) { - $form['field']['settings'] = $additions; + $form['field']['settings'] = array(); + if (module_hook($field['module'], 'field_settings_form')) { + $function = $field['module'] . '_field_settings_form'; + $form['field']['settings'] = $function($field, $instance, $has_data, $form, $form_state); } $form['actions'] = array('#type' => 'actions'); Index: modules/field_ui/field_ui.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/field_ui/field_ui.api.php,v retrieving revision 1.10 diff -u -p -r1.10 field_ui.api.php --- modules/field_ui/field_ui.api.php 12 Nov 2010 03:10:38 -0000 1.10 +++ modules/field_ui/field_ui.api.php 26 Nov 2010 18:04:00 -0000 @@ -32,11 +32,15 @@ * The instance structure being configured. * @param $has_data * TRUE if the field already has data, FALSE if not. + * @param $complete_form + * The form structure of field_ui_field_edit_form(). + * @param $form_state + * The current state of the form. * * @return * The form definition for the field settings. */ -function hook_field_settings_form($field, $instance, $has_data) { +function hook_field_settings_form($field, $instance, $has_data, $complete_form, &$form_state) { $settings = $field['settings']; $form['max_length'] = array( '#type' => 'textfield', @@ -59,11 +63,15 @@ function hook_field_settings_form($field * The field structure being configured. * @param $instance * The instance structure being configured. + * @param $complete_form + * The form structure of field_ui_field_edit_form(). + * @param $form_state + * The current state of the form. * * @return * The form definition for the field instance settings. */ -function hook_field_instance_settings_form($field, $instance) { +function hook_field_instance_settings_form($field, $instance, $complete_form, &$form_state) { $settings = $instance['settings']; $form['text_processing'] = array( @@ -101,11 +109,15 @@ function hook_field_instance_settings_fo * The field structure being configured. * @param $instance * The instance structure being configured. + * @param $complete_form + * The form structure of field_ui_field_edit_form(). + * @param $form_state + * The current state of the form. * * @return * The form definition for the widget settings. */ -function hook_field_widget_settings_form($field, $instance) { +function hook_field_widget_settings_form($field, $instance, $complete_form, &$form_state) { $widget = $instance['widget']; $settings = $widget['settings']; 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 26 Nov 2010 18:25:20 -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(); @@ -72,9 +75,16 @@ function file_field_settings_form($field /** * Implements hook_field_instance_settings_form(). */ -function file_field_instance_settings_form($field, $instance) { +function file_field_instance_settings_form($field, $instance, $complete_form, &$form_state) { $settings = $instance['settings']; + // #element_validate handlers are defined in this file, so ensure that it is + // loaded when the form is processed from cache. + $form_state['build_info']['files']['file'] = array( + 'module' => 'file', + 'name' => 'file.field', + ); + $form['file_directory'] = array( '#type' => 'textfield', '#title' => t('File directory'), @@ -486,6 +496,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 26 Nov 2010 17:51:41 -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 26 Nov 2010 18:24:53 -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(). */ @@ -67,11 +71,18 @@ function image_field_settings_form($fiel /** * Implements hook_field_instance_settings_form(). */ -function image_field_instance_settings_form($field, $instance) { +function image_field_instance_settings_form($field, $instance, $complete_form, &$form_state) { $settings = $instance['settings']; // Use the file field instance settings form as a basis. - $form = file_field_instance_settings_form($field, $instance); + $form = file_field_instance_settings_form($field, $instance, $complete_form, $form_state); + + // #element_validate handlers are defined in this file, so ensure that it is + // loaded when the form is processed from cache. + $form_state['build_info']['files']['image'] = array( + 'module' => 'image', + 'name' => 'image.field', + ); // Add maximum and minimum resolution settings. $max_resolution = explode('x', $settings['max_resolution']) + array('', ''); @@ -267,12 +278,12 @@ function image_field_widget_info() { /** * Implements hook_field_widget_settings_form(). */ -function image_field_widget_settings_form($field, $instance) { +function image_field_widget_settings_form($field, $instance, $complete_form, &$form_state) { $widget = $instance['widget']; $settings = $widget['settings']; // Use the file widget settings form. - $form = file_field_widget_settings_form($field, $instance); + $form = file_field_widget_settings_form($field, $instance, $complete_form, $form_state); $form['preview_image_style'] = array( '#title' => t('Preview image style'), @@ -313,6 +324,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 26 Nov 2010 17:51:41 -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(). */