0

I have a Drupal 7 module that uses hook_preprocess_menu_link() to hide menu links based on user access. I am in the process of migrating to Drupal 9, and it seems that hook_preprocess_menu_link() might not be available or suitable in Drupal 9.

/**                                                                                                                                                                     
 * Implements hook_preprocess_menu_link().
 */
function my_menu_access_preprocess_menu_link(&$vars) {
  global $user;
  $vars_href = $vars['element']['#href'];

  // get facilities the user is associated with.                                                                                                                  
  $user_facilities = user_facilities($user);

  // hidden class is added to hide the menu link from user.                                                                                                       
  if (strpos($vars_href, 'some-menu-path') !== false) {
    $resource_facility_nid = get_facility_nid_from_resource_path($vars_href);
    if (!in_array('administrator', $user->roles) &&
      !in_array($resource_facility_nid, $user_facilities)) {
      $vars['element']['#attributes']['class'] = array('hidden');
    }
  }

  if (strpos($vars_href, 'other-menu-path') !== false) {
    $outage_facility_nid = get_facility_nid_from_outage_path($vars_href);
    if (!in_array('administrator', $user->roles) &&
      !in_array($outage_facility_nid, $user_facilities)) {
      $vars['element']['#attributes']['class'] = array('hidden');
    }
  }
}
1

1 Answer 1

1

I prefer controlling this behavior via access to the entity rather than at the menu level because it means that anyone with a direct link to the entity won't have access AND if a user does not have access to a page (via Drupal permissions / access) then the menu link will be hidden by default (it won't be rendered at all, rather than simply hidden via JS or CSS).

One way that you could do this is via hook_ENTITY_TYPE_access() and a custom permission. I would define my own permission, something like view own facilities or something similar. (See How do you create a custom permission to administer a custom module?.)

Assuming I've defined my custom permission as view own facilities (and a bonus view all facilities permission for administrators) and configured roles appropriately, I could define my access hook something like:

function mymodule_ENTITY_TYPE_access(EntityInterface $entity, string $operation, AccountInterface $account) {
  if ($entity->bundle() == 'facility' && $operation === 'view') {
    if ($account->hasPermission('view all facilities')) {
      return AccessResult::allowed()
        ->cachePerPermissions();
    }

    // The operation we care about, and the user has the permission we care
    // about, so we now just need to check if they should have access to the
    // particular facility. Whether they do or not, any results at this level
    // should be cached per user since each user can reference different
    // facilities.
    if ($account->hasPermission('view own facilities')) {
      // user_has_facility is a custom function you should write that takes a
      // user ID and a facility ID and returns TRUE if the user references, i.e.
      // has access to, the particular facility.
      if (user_has_facility($account->id(), $entity->id())) {
        // Here we allow access because the user has the correct permission AND
        // they have access to this specific facility.
        return AccessResult::allowed()
          ->cachePerPermissions()
          ->cachePerUser();
      }
      else {
        // The user has the correct permission, but is missing access to this
        // specific facility. We return neutral so some other mechanism could
        // grant access if that other mechanism deems appropriate.
        return AccessResult::neutral()
          ->cachePerPermissions()
          ->cachePerUser();
      }
    }
    // The operation we care about, but the user does not have the permission we
    // care about, so return neutral (so some other mecahnism could grant access
    // if the other mechanism deems appropriate), but since this decision is based
    // on a permission, make sure we cache the result per permissions.
    else {
      return AccessResult::neutral()
        ->cachePerPermissions();
    }
  }

  // No opinion: some other operation we're not interacting with.
  return AccessResult::neutral();
}
3
  • I hope the OP knows he must replace ENTITY_TYPE in mymodule_ENTITY_TYPE_access with the correct entity type for that entity. (Since the question does not say which entity that is, the answer uses a placeholder for that.)
    – avpaderno
    Commented Jan 9 at 17:26
  • What should I replace ENTITY_TYPE with in this case? I'm dealing with menu sub items
    – user106854
    Commented Jan 9 at 17:38
  • It depends on what type of entity your facilities are. My guess is that they're nodes (you reference nids in your question), so you should use mymodule_node_access().
    – sonfd
    Commented Jan 9 at 18:10

Your Answer

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