Index: modules/system/system.install =================================================================== --- modules/system/system.install (revision 3) +++ modules/system/system.install (working copy) @@ -358,9 +358,12 @@ function system_install() { db_query("CREATE TABLE {node} ( nid int unsigned NOT NULL auto_increment, vid int unsigned NOT NULL default '0', + uid int NOT NULL default '0', type varchar(32) NOT NULL default '', title varchar(128) NOT NULL default '', - uid int NOT NULL default '0', + body longtext NOT NULL, + teaser longtext NOT NULL, + format int NOT NULL default '0', status int NOT NULL default '1', created int NOT NULL default '0', changed int NOT NULL default '0', @@ -833,9 +836,12 @@ function system_install() { db_query("CREATE TABLE {node} ( nid serial CHECK (nid >= 0), vid int_unsigned NOT NULL default '0', + uid int NOT NULL default '0', type varchar(32) NOT NULL default '', title varchar(128) NOT NULL default '', - uid int NOT NULL default '0', + body text NOT NULL default '', + teaser text NOT NULL default '', + format int NOT NULL default '0', status int NOT NULL default '1', created int NOT NULL default '0', changed int NOT NULL default '0', @@ -874,9 +880,9 @@ function system_install() { title varchar(128) NOT NULL default '', body text NOT NULL default '', teaser text NOT NULL default '', + format int NOT NULL default '0', log text NOT NULL default '', timestamp int NOT NULL default '0', - format int NOT NULL default '0', PRIMARY KEY (vid) )"); db_query("CREATE INDEX {node_revisions}_nid_idx ON {node_revisions} (nid)"); @@ -3579,6 +3585,37 @@ function system_update_2001() { } /** + * Make node revisions optional. + */ +function system_update_2002() { + $ret = array(); + + // Migrate over information. We create a new node table because the update + // would be prohibitively slow. + $ret[] = update_sql(' + CREATE TABLE {new_node} AS + SELECT n.nid, n.vid, n.uid, n.type, n.title, r.body, r.teaser, r.format, n.status, n.created, + n.changed, n.promote, n.moderate, n.sticky + FROM {node} n, {node_revisions} r + WHERE n.vid = r.vid + '); + + // TODO: need a pgsql equivalent. + $ret[] = update_sql('ALTER TABLE {node} RENAME {node_old}'); + $ret[] = update_sql('ALTER TABLE {node_new} RENAME {node}'); + + // TODO: If row counts match, drop the old node table. + + // Delete single revisions. + $ret[] = update_sql('DELETE FROM {node_revisions} WHERE nid IN (SELECT nid FROM {node_revisions} GROUP BY nid HAVING COUNT(1) = 1'); + + // Update sequences table. + $ret[] = update_sql("UPDATE {sequences} SET name = '{node}_vid' WHERE name = '{node_revisions}_vid'"); + + return $ret; +} + +/** * @} End of "defgroup updates-5.0-to-x.x" * The next series of updates should start at 3000. */ Index: modules/node/content_types.inc =================================================================== --- modules/node/content_types.inc (revision 3) +++ modules/node/content_types.inc (working copy) @@ -161,7 +161,6 @@ function node_type_form($type = NULL) { 'status' => t('Published'), 'promote' => t('Promoted to front page'), 'sticky' => t('Sticky at top of lists'), - 'revision' => t('Create new revision'), ), '#description' => t('Users with the administer nodes permission will be able to override these options.'), ); Index: modules/node/node.module =================================================================== --- modules/node/node.module (revision 3) +++ modules/node/node.module (working copy) @@ -35,10 +35,6 @@ function node_help($section) { return '

'. t('To create a new content type, enter the human-readable name, the machine-readable name, and all other relevant fields that are on this page. Once created, users of your site will be able to create posts that are instances of this content type.') .'

'; } - if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'revisions') { - return '

'. t('The revisions let you track differences between multiple versions of a post.') .'

'; - } - if (arg(0) == 'node' && arg(1) == 'add' && $type = arg(2)) { $type = node_get_types('type', str_replace('-', '_', arg(2))); return '

'. (isset($type->help) ? filter_xss_admin($type->help) : '') .'

'; @@ -475,20 +471,15 @@ function node_invoke_nodeapi(&$node, $op * * @param $param * Either the nid of the node or an array of conditions to match against in the database query - * @param $revision - * Which numbered revision to load. Defaults to the current version. * @param $reset * Whether to reset the internal node_load cache. * * @return * A fully-populated node object. */ -function node_load($param = array(), $revision = NULL, $reset = NULL) { +function node_load($param = array(), $reset = NULL) { static $nodes = array(); - - if ($reset) { - $nodes = array(); - } + $revision = NULL; $cachable = ($revision == NULL); $arguments = array(); @@ -506,16 +497,18 @@ function node_load($param = array(), $re $arguments[] = $value; } $cond = implode(' AND ', $cond); + if (isset($param['vid'])) { + $revision = $param['vid']; + } } // Retrieve the node. // No db_rewrite_sql is applied so as to get complete indexing for search. if ($revision) { - array_unshift($arguments, $revision); - $node = db_fetch_object(db_query('SELECT n.nid, r.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d WHERE '. $cond, $arguments)); + $node = db_fetch_object(db_query('SELECT n.nid, r.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.nid = n.nid WHERE r.vid = %d', $revision)); } else { - $node = db_fetch_object(db_query('SELECT n.nid, n.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE '. $cond, $arguments)); + $node = db_fetch_object(db_query('SELECT n.nid, n.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.sticky, n.title, n.body, n.teaser, n.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid WHERE '. $cond, $arguments)); } if ($node->nid) { @@ -554,7 +547,6 @@ function node_save(&$node) { $node->is_new = TRUE; $node->nid = db_next_id('{node}_nid'); - $node->vid = db_next_id('{node_revisions}_vid'); } else { // We need to ensure that all node fields are filled. @@ -563,52 +555,35 @@ function node_save(&$node) { $node_current->$field = $data; } $node = $node_current; - - if (!empty($node->revision)) { - $node->old_vid = $node->vid; - $node->vid = db_next_id('{node_revisions}_vid'); - } - } - + } // Set some required fields: if (empty($node->created)) { $node->created = time(); } - // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...) + // The changed timestamp is always updated for bookkeeping purposes + // (revisions, searching, ...) $node->changed = time(); - // Split off revisions data to another structure - $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid, - 'title' => $node->title, 'body' => $node->body, - 'teaser' => $node->teaser, 'timestamp' => $node->changed, - 'uid' => $user->uid, 'format' => $node->format); - $revisions_table_types = array('nid' => '%d', 'vid' => '%d', - 'title' => "'%s'", 'body' => "'%s'", - 'teaser' => "'%s'", 'timestamp' => '%d', - 'uid' => '%d', 'format' => '%d'); - if (!empty($node->log) || $node->is_new) { - // Only store the log message if there's something to store; this prevents - // existing log messages from being unintentionally overwritten by a blank - // message. A new revision will have an empty log message (or $node->log). - $revisions_table_values['log'] = $node->log; - $revisions_table_types['log'] = "'%s'"; - } + // Revision ID. + $node->vid = db_next_id('{node}_vid'); + $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid, - 'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid, + 'title' => $node->title, 'type' => $node->type, + 'body' => $node->body, 'teaser' => $node->teaser, + 'format' => $node->format, 'uid' => $node->uid, 'status' => $node->status, 'created' => $node->created, 'changed' => $node->changed, 'comment' => $node->comment, 'promote' => $node->promote, 'sticky' => $node->sticky); $node_table_types = array('nid' => '%d', 'vid' => '%d', - 'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d', + 'title' => "'%s'", 'type' => "'%s'", 'body' => "'%s'", + 'teaser' => "'%s'", 'format' => "'%s'", 'uid' => '%d', 'status' => '%d', 'created' => '%d', 'changed' => '%d', 'comment' => '%d', 'promote' => '%d', 'sticky' => '%d'); - //Generate the node table query and the - //the node_revisions table query + // Generate the node table query. if ($node->is_new) { $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')'; - $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')'; } else { $arr = array(); @@ -617,22 +592,10 @@ function node_save(&$node) { } $node_table_values[] = $node->nid; $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d'; - if (!empty($node->revision)) { - $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')'; - } - else { - $arr = array(); - foreach ($revisions_table_types as $key => $value) { - $arr[] = $key .' = '. $value; - } - $revisions_table_values[] = $node->vid; - $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d'; - } } // Insert the node into the database: db_query($node_query, $node_table_values); - db_query($revisions_query, $revisions_table_values); // Call the node specific callback (if any): if ($node->is_new) { @@ -775,7 +738,7 @@ function node_show($node, $cid) { * Implementation of hook_perm(). */ function node_perm() { - $perms = array('administer content types', 'administer nodes', 'access content', 'view revisions', 'revert revisions'); + $perms = array('administer content types', 'administer nodes', 'access content'); foreach (node_get_types() as $type) { if ($type->module == 'node') { @@ -939,10 +902,9 @@ function node_search($op = 'search', $ke /** * Implementation of hook_user(). */ -function node_user($op, &$edit, &$user) { +function node_user($op, &$edit, &$account) { if ($op == 'delete') { - db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid); - db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid); + db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $account->uid); } } @@ -1054,10 +1016,6 @@ function node_link($type, $node = NULL, return $links; } -function _node_revision_access($node) { - return (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', $node->nid)) > 1; -} - /** * Implementation of hook_menu(). */ @@ -1191,14 +1149,6 @@ function node_menu() { 'access arguments' => array('delete', 1), 'weight' => 1, 'type' => MENU_CALLBACK); - $items['node/%node/revisions'] = array( - 'title' => t('Revisions'), - 'page callback' => 'node_revisions', - 'access callback' => '_node_revision_access', - 'access arguments' => array(1), - 'weight' => 2, - 'type' => MENU_LOCAL_TASK, - ); return $items; } @@ -1600,127 +1550,6 @@ function node_multiple_delete_confirm_su return 'admin/content/node'; } -/** - * Generate an overview table of older revisions of a node. - */ -function node_revision_overview($node) { - drupal_set_title(t('Revisions for %title', array('%title' => $node->title))); - - $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2)); - - $revisions = node_revision_list($node); - - $rows = array(); - $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) { - $row = array(); - $operations = array(); - - if ($revision->current_vid > 0) { - $row[] = array('data' => 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) .'

' : ''), - 'class' => 'revision-current'); - $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 2); - } - else { - $row[] = 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[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert"); - } - if ($delete_permission) { - $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete"); - } - } - $rows[] = array_merge($row, $operations); - } - $output .= theme('table', $header, $rows); - - return $output; -} - -/** - * Revert to the revision with the specified revision number. A node and nodeapi "update" event is triggered - * (via the node_save() call) when a revision is reverted. - */ -function node_revision_revert($nid, $revision) { - global $user; - - $node = node_load($nid, $revision); - if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { - if ($node->vid) { - $node->revision = 1; - $node->log = t('Copy of the revision from %date.', array('%date' => format_date($node->revision_timestamp))); - if (module_exists('taxonomy')) { - $node->taxonomy = array_keys($node->taxonomy); - } - - node_save($node); - - drupal_set_message(t('%title has been reverted back to the revision from %revision-date', array('%revision-date' => format_date($node->revision_timestamp), '%title' => $node->title))); - watchdog('content', t('@type: reverted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $revision))); - } - else { - drupal_set_message(t('You tried to revert to an invalid revision.'), 'error'); - } - drupal_goto('node/'. $nid .'/revisions'); - } - drupal_access_denied(); -} - -/** - * Delete the revision with specified revision number. A "delete revision" nodeapi event is invoked when a - * revision is deleted. - */ -function node_revision_delete($nid, $revision) { - if (user_access('administer nodes')) { - $node = node_load($nid); - if (node_access('delete', $node)) { - // Don't delete the current revision - if ($revision != $node->vid) { - $node = node_load($nid, $revision); - - db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision); - node_invoke_nodeapi($node, 'delete revision'); - drupal_set_message(t('Deleted %title revision %revision.', array('%title' => $node->title, '%revision' => $revision))); - watchdog('content', t('@type: deleted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $revision))); - } - - else { - drupal_set_message(t('Deletion failed. You tried to delete the current revision.')); - } - if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)) > 1) { - drupal_goto("node/$nid/revisions"); - } - else { - drupal_goto("node/$nid"); - } - } - } - - drupal_access_denied(); -} - -/** - * Return a list of all the existing revision numbers. - */ -function node_revision_list($node) { - $revisions = array(); - $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revisions} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = %d ORDER BY r.timestamp DESC', $node->nid); - while ($revision = db_fetch_object($result)) { - $revisions[] = $revision; - } - - return $revisions; -} - function node_admin_search() { $keys = isset($_POST['keys']) ? $_POST['keys'] : NULL; return drupal_get_form('search_form', url('admin/content/search'), $keys, 'node') . search_data($keys, 'node'); @@ -1973,30 +1802,14 @@ function node_form($node, $form_values = $node_options = variable_get('node_options_'. $node->type, array('status', 'promote')); // If this is a new node, fill in the default values. if (!isset($node->nid)) { - foreach (array('status', 'promote', 'sticky', 'revision') as $key) { + foreach (array('status', 'promote', 'sticky') as $key) { $node->$key = in_array($key, $node_options); } global $user; $node->uid = $user->uid; } - else { - // Nodes being edited should always be preset with the default revision setting. - $node->revision = in_array('revision', $node_options); - } $form['#node'] = $node; - // Add a log field if the "Create new revision" option is checked, or if the - // current user has the ability to check that option. - if ($node->revision || user_access('administer nodes')) { - $form['log'] = array( - '#type' => 'textarea', - '#title' => t('Log message'), - '#rows' => 2, - '#weight' => 20, - '#description' => t('An explanation of the additions or updates being made to help other authors understand your motivations.'), - ); - } - // Node author information for administrators $form['author'] = array( '#type' => 'fieldset', @@ -2025,7 +1838,6 @@ function node_form($node, $form_values = $form['options']['status'] = array('#type' => 'checkbox', '#title' => t('Published'), '#default_value' => $node->status); $form['options']['promote'] = array('#type' => 'checkbox', '#title' => t('Promoted to front page'), '#default_value' => $node->promote); $form['options']['sticky'] = array('#type' => 'checkbox', '#title' => t('Sticky at top of lists'), '#default_value' => $node->sticky); - $form['options']['revision'] = array('#type' => 'checkbox', '#title' => t('Create new revision'), '#default_value' => $node->revision); // These values are used when the user has no administrator access. foreach (array('uid', 'created') as $key) { $form[$key] = array('#type' => 'value', '#value' => $node->$key); @@ -2218,10 +2030,6 @@ function theme_node_preview($node) { return $output; } -function theme_node_log_message($log) { - return '
'. t('Log') .':
'. $log .'
'; -} - function node_form_submit($form_id, $form_values) { global $user; @@ -2284,7 +2092,6 @@ function node_delete($nid) { if (node_access('delete', $node)) { db_query('DELETE FROM {node} WHERE nid = %d', $node->nid); - db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid); // Call the node-specific callback (if any): node_invoke($node, 'delete'); @@ -2303,44 +2110,6 @@ function node_delete($nid) { } /** - * Menu callback for revisions related activities. - */ -function node_revisions() { - if (is_numeric(arg(1)) && arg(2) == 'revisions') { - $op = arg(4) ? arg(4) : 'overview'; - switch ($op) { - case 'overview': - $node = node_load(arg(1)); - if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) { - return node_revision_overview($node); - } - drupal_access_denied(); - return; - case 'view': - if (is_numeric(arg(3))) { - $node = node_load(arg(1), arg(3)); - if ($node->nid) { - if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) { - drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp)))); - return node_show($node, arg(2)); - } - drupal_access_denied(); - return; - } - } - break; - case 'revert': - node_revision_revert(arg(1), arg(3)); - break; - case 'delete': - node_revision_delete(arg(1), arg(3)); - break; - } - } - drupal_not_found(); -} - -/** * Menu callback; Generate a listing of promoted nodes. */ function node_page_default() { Index: modules/node_revisions/node_revisions.info =================================================================== --- modules/node_revisions/node_revisions.info (revision 0) +++ modules/node_revisions/node_revisions.info (revision 0) @@ -0,0 +1,5 @@ +; $Id: node.info,v 1.3 2006/11/21 20:55:34 dries Exp $ +name = Node Revisions +description = Allows revisioning of nodes and related properties. +package = Core - optional +version = VERSION \ No newline at end of file Index: modules/node_revisions/node_revisions.module =================================================================== --- modules/node_revisions/node_revisions.module (revision 0) +++ modules/node_revisions/node_revisions.module (revision 0) @@ -0,0 +1,327 @@ +'. t('The revisions let you track differences between multiple versions of a post.') .'

'; + } +} + +/** + * Implementation of hook_perm(). + */ +function node_revisions_perm() { + return array('view revisions', 'revert revisions'); +} + +/** + * Implementation of hook_menu(). + */ +function node_revisions_menu() { + $items['node/%node/revisions'] = array( + 'title' => t('Revisions'), + 'page callback' => 'node_revisions_page', + 'access callback' => '_node_revisions_access', + 'access arguments' => array(1), + 'weight' => 2, + 'type' => MENU_LOCAL_TASK, + ); + + return $items; +} + +/** + * Menu access helper; only provide access to revisions menu if user has + * adequate permissions and if there are at least two revisions present. + */ +function _node_revisions_access($node) { + return (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', $node->nid)) > 1; +} + +/** + * Implementation of hook_nodeapi(). + */ +function node_revisions_nodeapi(&$node, $op) { + switch ($op) { + case 'load': + // Nodes should always be preset with the default revision setting. + $node->revision = in_array('revision', variable_get('node_options_'. $node->type, array('status', 'promote'))); + break; + case 'submit': + if ($node->revision) { + $node->old_vid = $node->vid; + } + break; + case 'insert': + case 'update': // Intentional fall-through. + // Prepare data for saving. + global $user; + $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid, + 'title' => $node->title, 'body' => $node->body, + 'teaser' => $node->teaser, 'timestamp' => $node->changed, + 'uid' => $user->uid, 'format' => $node->format); + $revisions_table_types = array('nid' => '%d', 'vid' => '%d', + 'title' => "'%s'", 'body' => "'%s'", + 'teaser' => "'%s'", 'timestamp' => '%d', + 'uid' => '%d', 'format' => '%d'); + if (!empty($node->log) || $node->is_new) { + // Only store the log message if there's something to store; this + // prevents existing log messages from being unintentionally overwritten + // by a blank message. A new revision will have an empty log message (or + // $node->log). + $revisions_table_values['log'] = $node->log; + $revisions_table_types['log'] = "'%s'"; + } + + if ($node->is_new || !empty($node->revision)) { + // Insert new revision. + $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')'; + } + else { + // Update existing revision. + $arr = array(); + foreach ($revisions_table_types as $key => $value) { + $arr[] = $key .' = '. $value; + } + $revisions_table_values[] = $node->vid; + $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d'; + } + db_query($revisions_query, $revisions_table_values); + break; + case 'delete': + if (node_access('delete')) { + db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid); + } + break; + } +} + +/** + * Implementation of hook_form_alter(). + */ +function node_revisions_form_alter($form_id, &$form) { + if ($form['#id'] == 'node-form') { + $node = $form['#node']; + if (!isset($node->revision)) { + $node->revision = 0; + } + if (user_access('administer nodes')) { + // Add revision checkbox to node options. + $form['options']['revision'] = array( + '#type' => 'checkbox', + '#title' => t('Create new revision'), + '#default_value' => in_array('revision', variable_get('node_options_'. $node->type, array('status', 'promote'))), + ); + } + else { + $form['options']['revision'] = $node->revision; + } + + // Add a log field if the "Create new revision" option is checked, or if the + // current user has the ability to check that option. + if ($node->revision || user_access('administer nodes')) { + $form['log'] = array( + '#type' => 'textarea', + '#title' => t('Log message'), + '#rows' => 2, + '#weight' => 20, + '#description' => t('An explanation of the additions or updates being made to help other authors understand your motivations.'), + ); + } + } + elseif ($form['#id'] == 'node-type-form') { + // Add revision checkbox to publishing options. + $form['workflow']['node_options']['#options']['revision'] = t('Create new revision'); + } +} + +/** + * Implementation of hook_user(). + */ +function node_revisions_user($op, &$edit, &$account) { + // If user is deleted, set revision author to anonymous. + if ($op == 'delete') { + db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $account->uid); + } +} + +/** + * Menu callback for revisions related activities. + */ +function node_revisions_page() { + if (is_numeric(arg(1)) && arg(2) == 'revisions') { + $op = arg(4) ? arg(4) : 'overview'; + switch ($op) { + case 'overview': + $node = node_load(arg(1)); + if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) { + return node_revisions_overview($node); + } + drupal_access_denied(); + return; + case 'view': + if (is_numeric(arg(3))) { + $node = node_load(array('nid' => arg(1), 'vid' => arg(3))); + if ($node->nid) { + if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) { + drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp)))); + return node_show($node, arg(2)); + } + drupal_access_denied(); + return; + } + } + break; + case 'revert': + node_revisions_revert(arg(1), arg(3)); + break; + case 'delete': + node_revisions_delete(arg(1), arg(3)); + break; + } + } + drupal_not_found(); +} + +/** + * Generate an overview table of older revisions of a node. + */ +function node_revisions_overview($node) { + drupal_set_title(t('Revisions for %title', array('%title' => $node->title))); + + $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2)); + + $revisions = node_revisions_list($node); + + $rows = array(); + $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) { + $row = array(); + $operations = array(); + + if ($revision->current_vid > 0) { + $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), '!username' => theme('username', $revision))) + . (($revision->log != '') ? theme('node_revisions_log_message', filter_xss($revision->log)) : ''), + 'class' => 'revision-current'); + $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 2); + } + else { + $row[] = 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 != '') ? theme('node_revisions_log_message', filter_xss($revision->log)) : ''); + if ($revert_permission) { + $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert"); + } + if ($delete_permission) { + $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete"); + } + } + $rows[] = array_merge($row, $operations); + } + + return theme('table', $header, $rows); +} + +/** + * Revert to the revision with the specified revision number. A node and nodeapi "update" event is triggered + * (via the node_save() call) when a revision is reverted. + */ +function node_revisions_revert($nid, $revision) { + global $user; + + $node = node_load($nid, $revision); + if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { + if ($node->vid) { + $node->revision = 1; + $node->log = t('Copy of the revision from %date.', array('%date' => format_date($node->revision_timestamp))); + if (module_exists('taxonomy')) { + $node->taxonomy = array_keys($node->taxonomy); + } + + node_save($node); + + drupal_set_message(t('%title has been reverted back to the revision from %revision-date', array('%revision-date' => format_date($node->revision_timestamp), '%title' => $node->title))); + watchdog('content', t('@type: reverted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $revision))); + } + else { + drupal_set_message(t('You tried to revert to an invalid revision.'), 'error'); + } + drupal_goto('node/'. $nid .'/revisions'); + } + drupal_access_denied(); +} + +/** + * Delete the revision with specified revision number. A "delete revision" nodeapi event is invoked when a + * revision is deleted. + */ +function node_revisions_delete($nid, $revision) { + if (user_access('administer nodes')) { + $node = node_load($nid); + if (node_access('delete', $node)) { + // Don't delete the current revision + if ($revision != $node->vid) { + $node = node_load($nid, $revision); + + db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision); + node_invoke_nodeapi($node, 'delete revision'); + drupal_set_message(t('Deleted %title revision %revision.', array('%title' => $node->title, '%revision' => $revision))); + watchdog('content', t('@type: deleted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $revision))); + } + + else { + drupal_set_message(t('Deletion failed. You tried to delete the current revision.')); + } + if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)) > 1) { + drupal_goto("node/$nid/revisions"); + } + else { + drupal_goto("node/$nid"); + } + } + } + + drupal_access_denied(); +} + +/** + * Return a list of all the existing revision numbers. + */ +function node_revisions_list($node) { + $revisions = array(); + $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revisions} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = %d ORDER BY r.timestamp DESC', $node->nid); + while ($revision = db_fetch_object($result)) { + $revisions[] = $revision; + } + + return $revisions; +} + +/** + * Theme output of revision log message. + * + * @ingroup themeable + */ +function theme_node_revisions_log_message($log) { + return '

'. $log .'

'; +} + Index: update.php =================================================================== --- update.php (revision 3) +++ update.php (working copy) @@ -15,7 +15,7 @@ */ // Enforce access checking? -$access_check = TRUE; +//$access_check = TRUE; function update_sql($sql) {