2

I'm using revisions to keep a public archive of changes in my nodes. Is it possible for me to edit an old revision of a node (both the message and the fields) directly, and not change the current revision? Basically, what I'm trying to do is what the User Revision Edit module does, but for nodes.

So far, in my custom module, I have something like this (notice that I'm also using the Revisioning module):

/**
 * Implements hook_menu().
 */
function node_revision_edit_menu() {
  $items['node/%node/revisions/%vid/modify'] = array(
    'title' => 'Modify this revision',
    'load arguments' => array(3),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('node_revision_edit_form', 1, 3),
    'access callback' => '_node_revision_edit_link_access',
    'access arguments' => array('modify revisions'),
    'file' => 'node_revision_edit.admin.inc',
  );
  return $items;
}

and the essence of the logic that I'm trying to apply:

function node_revision_edit_form($form, &$form_state, $node, $vid) {

  // Prepare loading the node edit form
  form_load_include($form_state, 'inc', 'node', 'node.pages');

  // Populate the form state.
  $form_state += form_state_defaults();

  // Save the form id for the current content type.
  $nfid = $node->type . '_node_form';

  // Rebuild the entire user edit form for our needs
  $form = drupal_retrieve_form($nfid, $form_state);
  drupal_prepare_form($nfid, $form, $form_state);

  // Setup the already existing log in the field
  $form['revision_information']['log']['#default_value'] = node_revision_list($node)[$vid]->log;

  // Set the "Modify current revision, no moderation" as the default revision operation
  $form['revision_information']['revision_operation']['#default_value'] = 0;

  // Unset the options that we do not need
  // unset($form['options']);

  // Create a new submit button since we do not want to invoke the standard callback functions
  unset($form['#submit']);
  $form['#submit'] = array('node_revision_edit_form_submit');

My problem is that I have no idea as to where to go from here. I obviously can't use node_save since it automatically handles revisions. Any ideas on what my next step should be? Is there a function that I'm missing and could be useful for my problem? Or will I have to go for direct database edits?

Any feedback is greatly appreciated.

2

I'm afraid that not even _node_save_revision allows that. Revisions were designed to keep old stuff archived and unchanged. Of course sometimes we need to violate that principle for the sake of moderation.

You will have to smartly adapt piece from above function:

$node->uid = $uid; // Make sure revision's author stays unchanged
drupal_write_record('node_revision', $node, $update);

I don't have time to experiment with it, but I'm sure you will find out pretty easily.

Carefully consider if you want to also call

module_invoke_all('node_presave', $node);
module_invoke_all('entity_presave', $node, 'node');

before save, and

module_invoke_all('node_' . $op, $node);
module_invoke_all('entity_' . $op, $node, 'node');

after save. Probably you don't, but it depends - some modules may use revision data for search or other purposes.

Last but not least: I must admit I have no idea how field_attach_update handles field revisions.

  • 1
    I know it is not a full answer. I just hope it will help OP with "what my next step should be?" and I'm perfectly willing to delete it if it'll get obsolete due to more complete answer posted. – Mołot Aug 21 '14 at 7:09
  • 3
    Thanks a lot for taking the time to share your thoughts and knowledge on the question. I have followed a different route to achieve the functionality that I'm after but it's a very "case-specific" approach. I will keep working on this and if I find a more generic solution I'll post it and probably pack it as a module for drupal.org. If not, I will post my current implementation for posterity's sake. – F1234k Aug 21 '14 at 12:23
1

Both node_save and field_attach_update can be used to update old revisions. The issue is that they also update the current revision. Other revisions are left unchanged. Node_save updates the last edited time/date, which is undesirable when updating an old revision, so field_attach_update could be a better option.

<?php

$nid = 1; //node id    
$vid = 5; //revision no

$loaded_node = node_load($nid, $vid);
$original_node = node_load($nid);

//make changes to old node version here
$loaded_node->field_tags[LANGUAGE_NONE][0]['tid'] = '1';

//save the old node revision. unfortunately this updates the current node too.

field_attach_update('node', $loaded_node);
//    node_save($loaded_node); //this is possible here too

//replace the current node with its original content
field_attach_update('node', $original_node);
//    node_save($original_node); // this works but changes the last modified time etc of the current node

?>

You would normally then flush the cache for the given node after performing field_attach_update, however this might not be necessary as wouldn't imagine old revisions are cached and the current node is unchanged.

drupal_write_record would also work to make a modification at the database level, but means you are working with the database rather than a node object.

0

After spending some time browsing through the code of node_save() to figure out exactly how it works and testing various methods, I managed to solve my problem by performing the following steps:

  1. Load the node's revision that the user selected to edit
  2. Set the node's properties from the values retrieved from the $form_state
  3. Call node_save for the specific $node object
  4. Since the newer revisions' state changes to "pending approval", I also have to save the latest revision of the node with node_save so that it becomes current again

My submit callback function looks like this:

/**
 * Submit callback for edit revision form.
 */
function node_revision_edit_form_submit($form, &$form_state) {

  // Store the values that we will be needing
  $nid = $form['nid']['#value'];
  $vid = $form['vid']['#value'];

  // STEP 1
  $node = node_load($nid, $vid, FALSE);
  $latestvidobj = array_shift(node_revision_list($node));
  $latestvid = $latestvidobj->vid;

  // STEP 2
  $node->title = $form_state['values']['title'];
  $node->log = $form_state['values']['log'];
  $node->field_tags = $form_state['values']['field_tags'];
  $node->field_law_article_body = $form_state['values']['field_law_article_body'];

  // These are entity reference fields that accept unlimited values
  // I had to unset an 'add_more' variable that is left over in the $form_state
  unset($form_state['values']['field_article_modifies'][LANGUAGE_NONE]['add_more']);
  unset($form_state['values']['field_article_is_modified'][LANGUAGE_NONE]['add_more']);

  // And then remove an empty array left in there
  $tempfam = array_pop($form_state['values']['field_article_modifies'][LANGUAGE_NONE]);
  $tempfaim = array_pop($form_state['values']['field_article_is_modified'][LANGUAGE_NONE]);
  // Then set the $node's properties
  $node->field_article_modifies = $form_state['values']['field_article_modifies'];
  $node->field_article_is_modified = $form_state['values']['field_article_is_modified'];


  $node->field_law_free_tags = $form_state['values']['field_law_free_tags'];
  $node->field_law_article_extra_notes = $form_state['values']['field_law_article_extra_notes'];

  // STEP 3
  node_save($node);
  // STEP 4
  $latestnode = node_load($nid, $latestvid, FALSE);
  node_save($latestnode);

  // Log the revision save
  watchdog('node', "Modified node id: {$nid} and revision id: {$vid}.");

  // Notify the user that the revision was edited succesfully
  drupal_set_message(t('Updated revision %vid of node %nid.', array('%vid' => $vid, '%nid' => $nid,)), 'status');

  drupal_goto('node/' . $nid . '/revisions/' . $vid . '/list');
}

I don't like this code, since it's case-specific and I would like to find a more abstract way of passing the $form_state['values'] to the $node object. Since I have some other requirements from my editors (like the option to rearrange the revisions), I will try to find a cleaner way to do it and post it here (I may package it as a separate module and post it in drupal.org once the project is done).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.