How to create a new revision from a set of nodes in Drupal

Let's assume you want to mass-update a set of nodes or create a new revision for each of them. You could do this through the UI, but of course for hundreds (or thousands) of nodes this is not really an option. You can do this programatically with a small PHP script.

First of all, you should have some means of running your own PHP code inside your Drupal environment. There're a number of ways for doing this (eg. you can create a page with an input format that has the PHP filter enabled and execute your code simply by viewing your page node), but the Devel module has already this feature and is usable for our purpose. Install this module and add the "Execute PHP" block to one of your site regions (eg. to the left sidebar under the navigation, if you're using the default Garland theme).

Now for the code. It's really simple. We've to fetch a list of the nodes to be modified, load the node, modify it's values and save it. The loading is done with node_load(), the saving with node_save(). The latter is a bit tricky though.

You can create a new revision of a node by loading the node, setting the revision attribute in the node object to TRUE and calling node_save(). However this procedure overwrites the uid of the last revision of the node with the one of the current user (that you're logged on with, when you execute this code). In case you're creating a new revision, then the uid of the new revision will be set as the current user. Since you're mass updating nodes, this is probably not what you want. node_save() sets the uid of the new revision to the current user's uid, so we can change that (eg. to the uid of the last revision of the node) and trick node_save(). Smiling

Another problem with updating nodes with node_save() is that the changed (time of last modification) field of the node and the timestamp field of the revision is updated and there's no workaround for this. The only thing you can do is to save these values and update them manually afterwards with a direct SQL update. There's no other way around this.

Now here comes an example that will create a new revision for each story node in your system and the author of the new revision will be the author of the last revision of the node (or the initial author of the node if there're no revisions yet):

<?php
// execution time and required memory depend on your Drupal setup
// (eg. number of nodes, modules, etc.)
ini_set('max_execution_time'600);
ini_set('memory_limit''128M');
global 
$user;
$old_uid $user->uid;
$rows db_query("SELECT n.`nid` FROM {node} n WHERE n.`type` = 'story' ORDER BY 1");
while (
$node db_fetch_object($rows)) {
 
$node node_load($node->nid);
 
$revisions node_revision_list($node);
  if (
count($revisions) > 0) {
   
// the return value of node_revision_list() contains
    // the list of revisions in a descending order of the creation date
    // so we've to pick the first one
   
reset($revisions);
   
$rev current($revisions);
   
$user->uid $rev->uid;
  } else {
   
$user->uid $node->uid;
  }
 
// the next two lines are only necessary if you want to keep the "changed" and "timestamp" fields
  // as they were
 
$node_changed $node->changed;
 
$rev_timestamp $node->revision_timestamp;
 
// comment out the following if you do not want to create a new revision
 
$node->revision TRUE;
 
node_save($node);
 
// comment out the following two lines if you don't want to have "changed" and "timestamp" fields
  // unaltered
 
db_query("UPDATE {node} SET `changed` = '%d' WHERE `nid` = '%d'"$node_changed$node->nid);
 
db_query("UPDATE {node_revisions} SET `timestamp` = '%d' WHERE `nid` = '%d' AND `vid` = '%d'"$rev_timestamp$node->nid$node->vid);
}
$user->uid $old_uid;
?>

This works if you run it with the Drupal superuser (the one with uid=1), but might fail if you run it with any other user. I'm not sure whether node_save does any access permission tests or not (I didn't see any, but that means nothing). Another solution to the problem could be not to mess with the $user->uid, but simply let node_save() create the new revision with whatever uid it wants to and do an UPDATE on the node_revisions table afterwards and set the uid directly in the table. This is quite brutal again, but Drupal5 has no API for manipulating individual revisions (not that I'd know of).

And if you ask yourself what might be the purpose of all this, here's one example: computed CCK fields. If you're developer of a Drupal based website and you've added a new computed field to a content type that already has a lot of existing content (nodes), chances are high that you want to have a proper value in the computed field even in your old content. Mass updating nodes (either with creating a new revision or without it) causes the computed fields to be recalculated and saved.

PS: in the example above I've included a call to ini_set() to increase the max_execution_time limit. However (depending on your setup) this might not be the only place where you've to adjust limits to increase the max execution time. Eg. if you're using PHP via FastCGI, you've to increase the CGI wrapper's communication timeout too. The max_execution_time parameter is only interpreted by the PHP engine and the CGI wrapper runs the PHP engine, thus the CGI wrapper's execution timeout must be equal or higher than the PHP max_execution_time setting.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Error in case of object declaration in computed_field

Great article (got if from http://drupal.org/node/195013#comment-948201).

This did not work for me, however, since inside my computed field I have a declaration of some class (save in database). Thus, the code given above (replace with my query) - when run via Run PHP of the devel module - outputs an error and actually seems to corrupt the database (I had to import the backup).
The error was: Fatal error: Cannot redeclare class [MYCLASS] in [...] computed_field.module(161) : eval()'d code on line 921

If anybody gets to this comment in the coming days and has a solution, please comment Smiling

Thanks!

Re: Error in case of object declaration in computed_field

I'm sorry it didn't work for you out-of-the-box. I haven't tested the code with your particular setup (a class declaration inside a computed field). Is there any chance to move that class declaration to a custom module and only reference (instantiate) the class in the computed field? It seems that the problem is not really with my code conflicting with your setup. I believe that using a class declaration inside a computed field might trigger the same error during normal operation too. Eg. if you had a module that allowed editing multiple nodes in a single form submit (there're a couple of modules providing such capability), then you should get the error as well.

Your problem comes most probably from the fact that during normal node editing you submit the save of only a single node. Thus the calculated field is evaluated only once during the saving of the one node. However in my code we save a number of nodes ... thus for the first node your calculated field is evaulated and the class is declared. For the second node in the loop the custom field is evaluated too and the class declaration triggers the error since it was already declared in during the evaulation of the calc. field of the first node. And for all succeeding nodes the evaluation of the calculated field triggers the same error message.

Interesting and very useful

Interesting and very useful article. Thanks Eye-wink

And to log the revision

To log the revision fill out $node->log. So you could do something like

<?php
$node
->log "Automatic programmatic update"
?>

Syndicate content