diff --git a/includes/menu.inc b/includes/menu.inc index f1b375f52a6f355110ed5beb9e1c54af2c0edd6d..50352dbcbec8c4d0fcc75bfe97969d506336021e 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -360,9 +360,12 @@ function menu_execute_active_handler($path = NULL) { * @return * Returns TRUE for success, FALSE if an object cannot be loaded */ -function _menu_load_objects($item, &$map) { - if ($item['load_functions']) { - $load_functions = unserialize($item['load_functions']); +function _menu_load_objects(&$item, &$map) { + if ($load_functions = $item['load_functions']) { + // If someone calls this function twice, then unserialize will fail. + if ($load_functions_unserialized = unserialize($load_functions)) { + $load_functions = $load_functions_unserialized; + } $path_map = $map; foreach ($load_functions as $index => $function) { if ($function) { @@ -373,6 +376,7 @@ function _menu_load_objects($item, &$map) { // some processing. In this case the $function is the key to the // load_function array, and the value is the list of arguments. list($function, $args) = each($function); + $load_functions[$index] = $function; // Some arguments are placeholders for dynamic items to process. foreach ($args as $i => $arg) { @@ -387,6 +391,9 @@ function _menu_load_objects($item, &$map) { // the map. $args[$i] = &$map; } + if (is_int($arg)) { + $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : ''; + } } array_unshift($args, $value); $return = call_user_func_array($function, $args); @@ -403,6 +410,7 @@ function _menu_load_objects($item, &$map) { $map[$index] = $return; } } + $item['load_functions'] = $load_functions; } return TRUE; } @@ -609,6 +617,31 @@ function _menu_link_translate(&$item) { return $map; } +/** + * Get a loaded object from a router item. + * + * menu_get_object() will provide you the current node on paths like node/5, + * node/5/revisions/48 etc. menu_get_object('user') will give you the user + * account on user/5 etc. + * + * @param $type + * Type of the object. These appear in hook_menu definitons as %type. Core + * provides aggregator_feed, aggregator_category, contact, filter_format, + * forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the + * relevant {$type}_load function for more on each. Defaults to node. + * @param $position + * The expected position for $type object. For node/%node this is 1, for + * comment/reply/%node this is 2. Defaults to 1. + * @param $path + * See @menu_get_item for more on this. Defaults to the current path. + */ +function menu_get_object($type = 'node', $position = 1, $path = NULL) { + $router_item = menu_get_item($path); + if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type .'_load') { + return $router_item['map'][$position]; + } +} + /** * Render a menu tree based on the current path. * diff --git a/includes/theme.inc b/includes/theme.inc index 45655c58ade00b3c9f9a0d609577d69bfd03be5c..42c54783f1f51dd294062b23b99c502b62e68cba 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1736,8 +1736,8 @@ function template_preprocess_page(&$variables) { // Closure should be filled last. $variables['closure'] = theme('closure'); - if ((arg(0) == 'node') && is_numeric(arg(1))) { - $variables['node'] = node_load(arg(1)); + if ($node = menu_get_object()) { + $variables['node'] = $node; } // Compile a list of classes that are going to be applied to the body element. diff --git a/modules/node/node.module b/modules/node/node.module index 76c000a5014d5d903cc1c93105cd39a194b538f5..5839b5889085f5303a833296b56db15f083af216 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -1043,7 +1043,10 @@ function node_build_content($node, $teaser = FALSE, $page = FALSE) { /** * Generate a page displaying a single node, along with its comments. */ -function node_show($node, $cid) { +function node_show($node, $cid, $message = FALSE) { + if ($message) { + drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp)))); + } $output = node_view($node, FALSE, TRUE); if (function_exists('comment_render') && $node->comment) { @@ -1069,7 +1072,7 @@ function theme_node_log_message($log) { * 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', 'view revisions', 'revert revisions', 'delete revisions'); foreach (node_get_types() as $type) { if ($type->module == 'node') { @@ -1304,8 +1307,31 @@ function node_link($type, $node = NULL, $teaser = FALSE) { 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; +function _node_revision_access($node, $op = 'view') { + static $access = array(); + if (!isset($access[$node->vid])) { + $node_current_revision = node_load($node->nid); + $is_current_revision = $node_current_revision->vid == $node->vid; + // There should be at least two revisions. If the vid of the given node + // and the vid of the current revision differs, then we already have two + // different revisions so there is no need for a separate database check. + // Also, if you try to revert to or delete the current revision, that's + // not good. + if ($is_current_revision && (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node->nid)) == 1 || $op == 'update' || $op == 'delete')) { + $access[$node->vid] = FALSE; + } + elseif (user_access('administer nodes')) { + $access[$node->vid] = TRUE; + } + else { + $map = array('view' => 'view revisions', 'update' => 'revert revisions', 'delete' => 'delete revisions'); + // First check the user permission, second check the access to the + // current revision and finally, if the node passed in is not the current + // revision then access to that, too. + $access[$node->vid] = isset($map[$op]) && user_access($map[$op]) && node_access($op, $node_current_revision) && ($is_current_revision || node_access($op, $node)); + } + } + return $access[$node->vid]; } function _node_add_access() { @@ -1450,28 +1476,37 @@ function node_menu() { 'type' => MENU_CALLBACK); $items['node/%node/revisions'] = array( 'title' => 'Revisions', - 'page callback' => 'node_revisions', + 'page callback' => 'node_revision_overview', + 'page arguments' => array(1), 'access callback' => '_node_revision_access', 'access arguments' => array(1), 'weight' => 2, 'file' => 'node.pages.inc', 'type' => MENU_LOCAL_TASK, ); + $items['node/%node/revisions/%/view'] = array( + 'title' => 'Revisions', + 'page callback' => 'node_show', + 'page arguments' => array(1, NULL, TRUE), + 'type' => MENU_CALLBACK, + ); $items['node/%node/revisions/%/revert'] = array( 'title' => 'Revert to earlier revision', - 'page callback' => 'node_revision_revert', - 'page arguments' => array(1, 3), + 'load arguments' => array(3), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('node_revision_revert_confirm', 1), 'access callback' => '_node_revision_access', - 'access arguments' => array(1, 3), + 'access arguments' => array(1, 'update'), 'file' => 'node.pages.inc', 'type' => MENU_CALLBACK, ); $items['node/%node/revisions/%/delete'] = array( 'title' => 'Delete earlier revision', - 'page callback' => 'node_revision_delete', - 'page arguments' => array(1, 3), + 'load arguments' => array(3), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('node_revision_delete_confirm', 1), 'access callback' => '_node_revision_access', - 'access arguments' => array(1, 3), + 'access arguments' => array(1, 'delete'), 'file' => 'node.pages.inc', 'type' => MENU_CALLBACK, ); diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index 1092240f13aa31d91391b6cc949617a650ddfb51..6861626c6308a69a445e7e7813196dbcb813c409 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -505,38 +505,6 @@ function node_delete_confirm_submit($form, &$form_state) { return; } -/** - * 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; - } - } - drupal_not_found(); -} - /** * Generate an overview table of older revisions of a node. */ @@ -553,7 +521,7 @@ function node_revision_overview($node) { $revert_permission = TRUE; } $delete_permission = FALSE; - if (user_access('administer nodes')) { + if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) { $delete_permission = TRUE; } foreach ($revisions as $revision) { @@ -582,25 +550,6 @@ function node_revision_overview($node) { 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_revision_revert($node, $revision) { - global $user; - - if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { - $node_revision = node_load($node->nid, $revision); - if ($node_revision->vid) { - return drupal_get_form('node_revision_revert_confirm', $node_revision); - } - else { - drupal_set_message(t('You tried to revert to an invalid revision.'), 'error'); - drupal_goto('node/'. $node->nid .'/revisions'); - } - } - drupal_access_denied(); -} /** * Ask for confirmation of the reversion to prevent against CSRF attacks. */ @@ -624,28 +573,6 @@ function node_revision_revert_confirm_submit($form, &$form_state) { $form_state['redirect'] = 'node/'. $node_revision->nid .'/revisions'; } -/** - * Delete the revision with specified revision number. A "delete revision" nodeapi event is invoked when a - * revision is deleted. - */ -function node_revision_delete($node, $revision) { - if (user_access('administer nodes')) { - if (node_access('delete', $node)) { - // Don't allow deleting the current revision. - if ($revision != $node->vid) { - // Load the specific revision instead of the current one. - $node_revision = node_load($node->nid, $revision); - return drupal_get_form('node_revision_delete_confirm', $node_revision); - } - else { - drupal_set_message(t('Deletion failed. You tried to delete the current revision.')); - drupal_goto('node/'. $node->nid .'/revisions'); - } - } - } - drupal_access_denied(); -} - function node_revision_delete_confirm($form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/'. $node_revision->nid .'/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel'));