5

I need to alter the access of menu links before being rendered. In Drupal 7, we used hook_translated_menu_link_alter() for it. Drupal 8 change records show that there are no direct replacement for that hook.

Is there any other hook or controller to alter the access of the menu link before it is rendered?

3 Answers 3

7

After some trial and error an researching and researching I found out how to get it done.

Case: The aim is to avoid that any other user than the admin can access administration pages starting with path "/admin/config/*". The point is that we have a administrator role in our project who should have access to the menu administration pages (they need the permission "Use the administration pages and help" to get to this administration page). With this permission they also see the "Configuration" menu item in toolbar and all sub menu items like "System" but these pages are empty.

How to achive that by altering the access checks

First define your own RouteSubscribe class in folder/file your_module/src/Routing/AdminRouteSubscriber.php:

namespace Drupal\your_module\Routing;


use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;


class AdminRouteSubscriber extends RouteSubscriberBase  {


  /**
   * {@inheritdoc}
   */
  public function alterRoutes(RouteCollection $collection) {

    $path = '/admin/config';
    foreach ($collection->all() as $routename => $route) {
      if (substr($route->getPath(), 0, 13) == $path) {
        $route->setRequirement(
          '_custom_access',
          '\Drupal\your_module\AccessChecks\CustomAccessChecks::access'
        );
      }
    }
  }
}

What we did here is to check the path of every route and if it starts with "/admin/config" it gets added our custom AccessCheck class defining the method "admimAccess" to be called.

Than go ahead and define your own access check class in folder/file your_module/src/AccessChecks/CustomAccessChecks.php:

namespace Drupal\your_module\AccessChecks;



use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResultAllowed;
use Symfony\Component\Routing\Route;
use Drupal\Core\Routing\RouteMatchInterface;



class CustomAccessChecks implements AccessInterface {


  /**
   * A custom access check.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for this account.
   */
  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
    return AccessResultAllowed::allowedIf((int) $account->id() === 1);
  }
}

This access method has to return an implementation of AccessResultInterface. We say that access is allowed if the current user has the id 1.

Lastly you need to add these classes to the service container by defining your your_module.services.yml in your module's root folder:

services:
  your_module.route_subscriber:
    class: Drupal\your_module\Routing\AdminRouteSubscriber
    tags:
      - { name: event_subscriber }
  your_module.access_checker:
      class: Drupal\your_module\AccessChecks\CustomAccessChecks
      tags:
        - { name: access_check }

After rebuilding your cache it works.

1
  • This approach works really well, thanks for sharing.
    – bmunslow
    Jul 25, 2017 at 10:46
4

When having slugs in routes, for eg: block_content.add_form from core block content has {block_content_type} as slug. Following example can help.

example.services.yml

access_check.block.add:
  class: Drupal\example\Access\ExampleAccessCheck
  arguments: ['@current_user']
  tags:
    - { name: access_check, applies_to: _example_access_check }

RouteSubscriber.php

<?php

namespace Drupal\example\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route events.
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    $route = $collection->get('block_content.add_form');
    if ($route) {
      $route->setRequirements([
        '_example_access_check' => 'TRUE',
      ]);
    }
  }

}

ExampleAccessCheck.php

<?php

namespace Drupal\example\Access;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Determines access to for block add pages.
 */
class ExampleAccessCheck implements AccessInterface {

  /**
   * Checks access to the block add page for the block type.
   */
  public function access($block_content_type, AccountInterface $account) {
    return AccessResult::allowedIf($account->hasPermission("some permission"));
  }

}

$block_content_type is available which can be used to alter menu links for specific block type.

0

If you have a route as input of your menu link (you usually do), you may use custom_access checks like here:

example:
  path: '/example'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::content'
  requirements:
    _custom_access:  '\Drupal\example\Controller\ExampleController::access'

(Taken from https://www.drupal.org/node/2122195)

2
  • Sorry. I need to alter a existing menu links permission while rendering. Not to create a new one. Jun 20, 2016 at 8:26
  • You can still go for the _custom_access, but you need a event subscriber to change the requirement of the existing route. Read this for a start: drupal.org/node/2187643 Jun 20, 2016 at 9:40

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.