Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1264 diff -u -p -r1.1264 common.inc --- includes/common.inc 22 Nov 2010 05:22:43 -0000 1.1264 +++ includes/common.inc 29 Nov 2010 04:29:44 -0000 @@ -7308,14 +7308,36 @@ function entity_create_stub_entity($enti * * @todo Remove $conditions in Drupal 8. */ -function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) { +function entity_load($entity_type, $ids = array(), $conditions = array(), $reset = FALSE) { if ($reset) { - entity_get_controller($entity_type)->resetCache(); + entity_get_controller($entity_type)->resetCache($ids); } return entity_get_controller($entity_type)->load($ids, $conditions); } /** + * Loads the unchanged, i.e. not modified, entity from the database. + * + * Unlike entity_load() this function ensures the entity is directly loaded from + * the database, thus bypassing any static cache. In particular, this function + * is useful to determine changes by comparing the entity being saved to the + * stored entity. + * + * @param $entity_type + * The entity type to load, e.g. node or user. + * @param $id + * The id of the entity to load. + * + * @return + * The unchanged entity, or FALSE if the entity cannot be loaded. + */ +function entity_load_unchanged($entity_type, $id) { + entity_get_controller($entity_type)->resetCache(array($id)); + $result = entity_get_controller($entity_type)->load(array($id)); + return reset($result); +} + +/** * Get the entity controller class for an entity type. */ function entity_get_controller($entity_type) { Index: includes/entity.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/entity.inc,v retrieving revision 1.18 diff -u -p -r1.18 entity.inc --- includes/entity.inc 14 Nov 2010 22:07:57 -0000 1.18 +++ includes/entity.inc 29 Nov 2010 04:31:14 -0000 @@ -24,8 +24,12 @@ interface DrupalEntityControllerInterfac /** * Resets the internal, static entity cache. + * + * @param $id + * (optional) If specified, the cache is only reset for the entity with + * the given id. */ - public function resetCache(); + public function resetCache(array $ids = NULL); /** * Loads one or more entities. @@ -38,7 +42,7 @@ interface DrupalEntityControllerInterfac * @return * An array of entity objects indexed by their ids. */ - public function load($ids = array(), $conditions = array()); + public function load(array $ids = array(), array $conditions = array()); } /** @@ -139,14 +143,21 @@ class DrupalDefaultEntityController impl /** * Implements DrupalEntityControllerInterface::resetCache(). */ - public function resetCache() { - $this->entityCache = array(); + public function resetCache(array $ids = NULL) { + if (isset($ids)) { + foreach ($ids as $id) { + unset($this->entityCache[$id]); + } + } + else { + $this->entityCache = array(); + } } /** * Implements DrupalEntityControllerInterface::load(). */ - public function load($ids = array(), $conditions = array()) { + public function load(array $ids = array(), array $conditions = array()) { $entities = array(); // Revisions are not statically cached, and require a different query to Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.920 diff -u -p -r1.920 comment.module --- modules/comment/comment.module 23 Nov 2010 06:02:06 -0000 1.920 +++ modules/comment/comment.module 29 Nov 2010 04:33:07 -0000 @@ -1442,6 +1442,11 @@ function comment_save($comment) { $comment->node_type = 'comment_node_' . $node->type; } + // Load the stored entity, if any. + if (!empty($comment->cid)) { + $comment->original = entity_load_unchanged('comment', $comment->cid); + } + field_attach_presave('comment', $comment); // Allow modules to alter the comment before saving. Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1326 diff -u -p -r1.1326 node.module --- modules/node/node.module 27 Nov 2010 20:25:44 -0000 1.1326 +++ modules/node/node.module 29 Nov 2010 04:35:45 -0000 @@ -1032,6 +1032,11 @@ function node_save($node) { $transaction = db_transaction(); try { + // Load the stored entity, if any. + if (!empty($node->nid)) { + $node->original = entity_load_unchanged('node', $node->nid); + } + field_attach_presave('node', $node); global $user; Index: modules/node/node.test =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.test,v retrieving revision 1.102 diff -u -p -r1.102 node.test --- modules/node/node.test 24 Nov 2010 16:25:39 -0000 1.102 +++ modules/node/node.test 29 Nov 2010 04:24:58 -0000 @@ -1041,14 +1041,14 @@ class NodeSaveTestCase extends DrupalWeb $changed = $node->changed; node_save($node); - $node = $this->drupalGetNodeByTitle($edit['title']); + $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); $this->assertEqual($node->created, $created, t('Updating a node preserves "created" timestamp.')); // Programmatically set the timestamps using hook_node_presave. $node->title = 'testing_node_presave'; node_save($node); - $node = $this->drupalGetNodeByTitle('testing_node_presave'); + $node = $this->drupalGetNodeByTitle('testing_node_presave', TRUE); $this->assertEqual($node->created, 280299600, t('Saving a node uses "created" timestamp set in presave hook.')); $this->assertEqual($node->changed, 979534800, t('Saving a node uses "changed" timestamp set in presave hook.')); @@ -1071,10 +1071,35 @@ class NodeSaveTestCase extends DrupalWeb $node->changed = 280299600; node_save($node); - $node = $this->drupalGetNodeByTitle($edit['title']); + $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); $this->assertEqual($node->created, 979534800, t('Updating a node uses user-set "created" timestamp.')); $this->assertNotEqual($node->changed, 280299600, t('Updating a node doesn\'t use user-set "changed" timestamp.')); } + + /** + * Tests determing changes in hook_node_presave(). + */ + function testDeterminingChanges() { + // Initial creation. + $node = (object) array( + 'uid' => $this->web_user->uid, + 'type' => 'article', + 'title' => 'test_changes', + ); + node_save($node); + + // Update the node without applying changes. + node_save($node); + $this->assertEqual($node->title, 'test_changes', 'No changes have been determined.'); + + // Apply changes. + $node->title = 'updated'; + node_save($node); + + // The hook implementation node_test_node_presave() has to determine the + // changes and set the title to 'changed'. + $this->assertEqual($node->title, 'changed', 'Changes have been determined.'); + } } /** Index: modules/node/tests/node_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/tests/node_test.module,v retrieving revision 1.13 diff -u -p -r1.13 node_test.module --- modules/node/tests/node_test.module 24 Nov 2010 16:25:39 -0000 1.13 +++ modules/node/tests/node_test.module 29 Nov 2010 04:24:58 -0000 @@ -131,4 +131,10 @@ function node_test_node_presave($node) { // Drupal 1.0 release. $node->changed = 979534800; } + // Determine changes. + if (!empty($node->nid)) { + if ($node->original->title == 'test_changes' && $node->original->title != $node->title) { + $node->title = 'changed'; + } + } } Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.251 diff -u -p -r1.251 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 28 Nov 2010 07:32:39 -0000 1.251 +++ modules/simpletest/drupal_web_test_case.php 29 Nov 2010 04:24:58 -0000 @@ -801,12 +801,14 @@ class DrupalWebTestCase extends DrupalTe * * @param title * A node title, usually generated by $this->randomName(). + * @param $reset + * (optional) Whether to reset the internal node_load() cache. * * @return * A node object matching $title. */ - function drupalGetNodeByTitle($title) { - $nodes = node_load_multiple(array(), array('title' => $title)); + function drupalGetNodeByTitle($title, $reset = FALSE) { + $nodes = node_load_multiple(array(), array('title' => $title), $reset); // Load the first node returned from the database. $returned_node = reset($nodes); return $returned_node; Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.621 diff -u -p -r1.621 taxonomy.module --- modules/taxonomy/taxonomy.module 27 Nov 2010 20:25:44 -0000 1.621 +++ modules/taxonomy/taxonomy.module 29 Nov 2010 04:39:13 -0000 @@ -388,9 +388,12 @@ function taxonomy_vocabulary_save($vocab if (!empty($vocabulary->name)) { $vocabulary->name = trim($vocabulary->name); } - // For existing vocabularies, make sure we can detect machine name changes. - if (!empty($vocabulary->vid) && !isset($vocabulary->old_machine_name)) { - $vocabulary->old_machine_name = db_query("SELECT machine_name FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $vocabulary->vid))->fetchField(); + // Load the stored entity, if any. + if (!empty($vocabulary->vid)) { + $vocabulary->original = entity_load_unchanged('vocabulary', $vocabulary->vid); + + // Make sure machine name changes are detected. + $vocabulary->old_machine_name = $vocabulary->original->machine_name; } if (!isset($vocabulary->module)) { @@ -540,6 +543,11 @@ function taxonomy_term_save($term) { $term->vocabulary_machine_name = $vocabulary->machine_name; } + // Load the stored entity, if any. + if (!empty($term->tid)) { + $term->original = entity_load_unchanged('taxonomy_term', $term->tid); + } + field_attach_presave('taxonomy_term', $term); module_invoke_all('taxonomy_term_presave', $term); Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.1221 diff -u -p -r1.1221 user.module --- modules/user/user.module 26 Nov 2010 10:14:10 -0000 1.1221 +++ modules/user/user.module 29 Nov 2010 04:40:22 -0000 @@ -405,6 +405,11 @@ function user_save($account, $edit = arr unset($edit['pass']); } + // Load the stored entity, if any. + if (!empty($account->uid)) { + $account->original = entity_load_unchanged('user', $account->uid); + } + // Presave field allowing changing of $edit. $edit = (object) $edit; field_attach_presave('user', $edit);