4

Consider an action button on a custom entity's canonical page:

mymodule.actions.yml:

entity.item.action:
  route_name: entity.item.action
  title: 'Do something'
  appears_on:
    - entity.item.canonical

mymodule.routing.yml:

entity.item.action:
  path: '/item/{item}/action'
  defaults:
    _controller: '\Drupal\mymodule\Controller\ItemController::action'
    _title: 'Do something'
  requirements:
    _permission: 'edit items'

I would like to show or hide the action button depending on the value of some field of the entity.

How could I achieve this? Would it be wise to add custom access checking for this?

3 Answers 3

2

You can add a custom access method on your controller. According to this page you should have access to your item argument in the access method so you should be able to restrict access based on a value on that item. The action should be hidden if the user does not have access to the route, i.e. your custom access method denies access.

1
  • Sadly, that doesn't work because access is cached. It can't be used for on-the-fly conditions.
    – Gábor
    Commented Jul 8, 2021 at 14:51
2

I was doing a similar task with hook_menu_local_tasks_alter. Keep in mind though that you will have to query for the current entity (e.g. node) separately as it is not passed into this hook as a parameter

Something like this:

function XXX_menu_local_tasks_alter(&$data, $route_name) {
  $routes = ['entity.node.canonical'];

  if (in_array($route_name, $routes)) {
    foreach ($data['tabs'][0] as $task_name => &$task_array) {
      if ($task_name == 'entity.node.canonical') {

        $node = \Drupal::routeMatch()->getParameter('node');
        if ($node instanceof \Drupal\node\NodeInterface) {
6
  • 1
    This is a good choice if you did not create the module. Better to go with the other answer if you created the module. +1 nonetheless.
    – No Sssweat
    Commented Apr 7, 2018 at 21:18
  • Thanks for expanding into this. I agree, there can be multiple ways of doing things in Drupal. However, not sure that I understood the part if you did not create the module as in my book any custom code goes inside a module. Or at least the question itself states mymodule.actions.yml which a kind of implies a module to me :)
    – George
    Commented Apr 8, 2018 at 3:44
  • 1
    To clarify, if you did not create the module that created the local task. The above code is used to override, but if you created the local task to begin with, no point in overriding your own code.
    – No Sssweat
    Commented Apr 8, 2018 at 4:18
  • Maybe the alter hook makes sense even in my own module … While I did create the route, I want to hide it depending on conditions. How could I achieve this without a hook function?
    – Hendrik
    Commented Apr 8, 2018 at 8:44
  • No Sssweat, sounds like a fair point.
    – George
    Commented Apr 8, 2018 at 18:03
0

Two useful approaches are outlined in the Drupal 8 guide / Providing module defined actions. These are:

  1. Derivers that dynamically generate one or multiple actions
  2. Custom classes for the actions

I nearly solved the issue with approach 1. However, I'm still struggling with caching. This is my solution so far:

namespace Drupal\mymodule\Plugin\Derivative;

use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\mymodule\Entity\ItemInterface;
use Drupal\mymodule\Entity\Item;

/**
* Defines dynamic actions.
*/
class ItemLocalActions extends DeriverBase {

 /**
  * {@inheritdoc}
  */
 public function getDerivativeDefinitions($base_plugin_definition) {
   // Implement dynamic logic to provide values for the same keys as in example.links.task.yml.
   $id = \Drupal::routeMatch()->getRawParameter('item');
   $item = Item::load($id);

   if ($item instanceof ItemInterface) {
     switch($item->getStatus()) {
       case ItemInterface::STATUS_NEW:
         $this->derivatives['entity.item.action'] = $base_plugin_definition;
         $this->derivatives['entity.item.action']['title'] = t('Do something');
         $this->derivatives['entity.item.action']['route_name'] = 'entity.item.action';
         break;

         // add more derivative definitions here, depending on the item's state
     }
   }
   return $this->derivatives;
 }
}

and in mymodule.links.action.yml:

entity.item.state_actions:
  deriver: '\Drupal\mymodule\Plugin\Derivative\ItemLocalActions'
  cache_tags:
    - item_list
  appears_on:
    - entity.item.canonical

As stated above, caching is still a problem. Even though I set the appropriate cache tags, the actions are still rendered from cache.

Related questions:

5
  • Sorry to hear about the caching issue, not sure how to deal with that. One thing I'll note, though, is that you should probably create the access method in my answer as well as what you're doing here for the local task. This is because if someone should hit the url directly they would still be able to perform the actions defined by your controller.
    – sonfd
    Commented Apr 8, 2018 at 13:51
  • What exactly are you cache issues? Are you sure you're defining cache info correctly? Is the item remaining available even after the value on your entity changes? or is it some other issue?
    – sonfd
    Commented Apr 8, 2018 at 13:52
  • The caching issue is this: after I've saved the item and returned to the item's canonical page, the action buttons do not get rebuilt. The entity display reflects the new values, but the action buttons don't. I tried to achieve this with various cache settings (cache_tags, cache_max_age) on the local action route, but without success. See the link in my answer, this is pretty much my issue.
    – Hendrik
    Commented Apr 8, 2018 at 18:16
  • Regarding access: thanks for pointing this out. The route that points to the actual controller has all the access attributes. Do I need to have them on the local action route as well?
    – Hendrik
    Commented Apr 8, 2018 at 18:18
  • 1
    Re: route access, you're route definition in your question, the only requirement is edit items, meaning that anyone that could edit the item could hit your controller regardless of the field value you're using to show/hide the local task. An editor with the edit items permission could access your controller by hitting the url directly. Re: the tasks not changing when you edit the node, that sounds like a core bug if you're adding the node:5 (nid) and/or node_list cache tags and the tasks aren't updating. The solution on the link you reference uses the ENTITY_TYPE_list tag to resolve.
    – sonfd
    Commented Apr 9, 2018 at 1:11

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.