'. t('The diff module overwrites the normal revisions view. The revisions table is enhanced with a possibility to view the difference between two node revisions. Users with the %view_revisions permission will also be able to view the changes between any two selected revisions. You may disable this for individual content types on the content type configuration page. This module also provides a nifty %preview_changes button while editing a post.', array('%preview_changes' => t('Preview changes'), '%view_revisions' => t('view revisions'))).'
'; return $output; } } /** * Implementation of hook_requirements(). * Checks if the diff modules is loaded after the node module in the hook ordering. */ function diff_requirements($phase) { // Don't check when installing if ($phase == 'install') { return; } $modules = array_keys(module_list()); if (array_search('diff', $modules) <= array_search('node', $modules)) { diff_autoadjust(); } } /** * Implementation of hook_menu() * The menu path 'node/$nid/revisions' is overriden with 'diff_diffs'. */ function diff_menu($may_cache) { $items = array(); if (!$may_cache) { if (arg(0) == 'node' && is_numeric(arg(1))) { $node = node_load(arg(1)); if ($node->nid) { $revisions_access = (user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node) && db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', arg(1))) > 1; $items[] = array( 'path' => 'node/'. arg(1) .'/revisions', 'title' => t('Revisions'), 'callback' => 'diff_diffs', 'access' => $revisions_access, 'weight' => 4, 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'node/'. arg(1) .'/revisions/view/latest', 'title' => t('Revisions'), 'callback' => 'diff_latest', 'callback arguments' => array(arg(1)), 'access' => $revisions_access, 'type' => MENU_CALLBACK, ); } } } return $items; } // Menu callback - show latest revision for a given node. function diff_latest($nid) { $nid = (int)$nid; $revisions = node_revision_list(node_load($nid)); $new = array_shift($revisions); $old = array_shift($revisions); drupal_goto("node/$nid/revisions/view/$old->vid/$new->vid"); } /** * Adjust the module weights for diff to load after node module. */ function diff_autoadjust() { $modules = array_keys(module_list()); if (array_search('diff', $modules) <= array_search('node', $modules)) { module_load_install('diff'); diff_set_weight(); } } /** * Menu callback for diff related activities. */ function diff_diffs() { if (is_numeric(arg(1)) && arg(2) == 'revisions') { $op = arg(3) ? arg(3) : 'overview'; switch ($op) { case 'overview': $node = node_load(arg(1)); if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) { return diff_diffs_overview($node); } drupal_access_denied(); return; case 'view': if (is_numeric(arg(4)) && is_numeric(arg(5))) { $node = node_load(arg(1)); if ($node->nid) { if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) { drupal_set_title(t('Diff for %title', array('%title' => $node->title))); return diff_diffs_show($node, arg(4), arg(5)); } drupal_access_denied(); return; } } break; default: // A view, revert or delete operation from the orignial node module, // so call the original node module to handle this. return node_revisions(); break; } } drupal_not_found(); } /** * Generate an overview table of older revisions of a node and provide * an input form to select two revisions for a comparison. */ function diff_diffs_overview(&$node) { $output = ''; drupal_set_title(t('Revisions for %title', array('%title' => $node->title))); $output .= drupal_get_form('diff_node_revisions', $node); return $output; } /** * Input form to select two revisions. * * @param $node * Node whose revisions are displayed for selection. */ function diff_node_revisions(&$node) { global $form_values; $form = array(); $form['nid'] = array( '#type' => 'hidden', '#value' => $node->nid, ); $revision_list = node_revision_list($node); if (count($revision_list) > REVISION_LIST_SIZE) { // If the list of revisions is longer than the number shown on one page split the array. $page = isset($_GET['page']) ? $_GET['page'] : '0'; $revision_chunks = array_chunk(node_revision_list($node), REVISION_LIST_SIZE); $revisions = $revision_chunks[$page]; // Set up global pager variables as would 'pager_query' do. // These variables are then used in the theme('pager') call later. global $pager_page_array, $pager_total, $pager_total_items; $pager_total_items[0] = count($revision_list); $pager_total[0] = ceil(count($revision_list) / REVISION_LIST_SIZE); $pager_page_array[0] = max(0, min($page, ((int)$pager_total[0]) - 1)); } else { $revisions = $revision_list; } $revert_permission = FALSE; if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { $revert_permission = TRUE; } $delete_permission = FALSE; if (user_access('administer nodes')) { $delete_permission = TRUE; } foreach ($revisions as $revision) { $operations = array(); $revision_ids[$revision->vid] = ''; if ($revision->current_vid > 0) { $form['info'][$revision->vid] = array( '#value' => t('!date by !username', array( '!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), '!username' => theme('username', $revision))) . (($revision->log != '') ? ''. filter_xss($revision->log) .'
' : ''), ); } else { $form['info'][$revision->vid] = array( '#value' => t('!date by !username', array( '!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), '!username' => theme('username', $revision))) . (($revision->log != '') ? ''. filter_xss($revision->log) .'
' : '') ); if ($revert_permission) { $operations[] = array('#value' => l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert")); } if ($delete_permission) { $operations[] = array('#value' => l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete")); } // Set a dummy, even if the user has no permission for the other // operations, so that we can check if the operations array is // empty to know if the row denotes the current revision. $operations[] = array(); } $form['operations'][$revision->vid] = $operations; } $new_vid = key($revision_ids); next($revision_ids); $old_vid = key($revision_ids); $form['diff']['old'] = array( '#type' => 'radios', '#options' => $revision_ids, '#default_value' => $old_vid ); $form['diff']['new'] = array( '#type' => 'radios', '#options' => $revision_ids, '#default_value' => $new_vid ); $form['submit'] = array('#type' => 'submit', '#value' => t('Show diff')); if (count($revision_list) > REVISION_LIST_SIZE) { $form['#suffix'] = theme('pager', NULL, REVISION_LIST_SIZE, 0); } return $form; } /** * Theme function to display the revisions formular with means to select * two revisions. */ function theme_diff_node_revisions($form) { // Overview table: $header = array( t('Revision'), array('data' => drupal_render($form['submit']), 'colspan' => 2), array('data' => t('Operations'), 'colspan' => 2) ); if (isset($form['info']) && is_array($form['info'])) { foreach (element_children($form['info']) as $key) { $row = array(); if (isset($form['operations'][$key][0])) { // Note: even if the commands for revert and delete are not permitted, // the array is not empty since we set a dummy in this case. $row[] = drupal_render($form['info'][$key]); $row[] = drupal_render($form['diff']['old'][$key]); $row[] = drupal_render($form['diff']['new'][$key]); $row[] = drupal_render($form['operations'][$key][0]); $row[] = drupal_render($form['operations'][$key][1]); $rows[] = $row; } else { // its the current revision (no commands to revert or delete) $row[] = array('data' => drupal_render($form['info'][$key]), 'class' => 'revision-current'); $row[] = array('data' => drupal_render($form['diff']['old'][$key]), 'class' => 'revision-current'); $row[] = array('data' => drupal_render($form['diff']['new'][$key]), 'class' => 'revision-current'); $row[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => '2'); $rows[] = array( 'data' => $row, 'class' => 'error', ); } } } $output .= theme('table', $header, $rows); $output .= drupal_render($form); return $output; } /** * Submit code for input form to select two revisions. */ function diff_node_revisions_submit($form_id, $form_values) { // the ids are ordered so the old revision is always on the left $old_vid = min($form_values['old'], $form_values['new']); $new_vid = max($form_values['old'], $form_values['new']); return 'node/'.$form_values['nid'].'/revisions/view/'.$old_vid.'/'.$new_vid; } /** * Validation for input form to select two revisions. */ function diff_node_revisions_validate($form_id, $form_values) { $old_vid = $form_values['old']; $new_vid = $form_values['new']; if ($old_vid==$new_vid || !$old_vid || !$new_vid) { form_set_error('diff', t('Select different revisions to compare.')); } } /** * Create output string for a comparison of 'node' between * versions 'old_vid' and 'new_vid'. * * @param $node * Node on which to perform comparison * @param $old_vid * Version ID of the old revision. * @param $new_vid * Version ID of the new revision. */ function diff_diffs_show(&$node, $old_vid, $new_vid) { $lame_revisions = node_revision_list($node); foreach ($lame_revisions as $revision) { $node_revisions[$revision->vid] = $revision; } $old_node = node_load($node->nid, $old_vid); $new_node = node_load($node->nid, $new_vid); // Generate table header (date, username, logmessage). $old_header = t('!date by !username', array( '!date' => l(format_date($old_node->revision_timestamp), "node/$node->nid/revisions/$old_node->vid/view"), '!username' => theme('username', $node_revisions[$old_vid]), )); $new_header = t('!date by !username', array( '!date' => l(format_date($new_node->revision_timestamp), "node/$node->nid/revisions/$new_node->vid/view"), '!username' => theme('username', $node_revisions[$new_vid]), )); $old_log = $old_node->log != '' ? ''. filter_xss($old_node->log) .'
' : ''; $new_log = $new_node->log != '' ? ''. filter_xss($new_node->log) .'
' : ''; // Generate previous diff/next diff links. $next_vid = _diff_get_next_vid($node_revisions, $new_vid); if ($next_vid) { $next_link = l(t('next diff >'), 'node/'. $node->nid .'/revisions/view/'. $new_vid .'/'. $next_vid); } else { $next_link = ''; } $prev_vid = _diff_get_previous_vid($node_revisions, $old_vid); if ($prev_vid) { $prev_link = l(t('< previous diff'), 'node/'. $node->nid .'/revisions/view/'. $prev_vid .'/'. $old_vid); } else { $prev_link = ''; } $cols = array( array( array( 'class' => 'diff-marker', ), array( 'class' => 'diff-content', ), array( 'class' => 'diff-marker', ), array( 'class' => 'diff-content', ), ), ); $header = array( array( 'data' => $old_header, 'colspan' => 2 ), array( 'data' => $new_header, 'colspan' => 2 ) ); $rows = array(); if ($old_log || $new_log) { $rows[] = array( array( 'data' => $old_log, 'colspan' => 2 ), array( 'data' => $new_log, 'colspan' => 2 ) ); } $rows[] = array( array( 'data' => $prev_link, 'class' => 'diff-prevlink', 'colspan' => 2 ), array( 'data' => $next_link, 'class' => 'diff-nextlink', 'colspan' => 2 ) ); $rows = array_merge($rows, _diff_body_rows($old_node, $new_node)); $output = theme('diff_table', $header, $rows, array('class' => 'diff'), NULL, $cols); if ($node->vid == $new_vid) { $output .= '