5

Someone can help me ? I want to display a tab only when the content type is 'test'.

Here's my files.

mymodule.routing.yml :

mymodule.check_test:
  path: '/checktest/{node}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\CheckTestController::content'
  requirements:
    _access: 'TRUE'

mymodule.links.task.yml :

mymodule.check_test:
    title: Display tests
    route_name: mymodule.check_test
    base_route: entity.node.canonical

My tab is displayed for every content, i don't know how to filter with my content type...

Thanks :).

Edit : From mradcliffe's answer,

I've add a mymodule.services.yml :

services:
  access_check.test:
    class: Drupal\mymodule\Access\MyModuleTestAccess
    tags:
      - { name: access_check, applies_to: _access_test }

and a MyModuleTestAccess class with :

public function access(NodeInterface $node)
{
    return AccessResult::allowedIf($node->bundle() === 'test');
}

My edited mymodule.routing.yml :

mymodule.check_test:
  path: '/checktest/{node}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\CheckTestController::content'
  requirements:
    _access_test: ''
  • _access_test: ' ' must now be _access_test: 'true' . – Jonathan Mar 8 '17 at 14:28
  • It's probably good practice to namespace the service declaration: my_module.access_check.test – Jonathan Mar 8 '17 at 14:28
8

You can use an access check to hide a route.

If the user that will view the tab has the same permission for adding a node of a given type, then simply adding _node_add_access: 'node:MYNODETYPEHERE' would be sufficient. However this is probably not the case.

Instead you can add _custom_access to the route requirements as a method on the controller class.

mymodule.check_test:
  path: '/checktest/{node}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\CheckTestController::content'
  requirements:
    _custom_access: '\Drupal\mymodule\Controller\CheckTestController::checkAccess'
    # It is highly-recommended to always use the default entity access check for
    # an Entity as pointed out by @pwolanin below. Not doing so may lead to
    # undesirable results and potential information disclosure or access bypass
    # vulnerability.
    _entity_access: node.view

And then a corresponding method on the controller (see Access checking on routes for more details). Something like this might work for you (untested).

public function checkAccess($node) {
  return AccessResult::allowedif($node->bundle() === 'my_node_type');
}

Update 2019.08.30: Incorporated @pwolanin's answer into this one in case anyone misses and doesn't read its importance.

  • it does not work actually – David Sep 14 at 23:57
  • It would be more helpful if you explain what "it" was and what did "not work" specifically so that we can work together to understand and improve. Thank you. – mradcliffe Sep 15 at 19:36
2

I'm using Drupal 8.1.2 and both the accepted answer and the answer provided in the edit to the question did not actually work for me and they gave this error:

RuntimeException: Callable "Drupal\mymodule\Controller\CheckTestController::checkAccess" requires a value for the "$node" argument. in Drupal\Component\Utility\ArgumentsResolver->handleUnresolvedArgument() (line 142 of /var/www/html/core/lib/Drupal/Component/Utility/ArgumentsResolver.php).

the problem is that with the entity.node.canonical route, the $node variable seems to not actually link to the node itself, but stays as the ID.

The code that worked for me for the checkAccess method in the controller is:

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Controller\ControllerBase;
use Drupal\node\Entity\Node;

class CheckTestController extends ControllerBase {

    ...

    public function checkAccess($node) {
        $actualNode = Node::load($node);
        return AccessResult::allowedIf($actualNode->bundle() === 'test');
    }
}
  • It's maybe because your "$node" var is an interger and not a node entity. Check your routing and add : "parameters: node: type: 'entity:node'" to explain that your path contain a entity : "path: '/node/{node}/xxxx" – Pauleau Aug 8 '18 at 9:36
2

Also - the answer above is incomplete since it skips a check on access to the node itself. This is possibly a recipe for an access bypass vulnerability.

I'd think you'd want a route more like:

mymodule.check_test:
  path: '/checktest/{node}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\CheckTestController::content'
  requirements:
    _custom_access: '\Drupal\mymodule\Controller\CheckTestController::checkAcess'
    _entity_access: node.view
0

Yes, it's unfortunate that this is the normal/best way to handle this. We did't think hard enough about this kind of use case when writing the local task code originally.

An alternative (which I have been considering recently) is to use hook_menu_local_tasks_alter() whose purpose is to "Alter local tasks displayed on the page before they are rendered. The downside is that someone could still visit the path and get an unexpected result.

This is a bit sub-optimal since it's out of the OO flow, and you'd have to worry about updating the cache metadata correctly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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