View source
<?php
define('REVISION_LIST_SIZE', 50);
function diff_help($section) {
switch ($section) {
case 'admin/help#diff':
$output = '<p>' . 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'),
)) . '</p>';
return $output;
}
}
function diff_requirements($phase) {
if ($phase == 'install') {
return;
}
$modules = array_keys(module_list());
if (array_search('diff', $modules) <= array_search('node', $modules)) {
diff_autoadjust();
}
}
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;
}
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}");
}
function diff_autoadjust() {
$modules = array_keys(module_list());
if (array_search('diff', $modules) <= array_search('node', $modules)) {
module_load_install('diff');
diff_set_weight();
}
}
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:
return node_revisions();
break;
}
}
drupal_not_found();
}
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;
}
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) {
$page = isset($_GET['page']) ? $_GET['page'] : '0';
$revision_chunks = array_chunk(node_revision_list($node), REVISION_LIST_SIZE);
$revisions = $revision_chunks[$page];
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 != '' ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : ''),
);
}
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 != '' ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : ''),
);
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"),
);
}
$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;
}
function theme_diff_node_revisions($form) {
$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])) {
$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 {
$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;
}
function diff_node_revisions_submit($form_id, $form_values) {
$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;
}
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.'));
}
}
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);
$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 != '' ? '<p class="revision-log">' . filter_xss($old_node->log) . '</p>' : '';
$new_log = $new_node->log != '' ? '<p class="revision-log">' . filter_xss($new_node->log) . '</p>' : '';
$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 .= '<div class="diff-section-title">' . t('Current revision:') . '</div>';
}
else {
$output .= '<div class="diff-section-title">' . t('Revision of !new_date:', array(
'!new_date' => format_date($new_node->revision_timestamp),
)) . '</div>';
}
$output .= node_view($new_node, FALSE, FALSE, FALSE);
return $output;
}
function _diff_body_rows(&$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('taxonomy')) {
include_once 'taxonomy.inc';
}
if (module_exists('upload')) {
include_once 'upload.inc';
}
if (module_exists('content')) {
include_once 'cck.inc';
}
$rows = array();
$any_visible_change = false;
$node_diffs = module_invoke_all('diff', $old_node, $new_node);
$node_diffs['#sorted'] = TRUE;
$count = 0;
foreach (element_children($node_diffs) as $key) {
if (!isset($node_diffs[$key]['#weight'])) {
$node_diffs[$key]['#weight'] = $count / 1000;
}
else {
unset($node_diffs['#sorted']);
}
$count++;
}
if (!isset($node_diffs['#sorted'])) {
uasort($node_diffs, "_element_sort");
}
foreach ($node_diffs as $node_diff) {
$diff = new Diff($node_diff['#old'], $node_diff['#new']);
$formatter = new DrupalDiffFormatter();
if (isset($node_diff['#format'])) {
$formatter->show_header = $node_diff['#format']['show_header'];
}
$diff_rows = $formatter
->format($diff);
if (count($diff_rows)) {
$rows[] = array(
array(
'data' => t('Changes to %name', array(
'%name' => $node_diff['#name'],
)),
'class' => 'diff-section-title',
'colspan' => 4,
),
);
$rows = array_merge($rows, $diff_rows);
$any_visible_change = true;
}
}
if (!$any_visible_change) {
$rows[] = array(
array(
'data' => t('No visible changes'),
'class' => 'diff-section-title',
'colspan' => 4,
),
);
$rows[] = array(
array(
'data' => '',
),
array(
'data' => '',
),
array(
'data' => '',
),
array(
'data' => '',
),
);
}
return $rows;
}
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;
}
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;
}
function diff_form_alter($form_id, &$form) {
if (isset($form['type']) && $form['type']['#value'] . '_node_form' == $form_id) {
if (variable_get('show_preview_changes_' . $form['type']['#value'], TRUE) && $form['nid']['#value'] > 0) {
$form['preview_changes'] = array(
'#type' => 'button',
'#value' => t('Preview changes'),
'#weight' => 41,
);
$form['#theme'] = 'diff_node_form';
if (isset($form['#after_build']) && is_array($form['#after_build'])) {
$form['#after_build'][] = 'diff_node_form_add_changes';
}
else {
$form['#after_build'] = array(
'diff_node_form_add_changes',
);
}
}
}
elseif ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
$form['workflow']['show_preview_changes'] = array(
'#type' => 'checkbox',
'#title' => t('Show %preview_changes button on node edit form', array(
'%preview_changes' => t('Preview changes'),
)),
'#prefix' => '<strong>' . t('Preview changes') . '</strong>',
'#weight' => 10,
'#default_value' => variable_get('show_preview_changes_' . $form['#node_type']->type, TRUE),
);
}
}
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;
$rows = _diff_body_rows(node_load($form_values['nid']), $node);
$cols = array(
array(
array(
'class' => 'diff-marker',
),
array(
'class' => 'diff-content',
),
array(
'class' => 'diff-marker',
),
array(
'class' => 'diff-content',
),
),
);
$changes = theme('diff_table', array(), $rows, array(
'class' => 'diff',
), NULL, $cols);
$form['#prefix'] = isset($form['#prefix']) ? $changes . $form['#prefix'] : $changes;
}
return $form;
}
function theme_diff_node_form($form) {
$output = "\n<div class=\"node-form\">\n";
$admin = '';
if (isset($form['author'])) {
$admin .= " <div class=\"authored\">\n";
$admin .= drupal_render($form['author']);
$admin .= " </div>\n";
}
if (isset($form['options'])) {
$admin .= " <div class=\"options\">\n";
$admin .= drupal_render($form['options']);
$admin .= " </div>\n";
}
$buttons = drupal_render($form['preview']);
$buttons .= isset($form['preview_changes']) ? drupal_render($form['preview_changes']) : '';
$buttons .= drupal_render($form['submit']);
$buttons .= isset($form['delete']) ? drupal_render($form['delete']) : '';
$output .= " <div class=\"standard\">\n";
$output .= drupal_render($form);
$output .= " </div>\n";
if (!empty($admin)) {
$output .= " <div class=\"admin\">\n";
$output .= $admin;
$output .= " </div>\n";
}
$output .= $buttons;
$output .= "</div>\n";
return $output;
}
function theme_diff_table($header, $rows, $attributes = array(), $caption = NULL, $cols = array()) {
$output = '<table' . drupal_attributes($attributes) . ">\n";
if (isset($caption)) {
$output .= '<caption>' . $caption . "</caption>\n";
}
if (count($cols)) {
foreach ($cols as $number => $col) {
$attributes = array();
if (isset($col['data'])) {
foreach ($col as $key => $value) {
if ($key == 'data') {
$cells = $value;
}
else {
$attributes[$key] = $value;
}
}
}
else {
$cells = $col;
}
if (is_array($cells) && count($cells)) {
$output .= ' <colgroup' . drupal_attributes($attributes) . '>';
$i = 0;
foreach ($cells as $cell) {
$output .= ' <col' . drupal_attributes($cell) . ' />';
}
$output .= " </colgroup>\n";
}
else {
$output .= ' <colgroup' . drupal_attributes($attributes) . " />\n";
}
}
}
if (count($header)) {
$ts = tablesort_init($header);
$output .= ' <thead><tr>';
foreach ($header as $cell) {
$cell = tablesort_header($cell, $header, $ts);
$output .= _theme_table_cell($cell, TRUE);
}
$output .= " </tr></thead>\n";
}
$output .= "<tbody>\n";
if (count($rows)) {
$flip = array(
'even' => 'odd',
'odd' => 'even',
);
$class = 'even';
foreach ($rows as $number => $row) {
$attributes = array();
if (isset($row['data'])) {
foreach ($row as $key => $value) {
if ($key == 'data') {
$cells = $value;
}
else {
$attributes[$key] = $value;
}
}
}
else {
$cells = $row;
}
$class = $flip[$class];
if (isset($attributes['class'])) {
$attributes['class'] .= ' ' . $class;
}
else {
$attributes['class'] = $class;
}
$output .= ' <tr' . drupal_attributes($attributes) . '>';
$i = 0;
foreach ($cells as $cell) {
$cell = tablesort_cell($cell, $header, $ts, $i++);
$output .= _theme_table_cell($cell);
}
$output .= " </tr>\n";
}
}
$output .= "</tbody></table>\n";
return $output;
}