7

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: ''
2
  • _access_test: ' ' must now be _access_test: 'true' .
    – Jonathan
    Mar 8, 2017 at 14:28
  • It's probably good practice to namespace the service declaration: my_module.access_check.test
    – Jonathan
    Mar 8, 2017 at 14:28

5 Answers 5

10

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.

3
  • it does not work actually
    – David
    Sep 14, 2019 at 23:57
  • 1
    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, 2019 at 19:36
  • This worked for.
    – Amit
    Jun 22 at 8:47
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');
    }
}
1
  • 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, 2018 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.

0

The answer from @mradcliffe is largely correct... if you don't want to load the node then in your mymodule.routing.yml user the options to bring in the node object:

mymodule.check_test:
  path: '/checktest/{node}'
  requirements:
    _custom_access: '\Drupal\mymodule\Controller\CheckTestController::checkAccess'
    _entity_access: 'node.view'
  options:
    parameters:
      node:
        type: 'entity:node'

And then in your controller:

namespace Drupal\mymodule\Controller;

use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\node\NodeInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Access\AccessResult;

/**
 * Checks access for displaying time tab.
*/
class CheckAccess extends ControllerBase {

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

This will bring in the node object properly so you don't have to load it in your access method and avoid the error mentioned above if you try to call bundle() on the node id.

Your Answer

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

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