3

I have a content type with some entity reference fields to another content types. I want to add local task tabs to the node view page, to link those referenced node. However I cannot figure it out how to do it only for the specific content type.

I have created a my_module.links.task.yml file with following contents:

my_module.my_content.my_content_type:
  route_name: entity.node.canonical
  title: Overview
  parent_id: entity.node.canonical
  weight: 0
  class: '\Drupal\my_module\Plugin\Menu\MyContentTab'

my_module.my_content.my_referenced_node:
  route_name: entity.node.canonical
  title: Referenced content
  parent_id: entity.node.canonical
  weight: 10
  class: '\Drupal\my_module\Plugin\Menu\MyReferencedContentTab'
# ... More tabs to the other referenced nodes

The classes are working as they should, and providing the tabs to the referenced nodes correctly. However the tabs are also showing on all content types.

How can I make them show only on the specific content type?

1
  • I'd probably set up a custom permission for the tab pages, that checks the type, and denies access if the type is incorrect. Tabs will not be rendered if access is denied.
    – Jaypan
    Mar 9, 2020 at 16:06

1 Answer 1

4

The reason these tabs show up on all of your content types is that entity.node.canonical is the base route for all nodes. Since you're setting entity.node.canonical as the parent_id, Drupal thinks you want these tabs displayed on every node page.

The easiest way to achieve your goal is probably to implement hook_menu_local_tasks_alter() with code similar to this:

  if ($route_name === 'entity.node.canonical') {
    /** @var \Drupal\node\Entity\Node $node */
    $node = \Drupal::routeMatch()->getParameter('node');
    if ($node->bundle() !== 'my_bundle') {
      $data['tabs']['my_module.my_content.my_content_type']['#access'] = FALSE;
      $data['tabs']['my_module.my_content.my_referenced_node']['#access'] = FALSE;
    }
  }

Because Drupal performs upcasting on this route, you can get a reference to the current node directly from routeMatch() as a parameter, and then compare its bundle to your custom content type. When they don't match up, we set access to FALSE for those tabs.

I think another approach that would probably work is to override the getRouteParameters() method of your LocalTaskDefault classes and use similar logic; if the current node isn't your custom content type, then return an empty array of route parameters.

A third approach might be to create a custom route for your specific content type and then assign that route as the parent_id for these tabs.

5
  • 1
    Thanks Beau! hook_menu_local_tasks_alter() did it as your sample code.
    – Elin Y.
    Mar 10, 2020 at 14:52
  • Just to make it complete for the other readers, the code I used to deny access was: $accessForbidden = new AccessResultForbidden(); $data['tabs'][1]['my_content.my_content_type']['#access'] = $accessForbidden; $data['tabs'][1]['my_module.my_content.my_referenced_node']['#access'] = $accessForbidden; Which has two differences, AccessResultForbidden object instead of FALSE, and the tab level 1 for the second level which I was actually using. For the first level it should be 0.
    – Elin Y.
    Mar 11, 2020 at 9:21
  • This solution will not work because hook_menu_local_tasks_alter does not run per page load. It is cached. Therefore it will work the very first time the hook runs but will not work any time after. Jun 8, 2021 at 13:47
  • 1
    @chadpeppers, just to be clear: hook_menu_local_tasks_alter() does not run on every page load because it doesn't need to do so. This is because, as you mentioned, the results are cached. There is no reason to require this route configuration to be re-calculated on every page request. The crucial detail here is that, while this hook isn't called on every page load, its result remains in cache until/unless cleared. Once cleared, this hook, along with most other alter hooks, will be called in order to re-populate their respective data stores.
    – Beau
    Jun 8, 2021 at 17:21
  • @Beau you are correct. I meant to delete my comment after testing it yesterday but it hadn't shown up. It is cached but its cached on a specific level. In my example I wanted a tab to show up on certain Content types. When the code executes it is cached per node. So your solution works. Jun 9, 2021 at 17:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.