'. 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('View changes'), '%view_revisions' => t('view revisions'))) .'
'; return $output; case 'node/%/revisions/%/view': // the following string is copied from string copied from node_help('node/%/revisions') return ''. t('The revisions let you track differences between multiple versions of a post.') .'
'; case 'node/%/revisions/view/%/%': return ''. t('Comparing two revisions:') .'
'; } } /** * Implementation of hook_menu() */ function diff_menu() { $items = array(); /** * By using MENU_LOCAL_TASK (and 'tab_parent') we can get the various revision-views to * show the View|Edit|Revision-tabs of the node on top, and have the Revisions-tab open. * To avoid creating/showing any extra tabs or sub-tabs (tasks below top level) for the * various paths (i.e. "Diff", "Show latest" and "Show a specific revision") that need * a revision-id (vid) parameter, we make sure to set 'tab_parent' a bit odd. * This solution may not be the prettiest one, but by avoiding having two _LOCAL_TASKs * sharing a parent that can be accessed by its full path, it seems to work as desired. * Breadcrumbs work decently, at least the node link is among the crumbs. For some reason * any breadcrumbs "before/above" the node is only seen at 'node/%node/revisions/%/view'. */ $items['node/%node/revisions/list'] = array( // Not used directly, but was created to get the other menu items to work well 'title' => 'List revisions', 'page callback' => 'diff_diffs_overview', 'type' => MENU_DEFAULT_LOCAL_TASK, 'access callback' => 'diff_node_revision_access', 'access arguments' => array(1), 'file' => 'diff.pages.inc', ); $items['node/%node/revisions/view/%/%'] = array( 'title' => 'Diff', 'page callback' => 'diff_diffs_show', 'page arguments' => array(1, 4, 5), 'type' => MENU_LOCAL_TASK, 'access callback' => 'diff_node_revision_access', 'access arguments' => array(1), 'tab_parent' => 'node/%/revisions/list', 'file' => 'diff.pages.inc', ); $items['node/%node/revisions/view/latest'] = array( 'title' => 'Show latest diff', 'page callback' => 'diff_latest', 'page arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'access callback' => 'diff_node_revision_access', 'access arguments' => array(1), 'tab_parent' => 'node/%/revisions/view', 'file' => 'diff.pages.inc', ); return $items; } /** * Menu callback - show latest diff for a given node. */ function diff_latest($node) { $revisions = node_revision_list($node); $new = array_shift($revisions); $old = array_shift($revisions); drupal_goto("node/{$node->nid}/revisions/view/{$old->vid}/{$new->vid}"); } /** * 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'; $callbacks['node/%node/revisions']['file'] = 'diff.pages.inc'; $callbacks['node/%node/revisions/%/view']['tab_parent'] = 'node/%/revisions/list'; $callbacks['node/%node/revisions/%/revert']['tab_parent'] = 'node/%/revisions/%/view'; $callbacks['node/%node/revisions/%/delete']['tab_parent'] = 'node/%/revisions/%/view'; $callbacks['node/%node/revisions']['access callback'] = $callbacks['node/%node/revisions/%/view']['access callback'] = $callbacks['node/%node/revisions/%/revert']['access callback'] = $callbacks['node/%node/revisions/%/delete']['access callback'] = 'diff_node_revision_access'; return; } /** * Access callback for the node revisions page. */ function diff_node_revision_access($node) { $may_revision_this_type = variable_get('enable_revisions_page_'. $node->type, TRUE) || user_access('administer nodes'); return $may_revision_this_type && _node_revision_access($node); } /** * Implementation of hook_block(). */ function diff_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': return array('inline' => array('info' => t('Diff: inline diff revisions'))); case 'view': switch ($delta) { case 'inline': $block = array(); if (!empty($_SESSION['diff_inline_highlight'])) { $node = menu_get_object(); $revisions = node_revision_list($node); if (count($revisions) > 1) { $block['subject'] = t('Revisions'); $block['content'] = theme('diff_inline_revisions', $node, $revisions); } } return $block; } break; } } /** * Implementation of hook_nodeapi(). */ function diff_nodeapi(&$node, $op, $teaser, $page) { if ($page && $op == 'view' && user_access('view revisions') && variable_get('show_diff_inline_'. $node->type, FALSE)) { // Set the hilight flag if specified in the URL if (isset($_GET['diff'])) { $_SESSION['diff_inline_highlight'] = !empty($_GET['diff']); } $vids = array_keys(node_revision_list($node)); // Only render inline diff if there are multiple revisions if (count($vids) > 1) { // Set the active vid before we blow this information away from others by menu_set_active_item(). _diff_inline_set_vid($node->vid); // @TODO: This is a bad hack that should instead be a patch against the node module. menu_set_active_item("node/{$node->nid}"); // Add inline controls. $node->content['diff_inline'] = array('#value' => theme('diff_inline_controls', $node), '#weight' => -100); // Retrieve the vid immediately antecedent to the current vid. $position = array_search($node->vid, $vids) + 1; $old = isset($vids[$position]) ? $vids[$position] : NULL; // Only highlight changes if inline highlighting is enabled & there // is actually an older revision to diff against. if ($old && !empty($_SESSION['diff_inline_highlight'])) { module_load_include('inc', 'diff', 'diff.pages'); $node->content['body']['#value'] = diff_inline_diff($node, $old); } } } } /** * Implementation of hook_form_alter(). */ function diff_form_alter(&$form, $form_state, $form_id) { if (isset($form['type']['#value']) && $form['type']['#value'] .'_node_form' == $form_id) { // Add a 'View changes' button on the node edit form. if (variable_get('show_preview_changes_'. $form['type']['#value'], TRUE) && $form['nid']['#value'] > 0) { $form['buttons']['preview_changes'] = array( '#type' => 'submit', '#value' => t('View 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 'View 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('View changes'))), '#prefix' => ''. t('View changes') .'', '#weight' => 10, '#default_value' => variable_get('show_preview_changes_'. $form['#node_type']->type, TRUE), ); // Add checkbox to present inline diff forms and highlighting to users. $form['workflow']['show_diff_inline'] = array( '#type' => 'checkbox', '#title' => t('Show diffs inline for this content type'), '#weight' => 10, '#prefix' => "". t('Inline diffs') ."", '#default_value' => variable_get('show_diff_inline_'. $form['#node_type']->type, FALSE), ); // Add checkbox to enable 'Revisions' page per node type. $form['workflow']['enable_revisions_page'] = array( '#type' => 'checkbox', '#title' => t('Enable the %revisions page for this content type', array('%revisions' => t('Revisions'))), '#prefix' => ''. t('Enable revisions page') .'', '#weight' => 11, '#default_value' => variable_get('enable_revisions_page_'. $form['#node_type']->type, TRUE), ); } } /** * Callback if 'View changes' is pressed. */ function diff_node_form_build_preview_changes($form, &$form_state) { module_load_include('inc', 'diff', 'diff.pages'); $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; } /** * Helper static cache to set/retrieve the current active vid. */ function _diff_inline_set_vid($vid = NULL) { static $active; if (!empty($vid)) { $active = $vid; } return $active; } /** * Implementation of hook_theme(). */ function diff_theme() { return array( 'diff_node_revisions' => array( 'arguments' => array('form' => NULL), 'file' => 'diff.theme.inc', ), 'diff_table' => array( 'arguments' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'cols' => array()), 'file' => 'diff.theme.inc', ), 'diff_header_line' => array( 'arguments' => array('lineno' => NULL), 'file' => 'diff.theme.inc', ), 'diff_content_line' => array( 'arguments' => array('line' => NULL), 'file' => 'diff.theme.inc', ), 'diff_empty_line' => array( 'arguments' => array('line' => NULL), 'file' => 'diff.theme.inc', ), 'diff_inline_controls' => array( 'arguments' => array('info' => NULL), 'file' => 'diff.theme.inc', ), 'diff_inline_revisions' => array( 'arguments' => array('form' => NULL), 'file' => 'diff.theme.inc', ), 'diff_inline_chunk' => array( 'arguments' => array('text' => '', 'type' => NULL), 'file' => 'diff.theme.inc', ), ); }