View source
<?php
define('REVISION_LIST_SIZE', 50);
function diff_help($path, $arg) {
switch ($path) {
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_menu() {
$items = array();
$items['node/%node/revisions/view/%/%'] = array(
'title' => 'Diff',
'page callback' => 'diff_diffs_show',
'page arguments' => array(
1,
4,
5,
),
'type' => MENU_CALLBACK,
'access callback' => '_node_revision_access',
'access arguments' => array(
1,
),
);
return $items;
}
function diff_menu_alter(&$callbacks) {
$callbacks['node/%node/revisions']['page callback'] = 'diff_diffs_overview';
$callbacks['node/%node/revisions']['module'] = 'diff';
unset($callbacks['node/%node/revisions']['file']);
}
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($form_state, &$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) {
$output = '';
$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, &$form_state) {
$old_vid = min($form_state['values']['old'], $form_state['values']['new']);
$new_vid = max($form_state['values']['old'], $form_state['values']['new']);
$form_state['redirect'] = 'node/' . $form_state['values']['nid'] . '/revisions/view/' . $old_vid . '/' . $new_vid;
}
function diff_node_revisions_validate($form, &$form_state) {
$old_vid = $form_state['values']['old'];
$new_vid = $form_state['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) {
drupal_set_title(t('Revisions for %title', array(
'%title' => $node->title,
)));
$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 = _diff_default_cols();
$header = _diff_default_header($old_header, $new_header);
$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, $form_state, $form_id) {
if (isset($form['type']['#value']) && $form['type']['#value'] . '_node_form' == $form_id) {
if (variable_get('show_preview_changes_' . $form['type']['#value'], TRUE) && $form['nid']['#value'] > 0) {
$form['buttons']['preview_changes'] = array(
'#type' => 'submit',
'#value' => t('Preview changes'),
'#weight' => 12,
'#submit' => array(
'diff_node_form_build_preview_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_build_preview_changes($form, &$form_state) {
$node = node_form_submit_build_node($form, $form_state);
$rows = _diff_body_rows(node_load($form_state['values']['nid']), $node);
$cols = _diff_default_cols();
$header = _diff_default_header();
$changes = theme('diff_table', $header, $rows, array(
'class' => 'diff',
), NULL, $cols);
$form_state['node_preview'] = isset($form_state['node_preview']) ? $changes . $form_state['node_preview'] : $changes;
}
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;
}
function theme_diff_header_line($lineno) {
return '<strong>' . t('Line %lineno', array(
'%lineno' => $lineno,
)) . '</strong>';
}
function theme_diff_content_line($line) {
return '<div>' . $line . '</div>';
}
function theme_diff_empty_line($line) {
return $line;
}
function diff_theme() {
return array(
'diff_node_revisions' => array(
'arguments' => array(
'form' => NULL,
),
),
'diff_table' => array(
'arguments' => array(
'header' => NULL,
'rows' => NULL,
'attributes' => array(),
'caption' => NULL,
'cols' => array(),
),
),
'diff_header_line' => array(
'arguments' => array(
'lineno' => NULL,
),
),
'diff_content_line' => array(
'arguments' => array(
'line' => NULL,
),
),
'diff_empty_line' => array(
'arguments' => array(
'line' => NULL,
),
),
);
}
function _diff_default_cols() {
return array(
array(
array(
'class' => 'diff-marker',
),
array(
'class' => 'diff-content',
),
array(
'class' => 'diff-marker',
),
array(
'class' => 'diff-content',
),
),
);
}
function _diff_default_header($old_header = '', $new_header = '') {
return array(
array(
'data' => $old_header,
'colspan' => 2,
),
array(
'data' => $new_header,
'colspan' => 2,
),
);
}