4

I am using hook_link_alter(&$variables) to alter links depending on the user's role. I'm doing this for links in the main menu as well as for custom links shown on entities. This works good except caching. If I change a user's role still the previous behaviour of the altered links is shown. If I clear all caches, it works fine again.

I thought of adding cache context user.roles. But hook_link_alter seems not to support to add caching meta data to $variables see https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Menu%21menu.api.php/function/hook_link_alter/8.5.x. (Or I'm doing it wrong.)

What can I do to make caching of my altered links aware of the user's roles?

My code looks like in hook_link_alter(&$variables)

$roles = \Drupal::currentUser()->getRoles();

if(!in_array('member_gold', $roles)) {

  $url = $variables['url'];

  if (($url->isRouted() && $url->getRouteName() === 'view.my_content.page') OR ... ) {

    $url = Url::fromRoute('myModule.ajax_callback', ['op' => 'do_action']);
    $token = \Drupal::service('csrf_token')->get($url->getInternalPath());

    $variables['url'] = $url;
    $variables['options']['query']['token'] = $token;
    $variables['options']['attributes']['class'][] = 'use-ajax';

  }

}

Update:

The links that I alter are in a) the main menu, b) in pseudo fields and c) by other modules. I succeeded adding some cache context to a) and b) and skipped c). It seems to work, but I don't know why, it was more or less trial and error:

For the menu links: function mymodule_preprocess_menu(&$variables) {

if(isset($variables['menu_name']) && $variables['menu_name'] == 'main') {

  $variables['#cache']['contexts'][] = 'user.roles';
  $renderer = \Drupal::service('renderer');
  $renderer->addCacheableDependency($variables, \Drupal\user\Entity\User::load(\Drupal::currentUser()->id()));
}

In my pseudo fields:

$build['action_my_action'] = [
    ...
    '#cache' => [
      'contexts' => ['user.roles'],
    ],
  ];

  $renderer = \Drupal::service('renderer');
  $renderer->addCacheableDependency($build, \Drupal\user\Entity\User::load(\Drupal::currentUser()->id()));

I don't understand why this works. I initially only tried adding the cache context, but this did not work. Only if I add the dependency to the current user. However I don't know whether this is correct, as I don't want to vary by user but by role.

Would be great if someone can explain me if this is right and why it works?

2
  • ^ I too am curious about the need for ->addCacheableDependency() and how it aligns with a given cache context. Commented Jun 2, 2019 at 23:56
  • I created a core issue to fix this cache problem with hook_link_alter: drupal.org/project/drupal/issues/3082473
    – Brian
    Commented Sep 20, 2019 at 1:05

2 Answers 2

1

Since you can't have the role sensitivity reliably in the links themselves, I'd tackle this differently by using an intermediary routing function.

So if you have a link you want to point to mypath/role_1, mypath/role_2 etc, you might point it to role_route/somekey defined in mymodule.routing.yml like so:

mymodule.role_route:
 path: 'role_route/{key}'
 defaults: 
  _controller: '\Drupal\mymodule\Controller\RoleRouteController::route' 
 requirements: 
   _permission: 'access role paths' 

The route function of the controller might be something like:

function route($key) {
  $current_user = \Drupal::currentUser();
  $roles = $current_user->getRoles();
  // logic to process roles and key to get wanted route
  ...
  $role_route_name = ... ;
  return new RedirectResponse( Url::fromRoute($role_route_name);
}
12
  • Nice idea, but I'm also changing the links to use ajax $variables['options']['attributes']['class'][] = 'use-ajax'; I don't see a possibility to do this in an intermediate controller ? I'll update my question.
    – Sebastian
    Commented Jul 13, 2018 at 14:01
  • You can still change the links to use AJAX using your current method, just not change anything per role. The function called via AJAX can do different things based on user role. Commented Jul 13, 2018 at 14:17
  • But this means that all links would be ajaxified by default and that I have also to pass the original url to the intermediate function called via ajax. I'll try. But sounds quite complex. There is no way to add cache information to the links?
    – Sebastian
    Commented Jul 13, 2018 at 14:43
  • Without know more about how the site is built it's hard to advise. There's lots of ways to solve problems like this. Perhaps start earlier in the page creation process where the links /can/ be cached per role. What's outputting them? Commented Jul 13, 2018 at 15:07
  • Currently I have 3 types of links that I modify: 1. menu links inserted by views and created through the ui. 2. links placed by myself using pseudo fields. 3. links by other modules
    – Sebastian
    Commented Jul 13, 2018 at 15:10
0

You need to add the cache context to the actual element's render array, not to the overall $variables array. So you need something like:

$variables['my_link_element']['#cache']['contexts'] = ['user.role'];

Instead of what you're currently doing now which I'm guessing is something like:

$variables['#cache']['contexts'] = ['user.role'];
3
  • 2
    There is no element in $varialbes, see api.drupal.org/api/drupal/…. $variables does not contain a render array as far as I understand.
    – Sebastian
    Commented Jul 13, 2018 at 10:48
  • Sorry, my bad, should've looked at what hook_link_alter does. Thanks for clarifying.
    – Beebee
    Commented Jul 13, 2018 at 12:51
  • Any other ideas? I have also searched for a some hook like hook_preprocess_menu where I can add cache context. But I haven't found anything similar for links.
    – Sebastian
    Commented Jul 13, 2018 at 13:15

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.