You are here

diff.module in Diff 6

Provides functionality to show a diff between two node revisions.

File

diff.module
View source
<?php

/**
 * @file
 * Provides functionality to show a diff between two node revisions.
 */

/**
 * Number of items on one page of the revision list.
 */
define('REVISION_LIST_SIZE', 50);

/**
 * Implementation of hook_help().
 */
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;
  }
}

/**
 * Implementation of hook_menu()
 * The menu path 'node/$nid/revisions' is overriden with 'diff_diffs'.
 */
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;
}

/**
 * Implementation of hook_menu_alter().
 */
function diff_menu_alter(&$callbacks) {

  // Overwrite the default 'Revisions' page
  $callbacks['node/%node/revisions']['page callback'] = 'diff_diffs_overview';
  $callbacks['node/%node/revisions']['module'] = 'diff';
  unset($callbacks['node/%node/revisions']['file']);
}

/**
 * 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($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) {

    // 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 != '' ? '<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"),
        );
      }

      // 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) {
  $output = '';

  // 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, &$form_state) {

  // the ids are ordered so the old revision is always on the left
  $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;
}

/**
 * Validation for input form to select two revisions.
 */
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.'));
  }
}

/**
 * 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) {

  // Set same title as on the 'Revisions' tab for consistency
  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);

  // 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 != '' ? '<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>' : '';

  // 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 = _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>';
  }

  // Don't include node links (final argument) when viewing the diff.
  $output .= node_view($new_node, FALSE, FALSE, FALSE);
  return $output;
}

/**
 * Creates an array of rows which represent a diff between $old_node and $new_node.
 * The rows can be used via theme('diff_table') to be displayed.
 *
 * @param $old_node
 *   Node for comparison which will be displayed on the left side.
 * @param $new_node
 *   Node for comparison which will be displayed on the right side.
 */
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);

  // We start off assuming all form elements are in the correct order.
  $node_diffs['#sorted'] = TRUE;

  // Recurse through all child elements.
  $count = 0;
  foreach (element_children($node_diffs) as $key) {

    // Assign a decimal placeholder weight to preserve original array order.
    if (!isset($node_diffs[$key]['#weight'])) {
      $node_diffs[$key]['#weight'] = $count / 1000;
    }
    else {

      // If one of the child elements has a weight then we will need to sort
      // later.
      unset($node_diffs['#sorted']);
    }
    $count++;
  }

  // One of the children has a #weight.
  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,
      ),
    );

    // Needed to keep safari happy
    $rows[] = array(
      array(
        'data' => '',
      ),
      array(
        'data' => '',
      ),
      array(
        'data' => '',
      ),
      array(
        'data' => '',
      ),
    );
  }
  return $rows;
}

/**
 * Get the entry in the revisions list after $vid.
 * Returns false if $vid is the last entry.
 *
 * @param $node_revisions
 *   Array of node revision IDs in descending order.
 * @param $vid
 *   Version ID to look for.
 */
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.
 *
 * @param $node_revisions
 *   Array of node revision IDs in descending order.
 * @param $vid
 *   Version ID to look for.
 */
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, $form_state, $form_id) {
  if (isset($form['type']['#value']) && $form['type']['#value'] . '_node_form' == $form_id) {

    // Node editing form.
    // Add a 'Preview changes' button.
    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'])) {

    // Node type edit form.
    // Add checkbox to activate 'Preview changes' button per node 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),
    );
  }
}

/**
 * Callback if 'Preview changes' is pressed.
 */
function diff_node_form_build_preview_changes($form, &$form_state) {
  $node = node_form_submit_build_node($form, $form_state);

  // Create diff of old node and edited node
  $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);

  // Prepend diff to edit form
  $form_state['node_preview'] = isset($form_state['node_preview']) ? $changes . $form_state['node_preview'] : $changes;
}

/**
 * Theme functions
 */

/**
 * 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<div class=\"node-form\">\n";

  // Admin form fields and submit buttons must be rendered first, because
  // they need to go to the bottom of the form, and so should not be part of
  // the catch-all call to drupal_render().
  $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']) : '';

  // Everything else gets rendered here, and is displayed before the admin form
  // field and the submit buttons.
  $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;
}

/**
 * Return a themed table. This is a modified version of theme_table, adding
 * colgroup tag and col tag options.
 *
 * @param $header
 *   An array containing the table headers. Each element of the array can be
 *   either a localized string or an associative array with the following keys:
 *   - "data": The localized title of the table column.
 *   - "field": The database field represented in the table column (required if
 *     user is to be able to sort on this column).
 *   - "sort": A default sort order for this column ("asc" or "desc").
 *   - Any HTML attributes, such as "colspan", to apply to the column header cell.
 * @param $rows
 *   An array of table rows. Every row is an array of cells, or an associative
 *   array with the following keys:
 *   - "data": an array of cells
 *   - Any HTML attributes, such as "class", to apply to the table row.
 *
 *   Each cell can be either a string or an associative array with the following keys:
 *   - "data": The string to display in the table cell.
 *   - "header": Indicates this cell is a header.
 *   - Any HTML attributes, such as "colspan", to apply to the table cell.
 *
 *   Here's an example for $rows:
 *   @verbatim
 *   $rows = array(
 *     // Simple row
 *     array(
 *       'Cell 1', 'Cell 2', 'Cell 3'
 *     ),
 *     // Row with attributes on the row and some of its cells.
 *     array(
 *       'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky'
 *     )
 *   );
 *   @endverbatim
 *
 * @param $attributes
 *   An array of HTML attributes to apply to the table tag.
 * @param $caption
 *   A localized string to use for the <caption> tag.
 * @param $cols
 *   An array of table colum groups. Every column group is an array of columns,
 *   or an associative array with the following keys:
 *   - "data": an array of cells
 *   - Any HTML attributes, such as "class", to apply to the table column group.
 *
 *   Each column can be either an empty array or associative array with the following keys:
 *   - Any HTML attributes, such as "class", to apply to the table column group.
 *
 *   Here's an example for $cols:
 *   @verbatim
 *   $cols = array(
 *     // Simple colgroup.
 *     array(),
 *     // Simple colgroup with attributes.
 *     array(
 *       'data'  => array(), 'colspan' => 2, 'style' => 'color: green;',
 *     ),
 *     // Simple colgroup with one col.
 *     array(
 *       array(),
 *     ),
 *     // Colgroup with attributes on the colgroup and some of its cols.
 *     array(
 *       'data'  => array(array('class' => 'diff-marker'), array('colspan' => 2)), 'class' => 'funky',
 *     ),
 *   );
 *   @endverbatim
 *
 *   The HTML will look as follows:
 *   @verbatim
 *   <table>
 *     <!-- Simple colgroup. -->
 *     <colgroup />
 *
 *     <!-- Simple colgroup with attributes. -->
 *     <colgroup colspan="2" style="color: green;" />
 *
 *     <!-- Simple colgroup with one col. -->
 *     <colgroup>
 *       <col />
 *     </colgroup>
 *
 *     <!-- Colgroup with attributes on the colgroup and some of its cols. -->
 *     <colgroup class="funky">
 *       <col class="diff-marker" />
 *       <col colspan="2" />
 *     </colgroup>
 *     ...
 *   </table>
 *   @endverbatim
 *
 * @return
 *   An HTML string representing the table.
 */
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";
  }

  // Format the table columns:
  if (count($cols)) {
    foreach ($cols as $number => $col) {
      $attributes = array();

      // Check if we're dealing with a simple or complex column
      if (isset($col['data'])) {
        foreach ($col as $key => $value) {
          if ($key == 'data') {
            $cells = $value;
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $cells = $col;
      }

      // Build colgroup
      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";
      }
    }
  }

  // Format the table header:
  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";
  }

  // Format the table rows:
  $output .= "<tbody>\n";
  if (count($rows)) {
    $flip = array(
      'even' => 'odd',
      'odd' => 'even',
    );
    $class = 'even';
    foreach ($rows as $number => $row) {
      $attributes = array();

      // Check if we're dealing with a simple or complex row
      if (isset($row['data'])) {
        foreach ($row as $key => $value) {
          if ($key == 'data') {
            $cells = $value;
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $cells = $row;
      }

      // Add odd/even class
      $class = $flip[$class];
      if (isset($attributes['class'])) {
        $attributes['class'] .= ' ' . $class;
      }
      else {
        $attributes['class'] = $class;
      }

      // Build row
      $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;
}

/**
 * Theme function for a header line in the diff.
 */
function theme_diff_header_line($lineno) {
  return '<strong>' . t('Line %lineno', array(
    '%lineno' => $lineno,
  )) . '</strong>';
}

/**
 * Theme function for a content line in the diff.
 */
function theme_diff_content_line($line) {
  return '<div>' . $line . '</div>';
}

/**
 * Theme function for an empty line in the diff.
 */
function theme_diff_empty_line($line) {
  return $line;
}

/**
 * Implementation of hook_theme().
 */
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,
      ),
    ),
  );
}

/**
 * Helper function to create default 'cols' array for diff table.
 */
function _diff_default_cols() {
  return array(
    array(
      array(
        'class' => 'diff-marker',
      ),
      array(
        'class' => 'diff-content',
      ),
      array(
        'class' => 'diff-marker',
      ),
      array(
        'class' => 'diff-content',
      ),
    ),
  );
}

/**
 * Helper function to create default 'header' array for diff table.
 */
function _diff_default_header($old_header = '', $new_header = '') {
  return array(
    array(
      'data' => $old_header,
      'colspan' => 2,
    ),
    array(
      'data' => $new_header,
      'colspan' => 2,
    ),
  );
}

Functions

Namesort descending Description
diff_diffs_overview Generate an overview table of older revisions of a node and provide an input form to select two revisions for a comparison.
diff_diffs_show Create output string for a comparison of 'node' between versions 'old_vid' and 'new_vid'.
diff_form_alter Implementation of hook_form_alter(). Used to add a 'Preview changes' button on the node edit form.
diff_help Implementation of hook_help().
diff_menu Implementation of hook_menu() The menu path 'node/$nid/revisions' is overriden with 'diff_diffs'.
diff_menu_alter Implementation of hook_menu_alter().
diff_node_form_build_preview_changes Callback if 'Preview changes' is pressed.
diff_node_revisions Input form to select two revisions.
diff_node_revisions_submit Submit code for input form to select two revisions.
diff_node_revisions_validate Validation for input form to select two revisions.
diff_theme Implementation of hook_theme().
theme_diff_content_line Theme function for a content line in the diff.
theme_diff_empty_line Theme function for an empty line in the diff.
theme_diff_header_line Theme function for a header line in the diff.
theme_diff_node_form A copy of the 'theme_node_form' function with the addition of the new button to show a diff of the changes.
theme_diff_node_revisions Theme function to display the revisions formular with means to select two revisions.
theme_diff_table Return a themed table. This is a modified version of theme_table, adding colgroup tag and col tag options.
_diff_body_rows Creates an array of rows which represent a diff between $old_node and $new_node. The rows can be used via theme('diff_table') to be displayed.
_diff_default_cols Helper function to create default 'cols' array for diff table.
_diff_default_header Helper function to create default 'header' array for diff table.
_diff_get_next_vid Get the entry in the revisions list after $vid. Returns false if $vid is the last entry.
_diff_get_previous_vid Get the entry in the revision list before $vid. Returns false if $vid is the first entry.

Constants

Namesort descending Description
REVISION_LIST_SIZE Number of items on one page of the revision list.