Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.239
diff -u -p -r1.239 file.inc
--- includes/file.inc	21 Oct 2010 12:09:41 -0000	1.239
+++ includes/file.inc	11 Nov 2010 02:16:18 -0000
@@ -568,6 +568,9 @@ function file_save(stdClass $file) {
   $file->timestamp = REQUEST_TIME;
   $file->filesize = filesize($file->uri);
 
+  module_invoke_all('file_presave', $file);
+  module_invoke_all('entity_presave', $file, 'file');
+
   if (empty($file->fid)) {
     drupal_write_record('file_managed', $file);
     // Inform modules about the newly added file.
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.913
diff -u -p -r1.913 comment.module
--- modules/comment/comment.module	7 Nov 2010 21:46:09 -0000	1.913
+++ modules/comment/comment.module	11 Nov 2010 02:16:19 -0000
@@ -1433,6 +1433,7 @@ function comment_save($comment) {
 
     // Allow modules to alter the comment before saving.
     module_invoke_all('comment_presave', $comment);
+    module_invoke_all('entity_presave', $comment, 'comment');
 
     if ($comment->cid) {
       // Update the comment in the database.
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1317
diff -u -p -r1.1317 node.module
--- modules/node/node.module	9 Nov 2010 08:36:38 -0000	1.1317
+++ modules/node/node.module	11 Nov 2010 02:16:19 -0000
@@ -1035,6 +1035,7 @@ function node_save($node) {
 
     // Let modules modify the node before it is saved to the database.
     module_invoke_all('node_presave', $node);
+    module_invoke_all('entity_presave', $node, 'node');
 
     if ($node->is_new || !empty($node->revision)) {
       // When inserting either a new node or a new node revision, $node->log
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.616
diff -u -p -r1.616 taxonomy.module
--- modules/taxonomy/taxonomy.module	9 Nov 2010 17:52:57 -0000	1.616
+++ modules/taxonomy/taxonomy.module	11 Nov 2010 02:16:20 -0000
@@ -388,6 +388,7 @@ function taxonomy_vocabulary_save($vocab
   }
 
   module_invoke_all('taxonomy_vocabulary_presave', $vocabulary);
+  module_invoke_all('entity_presave', $vocabulary, 'taxonomy_vocabulary');
 
   if (!empty($vocabulary->vid) && !empty($vocabulary->name)) {
     $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid');
@@ -524,6 +525,7 @@ function taxonomy_term_save($term) {
 
   field_attach_presave('taxonomy_term', $term);
   module_invoke_all('taxonomy_term_presave', $term);
+  module_invoke_all('entity_presave', $term, 'taxonomy_term');
 
   if (empty($term->tid)) {
     $op = 'insert';
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1214
diff -u -p -r1.1214 user.module
--- modules/user/user.module	6 Nov 2010 23:24:33 -0000	1.1214
+++ modules/user/user.module	11 Nov 2010 02:16:20 -0000
@@ -416,6 +416,12 @@ function user_save($account, $edit = arr
     }
     user_module_invoke('presave', $edit, $account, $category);
 
+    $edit = (object) $edit;
+    $edit->uid = isset($account->uid) ? $account->uid : NULL;
+    module_invoke_all('entity_presave', $edit, 'user');
+    unset($edit->uid);
+    $edit = (array) $edit;
+
     if (is_object($account) && !$account->is_new) {
       // Consider users edited by an administrator as logged in, if they haven't
       // already, so anonymous users can view the profile (if allowed).
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.211
diff -u -p -r1.211 system.api.php
--- modules/system/system.api.php	28 Oct 2010 02:27:09 -0000	1.211
+++ modules/system/system.api.php	11 Nov 2010 02:16:21 -0000
@@ -276,6 +276,28 @@ function hook_entity_load($entities, $ty
 }
 
 /**
+ * Act on an entity before it is about to be created or updated.
+ *
+ * @param $entity
+ *   The entity object.
+ * @param $type
+ *   The type of entity being inserted (i.e. node, user, comment).
+ */
+function hook_entity_presave($entity, $type) {
+  // Insert the new entity into a fictional table of all entities.
+  $info = entity_get_info($type);
+  $id = reset(entity_extract_ids($type, $entity));
+  db_insert('example_entity')
+    ->fields(array(
+      'type' => $type,
+      'id' => $id,
+      'created' => REQUEST_TIME,
+      'updated' => REQUEST_TIME,
+    ))
+    ->execute();
+}
+
+/**
  * Act on entities when inserted.
  *
  * @param $entity
Index: modules/simpletest/tests/entity_crud_hook_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/entity_crud_hook_test.module,v
retrieving revision 1.1
diff -u -p -r1.1 entity_crud_hook_test.module
--- modules/simpletest/tests/entity_crud_hook_test.module	15 Oct 2010 03:36:21 -0000	1.1
+++ modules/simpletest/tests/entity_crud_hook_test.module	11 Nov 2010 02:16:21 -0000
@@ -2,6 +2,59 @@
 // $Id: entity_crud_hook_test.module,v 1.1 2010/10/15 03:36:21 webchick Exp $
 
 //
+// Presave hooks
+//
+
+/**
+ * Implements hook_entity_presave().
+ */
+function entity_crud_hook_test_entity_presave($entity, $type) {
+  $_SESSION['entity_crud_hook_test'][] = (__FUNCTION__ . ' called for type ' . $type);
+}
+
+/**
+ * Implements hook_comment_presave().
+ */
+function entity_crud_hook_test_comment_presave() {
+  $_SESSION['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
+}
+
+/**
+ * Implements hook_file_presave().
+ */
+function entity_crud_hook_test_file_presave() {
+  $_SESSION['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
+}
+
+/**
+ * Implements hook_node_presave().
+ */
+function entity_crud_hook_test_node_presave() {
+  $_SESSION['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
+}
+
+/**
+ * Implements hook_taxonomy_term_presave().
+ */
+function entity_crud_hook_test_taxonomy_term_presave() {
+  $_SESSION['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
+}
+
+/**
+ * Implements hook_taxonomy_vocabulary_presave().
+ */
+function entity_crud_hook_test_taxonomy_vocabulary_presave() {
+  $_SESSION['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
+}
+
+/**
+ * Implements hook_user_presave().
+ */
+function entity_crud_hook_test_user_presave() {
+  $_SESSION['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
+}
+
+//
 // Insert hooks
 //
 
Index: modules/simpletest/tests/entity_crud_hook_test.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/entity_crud_hook_test.test,v
retrieving revision 1.1
diff -u -p -r1.1 entity_crud_hook_test.test
--- modules/simpletest/tests/entity_crud_hook_test.test	15 Oct 2010 03:36:21 -0000	1.1
+++ modules/simpletest/tests/entity_crud_hook_test.test	11 Nov 2010 02:16:21 -0000
@@ -81,6 +81,8 @@ class EntityCrudHookTestCase extends Dru
     $_SESSION['entity_crud_hook_test'] = array();
     comment_save($comment);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type comment');
+    $this->assertHookMessage('entity_crud_hook_test_comment_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type comment');
     $this->assertHookMessage('entity_crud_hook_test_comment_insert called');
 
@@ -94,6 +96,8 @@ class EntityCrudHookTestCase extends Dru
     $comment->subject = 'New subject';
     comment_save($comment);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type comment');
+    $this->assertHookMessage('entity_crud_hook_test_comment_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_update called for type comment');
     $this->assertHookMessage('entity_crud_hook_test_comment_update called');
 
@@ -123,6 +127,8 @@ class EntityCrudHookTestCase extends Dru
     $_SESSION['entity_crud_hook_test'] = array();
     file_save($file);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type file');
+    $this->assertHookMessage('entity_crud_hook_test_file_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type file');
     $this->assertHookMessage('entity_crud_hook_test_file_insert called');
 
@@ -136,6 +142,8 @@ class EntityCrudHookTestCase extends Dru
     $file->filename = 'new.entity_crud_hook_test.file';
     file_save($file);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type file');
+    $this->assertHookMessage('entity_crud_hook_test_file_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_update called for type file');
     $this->assertHookMessage('entity_crud_hook_test_file_update called');
 
@@ -165,6 +173,8 @@ class EntityCrudHookTestCase extends Dru
     $_SESSION['entity_crud_hook_test'] = array();
     node_save($node);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type node');
+    $this->assertHookMessage('entity_crud_hook_test_node_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type node');
     $this->assertHookMessage('entity_crud_hook_test_node_insert called');
 
@@ -178,6 +188,8 @@ class EntityCrudHookTestCase extends Dru
     $node->title = 'New title';
     node_save($node);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type node');
+    $this->assertHookMessage('entity_crud_hook_test_node_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_update called for type node');
     $this->assertHookMessage('entity_crud_hook_test_node_update called');
 
@@ -209,6 +221,8 @@ class EntityCrudHookTestCase extends Dru
     $_SESSION['entity_crud_hook_test'] = array();
     taxonomy_term_save($term);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_term');
+    $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type taxonomy_term');
     $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_insert called');
 
@@ -222,6 +236,8 @@ class EntityCrudHookTestCase extends Dru
     $term->name = 'New name';
     taxonomy_term_save($term);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_term');
+    $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_update called for type taxonomy_term');
     $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_update called');
 
@@ -245,6 +261,8 @@ class EntityCrudHookTestCase extends Dru
     $_SESSION['entity_crud_hook_test'] = array();
     taxonomy_vocabulary_save($vocabulary);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary');
+    $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type taxonomy_vocabulary');
     $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_insert called');
 
@@ -258,6 +276,8 @@ class EntityCrudHookTestCase extends Dru
     $vocabulary->name = 'New name';
     taxonomy_vocabulary_save($vocabulary);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary');
+    $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_update called for type taxonomy_vocabulary');
     $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_update called');
 
@@ -283,6 +303,8 @@ class EntityCrudHookTestCase extends Dru
     $_SESSION['entity_crud_hook_test'] = array();
     $account = user_save($account, $edit);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type user');
+    $this->assertHookMessage('entity_crud_hook_test_user_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type user');
     $this->assertHookMessage('entity_crud_hook_test_user_insert called');
 
@@ -296,6 +318,8 @@ class EntityCrudHookTestCase extends Dru
     $edit['name'] = 'New name';
     $account = user_save($account, $edit);
 
+    $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type user');
+    $this->assertHookMessage('entity_crud_hook_test_user_presave called');
     $this->assertHookMessage('entity_crud_hook_test_entity_update called for type user');
     $this->assertHookMessage('entity_crud_hook_test_user_update called');
 
