Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1023 diff -u -p -r1.1023 common.inc --- includes/common.inc 16 Oct 2009 19:20:33 -0000 1.1023 +++ includes/common.inc 16 Oct 2009 21:45:27 -0000 @@ -3292,7 +3292,7 @@ function drupal_clear_css_cache() { * @return * The cleaned identifier. */ -function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '[' => '-', ']' => '')) { +function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) { // By default, we filter using Drupal's coding standards. $identifier = strtr($identifier, $filter); Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.355 diff -u -p -r1.355 menu.inc --- includes/menu.inc 17 Oct 2009 01:15:39 -0000 1.355 +++ includes/menu.inc 17 Oct 2009 04:13:01 -0000 @@ -185,6 +185,26 @@ define('MENU_LOCAL_ACTION', MENU_IS_LOCA */ /** + * @name Menu context types + * @{ + * Flags for use in the "context" attribute of menu router items. + */ + +/** + * Internal menu flag: Local task should be displayed in page context. + */ +define('MENU_CONTEXT_PAGE', 0x0001); + +/** + * Internal menu flag: Local task should be displayed inline. + */ +define('MENU_CONTEXT_INLINE', 0x0002); + +/** + * @} End of "Menu context types". + */ + +/** * @name Menu status codes * @{ * Status codes for menu callbacks. @@ -663,14 +683,14 @@ function _menu_item_localize(&$item, $ma * a non existing node) then this function return FALSE. */ function _menu_translate(&$router_item, $map, $to_arg = FALSE) { - if ($to_arg) { + if ($to_arg && !empty($router_item['to_arg_functions'])) { // Fill in missing path elements, such as the current uid. _menu_link_map_translate($map, $router_item['to_arg_functions']); } // The $path_map saves the pieces of the path as strings, while elements in // $map may be replaced with loaded objects. $path_map = $map; - if (!_menu_load_objects($router_item, $map)) { + if (!empty($router_item['load_functions']) && !_menu_load_objects($router_item, $map)) { // An error occurred loading an object. $router_item['access'] = FALSE; return FALSE; @@ -706,17 +726,15 @@ function _menu_translate(&$router_item, * An array of helper function (ex: array(2 => 'menu_tail_to_arg')) */ function _menu_link_map_translate(&$map, $to_arg_functions) { - if ($to_arg_functions) { - $to_arg_functions = unserialize($to_arg_functions); - foreach ($to_arg_functions as $index => $function) { - // Translate place-holders into real values. - $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index); - if (!empty($map[$index]) || isset($arg)) { - $map[$index] = $arg; - } - else { - unset($map[$index]); - } + $to_arg_functions = unserialize($to_arg_functions); + foreach ($to_arg_functions as $index => $function) { + // Translate place-holders into real values. + $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index); + if (!empty($map[$index]) || isset($arg)) { + $map[$index] = $arg; + } + else { + unset($map[$index]); } } } @@ -751,7 +769,9 @@ function _menu_link_translate(&$item) { } else { $map = explode('/', $item['link_path']); - _menu_link_map_translate($map, $item['to_arg_functions']); + if (!empty($item['to_arg_functions'])) { + _menu_link_map_translate($map, $item['to_arg_functions']); + } $item['href'] = implode('/', $map); // Note - skip callbacks without real values for their arguments. @@ -761,7 +781,7 @@ function _menu_link_translate(&$item) { } // menu_tree_check_access() may set this ahead of time for links to nodes. if (!isset($item['access'])) { - if (!_menu_load_objects($item, $map)) { + if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) { // An error occurred loading an object. $item['access'] = FALSE; return FALSE; @@ -1614,10 +1634,11 @@ function menu_local_tasks($level = 0) { $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC)) ->fields('menu_router') ->condition('tab_root', $router_item['tab_root']) + ->condition('context', MENU_CONTEXT_INLINE, '<>') ->orderBy('weight') ->orderBy('title') ->execute(); - $map = arg(); + $map = $router_item['original_map']; $children = array(); $tasks = array(); $root_path = $router_item['path']; @@ -1758,6 +1779,100 @@ function menu_local_tasks($level = 0) { } /** + * Retrieve contextual links for a system object based on registered local tasks. + * + * This leverages the menu system to retrieve the first layer of registered + * local tasks for a given system path. All local tasks of the tab type 'task' + * or 'context' are taken into account. + * + * @see hook_menu() + * + * For example, when considering the following registered local tasks: + * - node/%node/view (default local task) with no 'context' defined + * - node/%node/edit with context: MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE + * - node/%node/revisions with context: MENU_CONTEXT_PAGE + * - node/%node/report-as-spam with context: MENU_CONTEXT_INLINE + * + * If the path "node/123" is passed to this function, then it will return the + * links for 'edit' and 'report-as-spam'. + * + * @param $path + * The menu router path of the object to retrieve local tasks for, for example + * "node/123" or "admin/structure/menu/manage/[menu_name]". + * + * @return + * A list of menu router items that are local tasks for the passed in path. + * + * @see system_preprocess() + */ +function menu_contextual_links($parent_path, $args) { + static $path_empty = array(); + + $links = array(); + // Performance: In case a previous invocation for the same parent path did not + // return any links, we immediately return here. + if (isset($path_empty[$parent_path])) { + return $links; + } + // Construct the item-specific parent path. + $path = $parent_path . '/' . implode('/', $args); + + // Get the router item for the given parent link path. + $router_item = menu_get_item($path); + if (!$router_item || !$router_item['access']) { + $path_empty[$parent_path] = TRUE; + return $links; + } + $data = &drupal_static(__FUNCTION__, array()); + $root_path = $router_item['path']; + + // Performance: For a single, normalized path (such as 'node/%') we only query + // available tasks once per request. + if (!isset($data[$root_path])) { + // Get all contextual links that are direct children of the router item and + // not of the tab type 'view'. + $data[$root_path] = db_select('menu_router', 'm') + ->fields('m') + ->condition('tab_parent', $router_item['tab_root']) + ->condition('context', MENU_CONTEXT_PAGE, '<>') + ->orderBy('weight') + ->orderBy('title') + ->execute() + ->fetchAllAssoc('path', PDO::FETCH_ASSOC); + } + $parent_length = drupal_strlen($root_path) + 1; + $map = $router_item['original_map']; + foreach ($data[$root_path] as $item) { + // Extract the actual "task" string from the path argument. + $key = drupal_substr($item['path'], $parent_length); + + // Denormalize and translate the contextual link. + _menu_translate($item, $map, TRUE); + if (!$item['access']) { + continue; + } + // All contextual links are keyed by the actual "task" path argument. The + // menu system does not allow for two local tasks with the same name, and + // since the key is also used as CSS class for the link item, which may be + // styled as icon, it wouldn't make sense to display the same icon for + // different tasks. + $links[$key] = $item; + } + + // Allow modules to alter contextual links. + drupal_alter('menu_contextual_links', $links, $router_item, $root_path); + + // Performance: If the current user does not have access to any links for this + // router path and no other module added further links, we assign FALSE here + // to skip the entire process the next time the same router path is requested. + if (empty($links)) { + $path_empty[$parent_path] = TRUE; + } + + return $links; +} + +/** * Returns the rendered local tasks at the top level. */ function menu_primary_local_tasks() { @@ -2898,6 +3013,10 @@ function _menu_router_build($callbacks) $item['tab_parent'] = ''; $item['tab_root'] = $path; } + // If not specified, assign the default tab type for local tasks. + elseif (!isset($item['context'])) { + $item['context'] = MENU_CONTEXT_PAGE; + } for ($i = $item['_number_parts'] - 1; $i; $i--) { $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); if (isset($menu[$parent_path])) { @@ -2970,6 +3089,7 @@ function _menu_router_build($callbacks) 'theme callback' => '', 'description' => '', 'position' => '', + 'context' => 0, 'tab_parent' => '', 'tab_root' => $path, 'path' => $path, @@ -3013,6 +3133,7 @@ function _menu_router_save($menu, $masks 'delivery_callback', 'fit', 'number_parts', + 'context', 'tab_parent', 'tab_root', 'title', @@ -3041,6 +3162,7 @@ function _menu_router_save($menu, $masks 'delivery_callback' => $item['delivery callback'], 'fit' => $item['_fit'], 'number_parts' => $item['_number_parts'], + 'context' => $item['context'], 'tab_parent' => $item['tab_parent'], 'tab_root' => $item['tab_root'], 'title' => $item['title'], Index: misc/contextual_links.css =================================================================== RCS file: misc/contextual_links.css diff -N misc/contextual_links.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/contextual_links.css 16 Oct 2009 17:59:53 -0000 @@ -0,0 +1,38 @@ +/* $Id$ */ + +/** + * Contextual links regions. + */ +.contextual-links-region { + outline: none; + position: relative; +} +.contextual-links-region-active { + outline: #000 dashed 1px; +} + +/** + * Contextual links. + */ +ul.contextual-links { + float: right; + font-size: 90%; + margin: 0; + padding: 0; +} +ul.contextual-links li { + border-left: 1px solid #ccc; + display: inline; + line-height: 100%; + list-style: none; + margin: 0 0 0 0.3em; + padding: 0 0 0 0.6em; +} +ul.contextual-links li.first { + border-left: 0; + margin: 0; + padding: 0; +} +ul.contextual-links li a { + text-decoration: none; +} Index: misc/contextual_links.js =================================================================== RCS file: misc/contextual_links.js diff -N misc/contextual_links.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/contextual_links.js 16 Oct 2009 18:01:08 -0000 @@ -0,0 +1,33 @@ +// $Id$ +(function ($) { + +Drupal.contextualLinks = Drupal.contextualLinks || {}; + +/** + * Attach outline behavior for regions associated with contextual links. + */ +Drupal.behaviors.contextualLinks = { + attach: function (context) { + $('ul.contextual-links', context).once('contextual-links', function () { + $(this).hover(Drupal.contextualLinks.hover, Drupal.contextualLinks.hoverOut); + }); + } +}; + +/** + * Enables outline for the region contextual links are associated with. + */ +Drupal.contextualLinks.hover = function () { + $(this).addClass('contextual-links-link-active') + .closest('.contextual-links-region').addClass('contextual-links-region-active'); +}; + +/** + * Disables outline for the region contextual links are associated with. + */ +Drupal.contextualLinks.hoverOut = function () { + $(this).removeClass('contextual-links-link-active') + .closest('.contextual-links-region').removeClass('contextual-links-region-active'); +}; + +})(jQuery); Index: modules/block/block.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.api.php,v retrieving revision 1.8 diff -u -p -r1.8 block.api.php --- modules/block/block.api.php 31 Aug 2009 17:06:08 -0000 1.8 +++ modules/block/block.api.php 16 Oct 2009 17:50:23 -0000 @@ -140,6 +140,7 @@ function hook_block_view($delta = '') { 'content' => mymodule_display_block_exciting(), ); break; + case 'amazing': $block = array( 'subject' => t('Default title of the amazing block'), @@ -151,6 +152,79 @@ function hook_block_view($delta = '') { } /** + * Perform alterations to the content of a block. + * + * This hook allows you to modify any data returned by hook_block_view(). + * + * Note that instead of hook_block_view_alter(), which is called for all + * blocks, you can also use hook_block_view_MODULE_DELTA_alter() to alter a + * specific block. + * + * @param $data + * An array of data, as returned from the hook_block_view() implementation of + * the module that defined the block: + * - subject: The localized title of the block. + * - content: Either a string or a renderable array representing the content + * of the block. You should check that the content is an array before trying + * to modify parts of the renderable structure. + * @param $block + * The block object, as loaded from the database, having the main properties: + * - module: The name of the module that defined the block. + * - delta: The identifier for the block within that module, as defined within + * hook_block_info(). + * + * @see hook_block_view_alter() + * @see hook_block_view() + */ +function hook_block_view_alter(&$data, $block) { + // Remove the contextual links on all blocks that provide them. + if (is_array($data['content']) && isset($data['content']['#contextual_links'])) { + unset($data['content']['#contextual_links']); + } + // Add a theme wrapper function defined by the current module to all blocks + // provided by the "somemodule" module. + if (is_array($data['content']) && $block->module == 'somemodule') { + $data['content']['#theme_wrappers'][] = 'mymodule_special_block'; + } +} + +/** + * Perform alterations to a specific block. + * + * Modules can implement hook_block_view_MODULE_DELTA_alter() to modify a + * specific block, rather than implementing hook_block_view_alter(). + * + * Note that this hook fires before hook_block_view_alter(). Therefore, all + * implementations of hook_block_view_MODULE_DELTA_alter() will run before all + * implementations of hook_block_view_alter(), regardless of the module order. + * + * @param $data + * An array of data, as returned from the hook_block_view() implementation of + * the module that defined the block: + * - subject: The localized title of the block. + * - content: Either a string or a renderable array representing the content + * of the block. You should check that the content is an array before trying + * to modify parts of the renderable structure. + * @param $block + * The block object, as loaded from the database, having the main properties: + * - module: The name of the module that defined the block. + * - delta: The identifier for the block within that module, as defined within + * hook_block_info(). + * + * @see hook_block_view_alter() + * @see hook_block_view() + */ +function hook_block_view_MODULE_DELTA_alter(&$data, $block) { + // This code will only run for a specific block. For example, if MODULE_DELTA + // in the function definition above is set to "mymodule_somedelta", the code + // will only run on the "somedelta" block provided by the "mymodule" module. + + // Change the title of the "somedelta" block provided by the "mymodule" + // module. + $data['subject'] = t('New title of the block'); +} + +/** * Act on blocks prior to rendering. * * This hook allows you to add, remove or modify blocks in the block list. The Index: modules/block/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.module,v retrieving revision 1.391 diff -u -p -r1.391 block.module --- modules/block/block.module 16 Oct 2009 23:48:37 -0000 1.391 +++ modules/block/block.module 17 Oct 2009 04:02:49 -0000 @@ -95,15 +95,20 @@ function block_menu() { 'type' => MENU_CALLBACK, 'file' => 'block.admin.inc', ); - $items['admin/structure/block/manage/%block/%/configure'] = array( + $items['admin/structure/block/manage/%block/%'] = array( 'title' => 'Configure block', 'page callback' => 'drupal_get_form', 'page arguments' => array('block_admin_configure', 4), 'load arguments' => array(5), 'access arguments' => array('administer blocks'), - 'type' => MENU_CALLBACK, 'file' => 'block.admin.inc', ); + $items['admin/structure/block/manage/%block/%/configure'] = array( + 'title' => 'Configure block', + 'load arguments' => array(5), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + ); $items['admin/structure/block/manage/%block/%/delete'] = array( 'title' => 'Delete block', 'page callback' => 'drupal_get_form', @@ -282,6 +287,12 @@ function _block_get_renderable_array($li foreach ($list as $key => $block) { $build[$key] = $block->content; unset($block->content); + + // Add contextual links for this block; skipping the system main block. + if ($key != 'system_main') { + $build[$key]['#contextual_links']['block'] = menu_contextual_links('admin/structure/block/manage', array($block->module, $block->delta)); + } + $build[$key] += array( '#block' => $block, '#weight' => ++$weight, @@ -785,6 +796,12 @@ function _block_render_blocks($region_bl } else { $array = module_invoke($block->module, 'block_view', $block->delta); + + // Allow modules to modify the block before it is viewed, via either + // hook_block_view_MODULE_DELTA_alter() or hook_block_view_alter(). + drupal_alter("block_view_{$block->module}_{$block->delta}", $array, $block); + drupal_alter('block_view', $array, $block); + if (isset($cid)) { cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY); } Index: modules/block/block.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.tpl.php,v retrieving revision 1.4 diff -u -p -r1.4 block.tpl.php --- modules/block/block.tpl.php 11 Sep 2009 06:48:02 -0000 1.4 +++ modules/block/block.tpl.php 16 Oct 2009 17:52:58 -0000 @@ -11,6 +11,7 @@ * - $block->module: Module that generated the block. * - $block->delta: An ID for the block, unique within each module. * - $block->region: The block region embedding the current block. + * - $contextual_links (array): An array of contextual links for the block. * - $classes: String of classes that can be used to style contextually through * CSS. It can be manipulated through the variable $classes_array from * preprocess functions. The default values can be one or more of the following: @@ -36,6 +37,11 @@ */ ?>
> + + + + + subject): ?> >subject ?> Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.792 diff -u -p -r1.792 comment.module --- modules/comment/comment.module 16 Oct 2009 20:40:05 -0000 1.792 +++ modules/comment/comment.module 17 Oct 2009 04:16:28 -0000 @@ -185,6 +185,7 @@ function comment_menu() { 'access callback' => 'comment_access', 'access arguments' => array('edit', 1), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, 'weight' => 0, ); $items['comment/%comment/approve'] = array( @@ -193,6 +194,7 @@ function comment_menu() { 'page arguments' => array(1), 'access arguments' => array('administer comments'), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, 'file' => 'comment.pages.inc', 'weight' => 1, ); @@ -202,6 +204,7 @@ function comment_menu() { 'page arguments' => array('comment_confirm_delete', 1), 'access arguments' => array('administer comments'), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, 'file' => 'comment.admin.inc', 'weight' => 2, ); @@ -794,6 +797,8 @@ function comment_build($comment, $node, '#node' => $node, '#build_mode' => $build_mode, ); + // Add contextual links for this comment. + $build['#contextual_links']['comment'] = menu_contextual_links('comment', array($comment->cid)); $prefix = ''; $is_threaded = isset($comment->divs) && variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED; Index: modules/comment/comment.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.tpl.php,v retrieving revision 1.13 diff -u -p -r1.13 comment.tpl.php --- modules/comment/comment.tpl.php 10 Oct 2009 13:37:10 -0000 1.13 +++ modules/comment/comment.tpl.php 16 Oct 2009 18:06:07 -0000 @@ -19,6 +19,7 @@ * - $status: Comment status. Possible values are: * comment-unpublished, comment-published or comment-preview. * - $title: Linked title. + * - $contextual_links (array): An array of contextual links for the comment. * - $classes: String of classes that can be used to style contextually through * CSS. It can be manipulated through the variable $classes_array from * preprocess functions. The default values can be one or more of the following: @@ -46,6 +47,10 @@ */ ?>
> + + + + Index: modules/locale/locale.test =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v retrieving revision 1.47 diff -u -p -r1.47 locale.test --- modules/locale/locale.test 16 Oct 2009 15:47:46 -0000 1.47 +++ modules/locale/locale.test 16 Oct 2009 21:45:28 -0000 @@ -1089,7 +1089,7 @@ class LanguageSwitchingFunctionalTest ex $this->assertText(t('Languages'), t('Language switcher block found.')); // Assert that only the current language is marked as active. - list($language_switcher) = $this->xpath('//div[@id="block-locale-language"]'); + list($language_switcher) = $this->xpath('//div[@id="block-locale-language"]/div[@class="content"]'); $links = array( 'active' => array(), 'inactive' => array(), @@ -1098,7 +1098,7 @@ class LanguageSwitchingFunctionalTest ex 'active' => array(), 'inactive' => array(), ); - foreach ($language_switcher->div->ul->li as $link) { + foreach ($language_switcher->ul->li as $link) { $classes = explode(" ", (string) $link['class']); list($language) = array_intersect($classes, array('en', 'fr')); if (in_array('active', $classes)) { Index: modules/menu/menu.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.api.php,v retrieving revision 1.17 diff -u -p -r1.17 menu.api.php --- modules/menu/menu.api.php 15 Oct 2009 14:07:29 -0000 1.17 +++ modules/menu/menu.api.php 17 Oct 2009 04:06:44 -0000 @@ -199,6 +199,21 @@ * this alone; the default alphabetical order is usually best. * - "menu_name": Optional. Set this to a custom menu if you don't want your * item to be placed in Navigation. + * - "context": (optional) Defines the type of a tab to control its placement + * depending on the requested context. By default, all tabs are only + * displayed as local tasks when being rendered in a page context. All tabs + * that should be accessible as contextual links in page region containers + * outside of the parent menu item's primary page context should be + * registered using one of the following contexts: + * - MENU_CONTEXT_PAGE: (default) The tab is displayed as local task for the + * page context only. + * - MENU_CONTEXT_INLINE: The tab is displayed as contextual link outside of + * the primary page context only. + * Contexts can be combined. For example, to display a tab both on a page + * and inline, a menu router item may specify: + * @code + * 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + * @endcode * - "tab_parent": For local task menu items, the path of the task's parent * item; defaults to the same path without the last component (e.g., the * default parent for 'admin/people/create' is 'admin/people'). @@ -497,5 +512,46 @@ function hook_menu_local_tasks_alter(&$d } /** + * Alter contextual links before they are rendered. + * + * This hook is invoked by menu_contextual_links(). The system-determined + * contextual links are passed in by reference. Additional links may be added + * or existing links can be altered. + * + * Each contextual link must at least contain: + * - title: The localized title of the link. + * - href: The system path to link to. + * - localized_options: An array of options to pass to url(). + * + * @param $links + * An associative array containing contextual links for the given $root_path, + * as described above. The array keys are used to build CSS class names for + * contextual links and must therefore be unique for each set of contextual + * links. + * @param $router_item + * The menu router item belonging to the $root_path being requested. + * @param $root_path + * The (parent) path that has been requested to build contextual links for. + * This is a normalized path, which means that an originally passed path of + * 'node/123' became 'node/%'. + * + * @see menu_contextual_links() + */ +function hook_menu_contextual_links_alter(&$links, $router_item, $root_path) { + // Add a link to all contextual links for nodes. + if ($root_path == 'node/%') { + $links['foo'] = array( + 'title' => t('Do fu'), + 'href' => 'foo/do', + 'localized_options' => array( + 'query' => array( + 'foo' => 'bar', + ), + ), + ); + } +} + +/** * @} End of "addtogroup hooks". */ Index: modules/menu/menu.module =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v retrieving revision 1.211 diff -u -p -r1.211 menu.module --- modules/menu/menu.module 17 Oct 2009 00:51:52 -0000 1.211 +++ modules/menu/menu.module 17 Oct 2009 04:09:36 -0000 @@ -98,6 +98,7 @@ function menu_menu() { 'title' => 'List links', 'weight' => -10, 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, ); $items['admin/structure/menu/manage/%menu/add'] = array( 'title' => 'Add link', @@ -113,6 +114,7 @@ function menu_menu() { 'page arguments' => array('menu_edit_menu', 'edit', 4), 'access arguments' => array('administer menu'), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, 'file' => 'menu.admin.inc', ); $items['admin/structure/menu/manage/%menu/delete'] = array( @@ -423,10 +425,27 @@ function menu_block_view($delta = '') { $menus = menu_get_menus(FALSE); $data['subject'] = check_plain($menus[$delta]); $data['content'] = menu_tree($delta); + // Add contextual links for this block. + if (!empty($data['content'])) { + $data['content']['#contextual_links']['menu'] = menu_contextual_links('admin/structure/menu/manage', array($delta)); + } return $data; } /** + * Implement hook_block_view_alter(). + */ +function menu_block_view_alter(&$data, $block) { + // Add contextual links for system menu blocks. + if ($block->module == 'system' && !empty($data['content'])) { + $system_menus = menu_list_system_menus(); + if (isset($system_menus[$block->delta])) { + $data['content']['#contextual_links']['menu'] = menu_contextual_links('admin/structure/menu/manage', array($block->delta)); + } + } +} + +/** * Implement hook_node_insert(). */ function menu_node_insert($node) { Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1149 diff -u -p -r1.1149 node.module --- modules/node/node.module 16 Oct 2009 19:06:23 -0000 1.1149 +++ modules/node/node.module 17 Oct 2009 04:25:04 -0000 @@ -1112,6 +1112,9 @@ function node_build($node, $build_mode = '#node' => $node, '#build_mode' => $build_mode, ); + // Add contextual links for this node. + $build['#contextual_links']['node'] = menu_contextual_links('node', array($node->nid)); + return $build; } @@ -1806,11 +1809,13 @@ function node_menu() { 'page arguments' => array(1), 'access callback' => 'node_access', 'access arguments' => array('view', 1), - 'type' => MENU_CALLBACK); + 'type' => MENU_CALLBACK, + ); $items['node/%node/view'] = array( 'title' => 'View', 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => -10); + 'weight' => -10, + ); $items['node/%node/edit'] = array( 'title' => 'Edit', 'page callback' => 'node_page_edit', @@ -1818,8 +1823,9 @@ function node_menu() { 'access callback' => 'node_access', 'access arguments' => array('update', 1), 'theme callback' => '_node_custom_theme', - 'weight' => 1, + 'weight' => 0, 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, 'file' => 'node.pages.inc', ); $items['node/%node/delete'] = array( Index: modules/node/node.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.tpl.php,v retrieving revision 1.24 diff -u -p -r1.24 node.tpl.php --- modules/node/node.tpl.php 11 Oct 2009 06:43:33 -0000 1.24 +++ modules/node/node.tpl.php 16 Oct 2009 17:56:11 -0000 @@ -18,6 +18,7 @@ * - $node_url: Direct url of the current node. * - $terms: the themed list of taxonomy term links output from theme_links(). * - $display_submitted: whether submission information should be displayed. + * - $contextual_links (array): An array of contextual links for the node. * - $classes: String of classes that can be used to style contextually through * CSS. It can be manipulated through the variable $classes_array from * preprocess functions. The default values can be one or more of the following: @@ -74,6 +75,10 @@ + + + + > Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.403 diff -u -p -r1.403 system.install --- modules/system/system.install 16 Oct 2009 23:48:38 -0000 1.403 +++ modules/system/system.install 17 Oct 2009 04:11:35 -0000 @@ -1026,6 +1026,12 @@ function system_schema() { 'default' => 0, 'size' => 'small', ), + 'context' => array( + 'description' => 'Only for local tasks (tabs) - the context of a local task to control its placement.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), 'tab_parent' => array( 'description' => 'Only for local tasks (tabs) - the router path of the parent page (which may also be a local task).', 'type' => 'varchar', @@ -2758,6 +2764,18 @@ function system_update_7042() { } /** + * Add a 'context' field to {menu_router} to control contextual placement of local tasks. + */ +function system_update_7043() { + db_add_field('menu_router', 'context', array( + 'description' => 'Only for local tasks (tabs) - the context of a local task to control its placement.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + )); +} + +/** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. */ Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.819 diff -u -p -r1.819 system.module --- modules/system/system.module 16 Oct 2009 23:48:38 -0000 1.819 +++ modules/system/system.module 17 Oct 2009 03:30:26 -0000 @@ -3488,3 +3488,83 @@ function system_archiver_info() { function theme_confirm_form($variables) { return drupal_render_children($variables['form']); } + +/** + * Template variable preprocessor for contextual links. + */ +function system_preprocess(&$variables, $hook) { + static $hooks; + + if (!isset($hooks)) { + $hooks = theme_get_registry(); + } + + // Initialize contextual links template variable. + $variables['contextual_links'] = array(); + + // Determine the primary theme function argument. + $keys = array_keys($hooks[$hook]['arguments']); + $key = $keys[0]; + if (isset($variables[$key])) { + $element = $variables[$key]; + } + + if (isset($element) && is_array($element) && isset($element['#contextual_links'])) { + $variables['contextual_links'] = system_build_contextual_links($element); + if (!empty($variables['contextual_links'])) { + $variables['classes_array'][] = 'contextual-links-region'; + } + } +} + +/** + * Build a renderable array for contextual links. + * + * @param $element + * A renderable array containing a #contextual_links property. + * + * @return + * A renderable array representing contextual links. + */ +function system_build_contextual_links($element) { + static $destination; + + // Transform contextual links into parameters suitable for theme_link(). + $items = call_user_func_array('array_merge_recursive', $element['#contextual_links']); + $build = array(); + if (empty($items)) { + return $build; + } + + if (!isset($destination)) { + $destination = drupal_get_destination(); + } + + $links = array(); + foreach ($items as $class => $item) { + $class = drupal_html_class($class); + $links[$class] = array( + 'title' => $item['title'], + 'href' => $item['href'], + ); + // @todo theme_links() should *really* use the same parameters as l()... + if (!isset($item['localized_options']['query'])) { + $item['localized_options']['query'] = array(); + } + $item['localized_options']['query'] += $destination; + $links[$class] += $item['localized_options']; + } + if ($links) { + $build = array( + '#theme' => 'links', + '#links' => $links, + '#attributes' => array('class' => array('contextual-links')), + '#attached' => array( + 'js' => array('misc/contextual_links.js'), + 'css' => array('misc/contextual_links.css'), + ), + ); + } + return $build; +} + Index: themes/garland/block.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/block.tpl.php,v retrieving revision 1.9 diff -u -p -r1.9 block.tpl.php --- themes/garland/block.tpl.php 11 Sep 2009 06:48:03 -0000 1.9 +++ themes/garland/block.tpl.php 16 Oct 2009 17:57:57 -0000 @@ -3,6 +3,10 @@ ?>
> + + + + subject)): ?>

>subject ?>

Index: themes/garland/comment.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/comment.tpl.php,v retrieving revision 1.17 diff -u -p -r1.17 comment.tpl.php --- themes/garland/comment.tpl.php 10 Oct 2009 13:37:11 -0000 1.17 +++ themes/garland/comment.tpl.php 16 Oct 2009 18:06:56 -0000 @@ -5,6 +5,10 @@
+ + + + Index: themes/garland/node.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/node.tpl.php,v retrieving revision 1.17 diff -u -p -r1.17 node.tpl.php --- themes/garland/node.tpl.php 11 Oct 2009 03:07:21 -0000 1.17 +++ themes/garland/node.tpl.php 16 Oct 2009 17:58:21 -0000 @@ -3,6 +3,10 @@ ?>
> + + + + Index: themes/garland/style.css =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/style.css,v retrieving revision 1.65 diff -u -p -r1.65 style.css --- themes/garland/style.css 5 Oct 2009 02:43:01 -0000 1.65 +++ themes/garland/style.css 16 Oct 2009 17:58:41 -0000 @@ -650,8 +650,8 @@ ul.secondary li.active a { */ .node { border-bottom: 1px solid #e9eff3; - margin: 0 -26px 1.5em; - padding: 1.5em 26px; + margin: 0 -16px 1.5em; + padding: 1.5em 16px; } ul.links li, ul.inline li { @@ -809,6 +809,17 @@ tr.even td.menu-disabled { } /** + * Contextual links. + */ +.contextual-links-region-active { + outline: #027AC6 dashed 1px; +} +.block ul.contextual-links { + margin: 0; + padding: 0; +} + +/** * Collapsible fieldsets */ fieldset {