'. 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 node types on the node type configuration page. This module also provides a nifty button while editing a post.') .'
';
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,
);
}
}
}
return $items;
}
/**
* 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.
*/
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'.
*/
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 = '';
}
// display table
$output .= '';
$output .= ' | '. $old_header .' | | '. $new_header .' |
';
if ($new_log || $old_log) {
$output .= ' | '. $old_log .' | | '. $new_log .' |
';
}
$output .= ' | '. $prev_link .' | | '. $next_link .' |
';
$output .= _diff_table_body($old_node, $new_node);
$output .= '
';
$output .= '
';
if ($node->vid == $new_vid) {
$output .= ''. t('Current revision:') .'
';
}
else {
$output .= ''. t('Revision of !new_date:', array('!new_date' => format_date($new_node->revision_timestamp))) .'
';
}
// Don't include node links (final argument) when viewing the diff.
$output .= node_view($new_node, FALSE, FALSE, FALSE);
return $output;
}
/**
* Create the table body of the diff between $old_node and $new_node.
* The result is a html table part enclosed in tags.
*/
function _diff_table_body(&$old_node, &$new_node) {
drupal_add_css(drupal_get_path('module', 'diff') .'/diff.css', 'module', 'all', FALSE);
include_once('DiffEngine.php');
include_once('node.inc');
if (module_exists('upload')) {
include_once('upload.inc');
}
if (module_exists('content')) {
include_once('cck.inc');
}
$output = '';
$any_visible_change = false;
$node_diffs = module_invoke_all('diff', $old_node, $new_node);
foreach($node_diffs as $node_diff) {
$diff = new Diff($node_diff['old'], $node_diff['new']);
$formatter = new TableDiffFormatter();
if (isset($node_diff['format'])) {
$formatter->show_header = $node_diff['format']['show_header'];
}
$formatter_output = $formatter->format($diff);
if ($formatter_output) {
$output .= ''. t('Changes to %name', array('%name' => $node_diff['name'])) .' |
';
$output .= $formatter_output;
$any_visible_change = true;
}
}
if (!$any_visible_change) {
$output .= '' .t('No visible changes') .' |
';
}
$output .= '';
return $output;
}
/**
* Get the entry in the revisions list after 'vid'.
* Returns false if 'vid' is the last entry.
*/
function _diff_get_next_vid(&$node_revisions, $vid) {
$previous = NULL;
foreach($node_revisions as $revision) {
if ($revision->vid == $vid) {
return ($previous ? $previous->vid : false);
}
$previous = $revision;
}
return false;
}
/**
* Get the entry in the revision list before 'vid'.
* Returns false if 'vid' is the first entry.
*/
function _diff_get_previous_vid(&$node_revisions, $vid) {
$previous = NULL;
foreach($node_revisions as $revision) {
if ($previous && $previous->vid == $vid) {
return $revision->vid;
}
$previous = $revision;
}
return false;
}
/**
* Implementation of hook_form_alter().
* Used to add a 'Preview changes' button on the node edit form.
*/
function diff_form_alter($form_id, &$form) {
if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
if (user_access('view revisions') && $form['nid']['#value'] > 0) {
$form['preview_changes'] = array('#type' => 'button', '#value' => t('Preview changes'), '#weight' => 41);
// Change the form render callback to display the new button
$form['#theme'] = 'diff_node_form';
// Hijack the callback to handle showing of the diff if requested.
$form['#after_build'] = array('diff_node_form_add_changes');
}
}
}
/**
* A copy of the 'theme_node_form' function with the addition of the new button
* to show a diff of the changes.
*/
function theme_diff_node_form($form) {
$output = "\n\n";
return $output;
}
/**
* Callback for node edit form to prepend the changes or preview to the form.
*/
function diff_node_form_add_changes($form) {
global $form_values;
$op = isset($form_values['op']) ? $form_values['op'] : '';
if ($op == t('Preview changes')) {
$node = (object)$form_values;
$changes = '';
$changes .= _diff_table_body(node_load($form_values['nid']), $node);
$changes .= '
';
$form['#prefix'] = isset($form['#prefix']) ? $changes . $form['#prefix'] : $changes;
return $form;
}
else {
// call original function from node module
return node_form_add_preview($form);
}
}