diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index dd1c307bf077d368c1fec95b7f672adbe4bd2522..ac85ebf67212c7dd4a6d4f9870c6b98780410864 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -925,35 +925,6 @@ function hook_field_attach_form($obj_type, $object, &$form, &$form_state, $langc
   }
 }
 
-/**
- * Act on field_attach_pre_load.
- *
- * This hook allows modules to load data before the Field Storage API,
- * optionally preventing the field storage module from doing so.
- *
- * This lets 3rd party modules override, mirror, shard, or otherwise store a
- * subset of fields in a different way than the current storage engine.
- * Possible use cases include : per-bundle storage, per-combo-field storage...
- *
- * @param $obj_type
- *   The type of objects for which to load fields; e.g. 'node' or 'user'.
- * @param $objects
- *   An array of objects for which to load fields, keyed by object id.
- * @param $age
- *   FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
- *   FIELD_LOAD_REVISION to load the version indicated by each object.
- * @param $skip_fields
- *   An array keyed by field ids whose data has already been loaded and
- *   therefore should not be loaded again. The values associated to these keys
- *   are not specified.
- * @return
- *   - Loaded field values are added to $objects. Fields with no values should be
- *   set as an empty array.
- *   - Loaded field ids are set as keys in $skip_fields.
- */
-function hook_field_attach_pre_load($obj_type, $objects, $age, &$skip_fields) {
-}
-
 /**
  * Act on field_attach_load.
  *
@@ -1007,88 +978,41 @@ function hook_field_attach_submit($obj_type, $object, $form, &$form_state) {
 function hook_field_attach_presave($obj_type, $object) {
 }
 
-/**
- * Act on field_attach_preprocess.
- *
- * This hook is invoked while preprocessing the field.tpl.php template file.
- *
- * @param $variables
- *   The variables array is passed by reference and will be populated with field
- *   values.
- * @param $context
- *   An associative array containing:
- *   - obj_type: The type of $object; e.g. 'node' or 'user'.
- *   - object: The object with fields to render.
- *   - element: The structured array containing the values ready for rendering.
- */
-function hook_field_attach_preprocess_alter(&$variables, $context) {
-}
-
 /**
  * Act on field_attach_insert.
  *
- * This hook allows modules to store data before the Field Storage
- * API, optionally preventing the field storage module from doing so.
+ * This hook is invoked after the field module has performed the operation.
  *
- * @param $obj_type
- *   The type of $object; e.g. 'node' or 'user'.
- * @param $object
- *   The object with fields to save.
- * @param $skip_fields
- *   An array keyed by field ids whose data has already been written and
- *   therefore should not be written again. The values associated to these keys
- *   are not specified.
- * @return
- *   Saved field ids are set set as keys in $skip_fields.
+ * See field_attach_insert() for details and arguments.
  */
-function hook_field_attach_pre_insert($obj_type, $object, &$skip_fields) {
+function hook_field_attach_insert($obj_type, $object) {
 }
 
 /**
  * Act on field_attach_update.
  *
- * This hook allows modules to store data before the Field Storage
- * API, optionally preventing the field storage module from doing so.
+ * This hook is invoked after the field module has performed the operation.
  *
- * @param $obj_type
- *   The type of $object; e.g. 'node' or 'user'.
- * @param $object
- *   The object with fields to save.
- * @param $skip_fields
- *   An array keyed by field ids whose data has already been written and
- *   therefore should not be written again. The values associated to these keys
- *   are not specified.
- * @return
- *   Saved field ids are set set as keys in $skip_fields.
+ * See field_attach_update() for details and arguments.
  */
-function hook_field_attach_pre_update($obj_type, $object, &$skip_fields) {
+function hook_field_attach_update($obj_type, $object) {
 }
 
 /**
- * Act on field_attach_pre_query.
+ * Act on field_attach_preprocess.
  *
- * This hook should be implemented by modules that use
- * hook_field_attach_pre_load(), hook_field_attach_pre_insert() and
- * hook_field_attach_pre_update() to bypass the regular storage engine, to
- * handle field queries.
+ * This hook is invoked while preprocessing the field.tpl.php template file.
  *
- * @param $field_name
- *   The name of the field to query.
- * @param $conditions
- *   See field_attach_query().
- *   A storage module that doesn't support querying a given column should raise
- *   a FieldQueryException. Incompatibilities should be mentioned on the module
- *   project page.
- * @param $options
- *   See field_attach_query(). All option keys are guaranteed to be specified.
- * @param $skip_field
- *   Boolean, always coming as FALSE.
- * @return
- *   See field_attach_query().
- *   The $skip_field parameter should be set to TRUE if the query has been
- *   handled.
+ * @param $variables
+ *   The variables array is passed by reference and will be populated with field
+ *   values.
+ * @param $context
+ *   An associative array containing:
+ *   - obj_type: The type of $object; e.g. 'node' or 'user'.
+ *   - object: The object with fields to render.
+ *   - element: The structured array containing the values ready for rendering.
  */
-function hook_field_attach_pre_query($field_name, $conditions, $options, &$skip_field) {
+function hook_field_attach_preprocess_alter(&$variables, $context) {
 }
 
 /**
@@ -1374,6 +1298,151 @@ function hook_field_storage_delete_field($field) {
 function hook_field_storage_delete_instance($instance) {
 }
 
+/**
+ * Act before the storage backends load field data.
+ *
+ * This hook allows modules to load data before the Field Storage API,
+ * optionally preventing the field storage module from doing so.
+ *
+ * This lets 3rd party modules override, mirror, shard, or otherwise store a
+ * subset of fields in a different way than the current storage engine.
+ * Possible use cases include: per-bundle storage, per-combo-field storage...
+ *
+ * @param $obj_type
+ *   The type of objects for which to load fields; e.g. 'node' or 'user'.
+ * @param $objects
+ *   An array of objects for which to load fields, keyed by object id.
+ * @param $age
+ *   FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
+ *   FIELD_LOAD_REVISION to load the version indicated by each object.
+ * @param $skip_fields
+ *   An array keyed by field ids whose data has already been loaded and
+ *   therefore should not be loaded again. The values associated to these keys
+ *   are not specified.
+ * @return
+ *   - Loaded field values are added to $objects. Fields with no values should
+ *     be set as an empty array.
+ *   - Loaded field ids are set as keys in $skip_fields.
+ */
+function hook_field_storage_pre_load($obj_type, $objects, $age, &$skip_fields) {
+}
+
+/**
+ * Act before the storage backends insert field data.
+ *
+ * This hook allows modules to store data before the Field Storage API,
+ * optionally preventing the field storage module from doing so.
+ *
+ * @param $obj_type
+ *   The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ *   The object with fields to save.
+ * @param $skip_fields
+ *   An array keyed by field ids whose data has already been written and
+ *   therefore should not be written again. The values associated to these keys
+ *   are not specified.
+ * @return
+ *   Saved field ids are set set as keys in $skip_fields.
+ */
+function hook_field_storage_pre_insert($obj_type, $object, &$skip_fields) {
+  if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
+    $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
+    foreach ($object->taxonomy_forums as $language) {
+      foreach ($language as $delta) {
+        $query->values(array(
+          'nid' => $object->nid,
+          'title' => $object->title[FIELD_LANGUAGE_NONE][0]['value'],
+          'tid' => $delta['value'],
+          'sticky' => $object->sticky,
+          'created' => $object->created,
+          'comment_count' => 0,
+          'last_comment_timestamp' => $object->created,
+        ));
+      }
+    }
+    $query->execute();
+  }
+}
+
+/**
+ * Act before the storage backends update field data.
+ *
+ * This hook allows modules to store data before the Field Storage API,
+ * optionally preventing the field storage module from doing so.
+ *
+ * @param $obj_type
+ *   The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ *   The object with fields to save.
+ * @param $skip_fields
+ *   An array keyed by field ids whose data has already been written and
+ *   therefore should not be written again. The values associated to these keys
+ *   are not specified.
+ * @return
+ *   Saved field ids are set set as keys in $skip_fields.
+ */
+function hook_field_storage_pre_update($obj_type, $object, &$skip_fields) {
+  $first_call = &drupal_static(__FUNCTION__, array());
+
+  if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
+    // We don't maintain data for old revisions, so clear all previous values
+    // from the table. Since this hook runs once per field, per object, make
+    // sure we only wipe values once.
+    if (!isset($first_call[$object->nid])) {
+      $first_call[$object->nid] = FALSE;
+      db_delete('forum_index')->condition('nid', $object->nid)->execute();
+    }
+    // Only save data to the table if the node is published.
+    if ($object->status) {
+      $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
+      foreach ($object->taxonomy_forums as $language) {
+        foreach ($language as $delta) {
+          $query->values(array(
+            'nid' => $object->nid,
+            'title' => $object->title,
+            'tid' => $delta['value'],
+            'sticky' => $object->sticky,
+            'created' => $object->created,
+            'comment_count' => 0,
+            'last_comment_timestamp' => $object->created,
+          ));
+        }
+      }
+      $query->execute();
+      // The logic for determining last_comment_count is fairly complex, so
+      // call _forum_update_forum_index() too.
+      _forum_update_forum_index($object->nid);
+    }
+  }
+}
+
+/**
+ * Act before the storage backend runs the query.
+ *
+ * This hook should be implemented by modules that use
+ * hook_field_storage_pre_load(), hook_field_storage_pre_insert() and
+ * hook_field_storage_pre_update() to bypass the regular storage engine, to
+ * handle field queries.
+ *
+ * @param $field_name
+ *   The name of the field to query.
+ * @param $conditions
+ *   See field_attach_query().
+ *   A storage module that doesn't support querying a given column should raise
+ *   a FieldQueryException. Incompatibilities should be mentioned on the module
+ *   project page.
+ * @param $options
+ *   See field_attach_query(). All option keys are guaranteed to be specified.
+ * @param $skip_field
+ *   Boolean, always coming as FALSE.
+ * @return
+ *   See field_attach_query().
+ *   The $skip_field parameter should be set to TRUE if the query has been
+ *   handled.
+ */
+function hook_field_storage_pre_query($field_name, $conditions, $options, &$skip_field) {
+}
+
 /**
  * @} End of "ingroup field_storage"
  */
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index bddec5d530cb7ec87c8b9859bc9db7d6daa639e4..b5c0b0e90107746cab02216f8b6976df49fed7b5 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -494,6 +494,7 @@ function field_attach_form($obj_type, $object, &$form, &$form_state, $langcode =
   $form['#extra_fields'] = field_extra_fields($bundle);
 
   // Let other modules make changes to the form.
+  // Avoid module_invoke_all() to let parameters be taken by reference.
   foreach (module_implements('field_attach_form') as $module) {
     $function = $module . '_field_attach_form';
     $function($obj_type, $object, $form, $form_state, $langcode);
@@ -575,16 +576,16 @@ function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT, $opti
   // Fetch other objects from their storage location.
   if ($queried_objects) {
     // The invoke order is:
-    // - hook_field_attach_pre_load()
+    // - hook_field_storage_pre_load()
     // - storage backend's hook_field_storage_load()
     // - field-type module's hook_field_load()
     // - hook_field_attach_load()
 
-    // Invoke hook_field_attach_pre_load(): let any module load field
+    // Invoke hook_field_storage_pre_load(): let any module load field
     // data before the storage engine, accumulating along the way.
     $skip_fields = array();
-    foreach (module_implements('field_attach_pre_load') as $module) {
-      $function = $module . '_field_attach_pre_load';
+    foreach (module_implements('field_storage_pre_load') as $module) {
+      $function = $module . '_field_storage_pre_load';
       $function($obj_type, $queried_objects, $age, $skip_fields, $options);
     }
 
@@ -627,10 +628,7 @@ function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT, $opti
 
     // Invoke hook_field_attach_load(): let other modules act on loading the
     // object.
-    foreach (module_implements('field_attach_load') as $module) {
-      $function = $module . '_field_attach_load';
-      $function($obj_type, $queried_objects, $age, $options);
-    }
+    module_invoke_all('field_attach_load', $obj_type, $queried_objects, $age, $options);
 
     // Build cache data.
     if ($cache_write) {
@@ -695,6 +693,7 @@ function field_attach_validate($obj_type, $object) {
   _field_invoke('validate', $obj_type, $object, $errors);
 
   // Let other modules validate the object.
+  // Avoid module_invoke_all() to let $errors be taken by reference.
   foreach (module_implements('field_attach_validate') as $module) {
     $function = $module . '_field_attach_validate';
     $function($obj_type, $object, $errors);
@@ -772,6 +771,7 @@ function field_attach_submit($obj_type, $object, $form, &$form_state) {
   _field_invoke_default('submit', $obj_type, $object, $form, $form_state);
 
   // Let other modules act on submitting the object.
+  // Avoid module_invoke_all() to let $form_state be taken by reference.
   foreach (module_implements('field_attach_submit') as $module) {
     $function = $module . '_field_attach_submit';
     $function($obj_type, $object, $form, $form_state);
@@ -793,10 +793,7 @@ function field_attach_presave($obj_type, $object) {
   _field_invoke('presave', $obj_type, $object);
 
   // Let other modules act on presaving the object.
-  foreach (module_implements('field_attach_presave') as $module) {
-    $function = $module . '_field_attach_presave';
-    $function($obj_type, $object);
-  }
+  module_invoke_all('field_attach_presave', $obj_type, $object);
 }
 
 /**
@@ -821,11 +818,11 @@ function field_attach_insert($obj_type, $object) {
 
   list($id, $vid, $bundle, $cacheable) = entity_extract_ids($obj_type, $object);
 
-  // Let other modules act on inserting the object, accumulating saved
-  // fields along the way.
+  // Let any module insert field data before the storage engine, accumulating
+  // saved fields along the way.
   $skip_fields = array();
-  foreach (module_implements('field_attach_pre_insert') as $module) {
-    $function = $module . '_field_attach_pre_insert';
+  foreach (module_implements('field_storage_pre_insert') as $module) {
+    $function = $module . '_field_storage_pre_insert';
     $function($obj_type, $object, $skip_fields);
   }
 
@@ -849,6 +846,9 @@ function field_attach_insert($obj_type, $object) {
     module_invoke($storage_info['module'], 'field_storage_write', $obj_type, $object, FIELD_STORAGE_INSERT, $fields);
   }
 
+  // Let other modules act on inserting the object.
+  module_invoke_all('field_attach_insert', $obj_type, $object);
+
   if ($cacheable) {
     cache_clear_all("field:$obj_type:$id", 'cache_field');
   }
@@ -867,11 +867,11 @@ function field_attach_update($obj_type, $object) {
 
   list($id, $vid, $bundle, $cacheable) = entity_extract_ids($obj_type, $object);
 
-  // Let other modules act on updating the object, accumulating saved
-  // fields along the way.
+  // Let any module update field data before the storage engine, accumulating
+  // saved fields along the way.
   $skip_fields = array();
-  foreach (module_implements('field_attach_pre_update') as $module) {
-    $function = $module . '_field_attach_pre_update';
+  foreach (module_implements('field_storage_pre_update') as $module) {
+    $function = $module . '_field_storage_pre_update';
     $function($obj_type, $object, $skip_fields);
   }
 
@@ -899,6 +899,9 @@ function field_attach_update($obj_type, $object) {
     module_invoke($storage_info['module'], 'field_storage_write', $obj_type, $object, FIELD_STORAGE_UPDATE, $fields);
   }
 
+  // Let other modules act on updating the object.
+  module_invoke_all('field_attach_update', $obj_type, $object);
+
   if ($cacheable) {
     cache_clear_all("field:$obj_type:$id", 'cache_field');
   }
@@ -933,10 +936,7 @@ function field_attach_delete($obj_type, $object) {
   }
 
   // Let other modules act on deleting the object.
-  foreach (module_implements('field_attach_delete') as $module) {
-    $function = $module . '_field_attach_delete';
-    $function($obj_type, $object);
-  }
+  module_invoke_all('field_attach_delete', $obj_type, $object);
 
   if ($cacheable) {
     cache_clear_all("field:$obj_type:$id", 'cache_field');
@@ -972,10 +972,7 @@ function field_attach_delete_revision($obj_type, $object) {
   }
 
   // Let other modules act on deleting the revision.
-  foreach (module_implements('field_attach_delete_revision') as $module) {
-    $function = $module . '_field_attach_delete_revision';
-    $function($obj_type, $object);
-  }
+  module_invoke_all('field_attach_delete_revision', $obj_type, $object);
 }
 
 /**
@@ -1071,8 +1068,8 @@ function field_attach_query($field_id, $conditions, $options = array()) {
   // Give a chance to 3rd party modules that bypass the storage engine to
   // handle the query.
   $skip_field = FALSE;
-  foreach (module_implements('field_attach_pre_query') as $module) {
-    $function = $module . '_field_attach_pre_query';
+  foreach (module_implements('field_storage_pre_query') as $module) {
+    $function = $module . '_field_storage_pre_query';
     $results = $function($field_id, $conditions, $options, $skip_field);
     // Stop as soon as a module claims it handled the query.
     if ($skip_field) {
@@ -1301,10 +1298,8 @@ function field_attach_create_bundle($obj_type, $bundle) {
   // Clear the cache.
   field_cache_clear();
 
-  foreach (module_implements('field_attach_create_bundle') as $module) {
-    $function = $module . '_field_attach_create_bundle';
-    $function($obj_type, $bundle);
-  }
+  // Let other modules act on creating the bundle.
+  module_invoke_all('field_attach_create_bundle', $obj_type, $bundle);
 }
 
 /**
@@ -1327,10 +1322,8 @@ function field_attach_rename_bundle($obj_type, $bundle_old, $bundle_new) {
   // Clear the cache.
   field_cache_clear();
 
-  foreach (module_implements('field_attach_rename_bundle') as $module) {
-    $function = $module . '_field_attach_rename_bundle';
-    $function($obj_type, $bundle_old, $bundle_new);
-  }
+  // Let other modules act on renaming the bundle.
+  module_invoke_all('field_attach_rename_bundle', $obj_type, $bundle_old, $bundle_new);
 }
 
 /**
@@ -1359,10 +1352,7 @@ function field_attach_delete_bundle($obj_type, $bundle) {
   field_cache_clear();
 
   // Let other modules act on deleting the bundle.
-  foreach (module_implements('field_attach_delete_bundle') as $module) {
-    $function = $module . '_field_attach_delete_bundle';
-    $function($obj_type, $bundle, $instances);
-  }
+  module_invoke_all('field_attach_delete_bundle', $obj_type, $bundle, $instances);
 }
 
 
diff --git a/modules/forum/forum.module b/modules/forum/forum.module
index f4142de4a63c15b60d6f64fd7d35e7c3299b8514..61b68c86328b601a0c319526b3598bc662788eb1 100644
--- a/modules/forum/forum.module
+++ b/modules/forum/forum.module
@@ -420,9 +420,9 @@ function forum_comment_delete($comment) {
 }
 
 /**
- * Implement hook_field_attach_pre_insert().
+ * Implement hook_field_storage_pre_insert().
  */
-function forum_field_attach_pre_insert($obj_type, $object, $skip_fields) {
+function forum_field_storage_pre_insert($obj_type, $object, &$skip_fields) {
   if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
     $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
     foreach ($object->taxonomy_forums as $language) {
@@ -443,9 +443,9 @@ function forum_field_attach_pre_insert($obj_type, $object, $skip_fields) {
 }
 
 /**
- * Implement hook_field_attach_pre_update().
+ * Implement hook_field_storage_pre_update().
  */
-function forum_field_attach_pre_update($obj_type, $object, $skip_fields) {
+function forum_field_storage_pre_update($obj_type, $object, &$skip_fields) {
   $first_call = &drupal_static(__FUNCTION__, array());
 
   if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {