Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

This change record has been updated based on https://www.drupal.org/node/2302893.

Basic details about contextual links can be found at https://drupal.org/documentation/modules/contextual

As a part of the conversion to use the new New Symfony-based routing system, the contextual link definitions are moving out of hook_menu() and into plugins implementing \DrupalCore\Menu\ContextualLinkInterface which are discovered using \Drupal\Core\Plugin\Discovery\YamlDiscovery which looks for YAML files in the same directory as your module with the file name pattern MODULENAME.links.contextual.yml. For example, block module has block.links.contextual.yml. The implementation \Drupal\Core\Menu\ContextualLinkDefault is used as the default class and will be suitable for the majority of standard uses. It can be extended for more advanced uses, such as rendering a dynamic title.

hook_menu_contextual_links_alter() has been removed. You can still alter contextual links via hook_contextual_links_view_alter()

Drupal 7

function block_menu() {

...

  $items['admin/structure/block/manage/%/%/configure'] = array(
    'title' => 'Configure block',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
  );

...

return $items;
}

Drupal 8

This link is using the base class - and all the needed definition information is encapsulated in the YAML core/modules/block/block.links.contextual.yml

block_configure:
  title: 'Configure block'
  route_name: 'block.admin_edit'
  group: 'block'

Each contextual link is represented by a plugin. Each plugin definition (found in the $module.links.contextual.yml file) contains contextual-link-specific keys underneath the top-level key which is the unique plugin ID. Suggested pattern for the plugin ID is: module_name.whatever_you_want_that_does_not_have_a_dot

Keys are:
route_name: The machine name of the local task route - this also determines where it's displayed
title: The title of the local action. By default, it will be passed through t() and localized.
title_context: Optional text context to be passed through to t() with the title.
group: The name of the group of links.
options: (optional) options to pass to the URL generator
weight:(optional) The integer weight (lower weight tabs are further left, default is 0).

You cannot specify contextual links in the same file as the routes - they are defined independently.

These plugins can also have a different class. Provide the class key for this in the local task YAML file with the name of the appropriate class as value.

Local tasks can also have derivatives (dynamically generated local tasks). To implement those, specify the derivative key with the name of an appropriate class, e.g.:

content_translation.contextual_links:
  derivative: 'Drupal\content_translation\Plugin\Derivative\ContentTranslationContextualLinks'
  weight: 2

Then make that class extend DerivativeBase and implement ContainerDerivativeInterface. You can take additional arguments via a static create() method and return derivatives from the getDerivativeDefinitions() method which should override the parent implementation.

The default class for local tasks is \Drupal\Core\Menu\LocalTaskDefault

Note that the confusing concept of looking up contextual links based on the conbination of 'context' key in hook_menu and matching a root path is now gone - this simpler API renders all links in the desired group.

In order to add a group of contextual links to a page, typically a module will insert them into the render array using the #contextual_links key followed by a key representing the group name and then providing any needed route parameters - e.g. for nodes:

/**
 * Render controller for nodes.
 */
class NodeViewBuilder extends EntityViewBuilder {

  ...

  protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode = NULL) {
    parent::alterBuild($build, $entity, $display, $view_mode, $langcode);
    if ($entity->id()) {
      $build['#contextual_links']['node'] = array(
        'route_parameters' =>array('node' => $entity->id()),
      );
    }

    ...
  }
}

or for blocks:


function _block_get_renderable_region($list = array()) {
  $build = array();
  
  ...

    // Add contextual links for this block; skip the main content block, since
    // contextual links are basically output as tabs/local tasks already. Also
    // skip the help block, since we assume that most users do not need or want
    // to perform contextual actions on the help block, and the links needlessly
    // draw attention on it.
    if (isset($build[$key]) && !in_array($block->get('plugin'), array('system_help_block', 'system_main_block'))) {
      $build[$key]['#contextual_links']['block'] = array(
        'route_parameters' => array('block' => $key),
      );

      // If there are any nested contextual links, move them to the top level.
      if (isset($build[$key]['content']['#contextual_links'])) {
        $build[$key]['#contextual_links'] += $build[$key]['content']['#contextual_links'];
        unset($build[$key]['content']['#contextual_links']);
      }
    }
  }
  return $build;
}
Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done