localgovdrupal / localgov_tasty_backend

Tasty backend intergration with Localgov Drupal
GNU General Public License v2.0
0 stars 0 forks source link

Filter inaccessible items #15

Open andybroomfield opened 6 months ago

andybroomfield commented 6 months ago

Sometimes if a user does not have access to any child items, the Moderate content / site features menu can still display. This is because the admin_toolbar_filter_links module only works on the admin menu. We need to duplicate it's functionality either here, or in the main Tasty backend module.

Below is what I changed admin_toolbar_access_fix.module to in order to fix this.

/**
 * Implements hook_preprocess_menu().
 *
 * Hides links from admin menu, if user doesn't have access rights.
 */
function admin_toolbar_links_access_filter_preprocess_menu(&$variables) {
  if (empty($variables['items'])) {
    // Additional empty check to prevent exotic situations, where the preprocess
    // function is entered even without items.
    // @see https://www.drupal.org/node/2833885
    return;
  }
  // Ensure that menu_name exists.
  if (!isset($variables['menu_name'])) {
    // In rare cases (for unknown reasons) menu_name may not be set.
    // As fallback, we can fetch it from the first menu item.
    $first_link = reset($variables['items']);
    /** @var Drupal\Core\Menu\MenuLinkDefault $original_link */
    // Fetch the menu_name from the original link.
    $original_link = $first_link['original_link'];
    $variables['menu_name'] = $original_link->getMenuName();
  }

  if ($variables['menu_name'] == 'admin' || $variables['menu_name'] == 'tb-manage') {
    if (!admin_toolbar_links_access_filter_user_has_admin_role($variables['user'])) {
      admin_toolbar_links_access_filter_filter_non_accessible_links($variables['items']);
    }
  }
}

/**
 * Hides links from admin menu, if user doesn't have access rights.
 */
function admin_toolbar_links_access_filter_filter_non_accessible_links(array &$items) {
  if (Drupal::currentUser()->id() == 1) {
    // Admin can access everything.
    return;
  }

  $access_manager = \Drupal::accessManager();
  foreach ($items as $menu_id => &$item) {
    try {
      $route_name = NULL;
      $route_params = [];
      if (!empty($item['original_link'])) {
        /** @var \Drupal\Core\Menu\MenuLinkBase $original_link */
        $original_link = $item['original_link'];
        if ($original_link->getUrlObject()->isExternal() || !$original_link->getUrlObject()->isRouted()) {
          // Do not filter external URL at all.
          continue;
        }
        $route_name = $original_link->getRouteName() ?: $original_link->getUrlObject()->getRouteName();
        $route_params = $original_link->getRouteParameters() ?: $original_link->getUrlObject()->getRouteParameters();
      }
      elseif (!empty($item['url'])) {
        /** @var \Drupal\Core\Url $url */
        $url = $item['url'];
        if ($url->isExternal() || !$original_link->getUrlObject()->isRouted()) {
          // Do not filter external URL at all.
          continue;
        }
        $route_name = $url->getRouteName();
        $route_params = $url->getRouteParameters();
      }

      // Check, if user has access rights to the route.
      if (!$access_manager->checkNamedRoute($route_name, $route_params)) {
        unset($items[$menu_id]);
      }
      else {
        if (!empty($items[$menu_id]['below'])) {
          // Recursively call this function for the child items.
          admin_toolbar_links_access_filter_filter_non_accessible_links($items[$menu_id]['below']);
        }
        if (empty($items[$menu_id]['below'])) {

          // Every child item has been cleared out.
          // Now check, if the given route represents an overview page only,
          // without having functionality on its own. In this case, we can
          // safely unset this item, as there aren't any children left.
          // This assumption is only valid, when the admin_toolbar module is
          // installed because otherwise we won't have child items at all.
          if (admin_toolbar_links_access_filter_is_overview_page($route_name)) {
            unset($items[$menu_id]);
          }
          // If there are no sub-items and the parent does not have a link, then
          // it is safe to remove it.
          elseif ($route_name === '<nolink>') {
            unset($items[$menu_id]);
          }
          else {
            // Let's remove the expanded flag.
            $items[$menu_id]['is_expanded'] = FALSE;
          }
        }
      }
    }
    catch (\UnexpectedValueException $e) {
      // Skip on errors like "base:block has no corresponding route":
      \Drupal::logger('my_module')->error($e->getMessage());
      continue;
    }
  }
}

/**
 * Checks if the given route name is an overview page.
 *
 * Checks if the given route name matches a pure (admin) overview page that can
 * be skipped, if there are no child items set. The typical example are routes
 * having the SystemController::systemAdminMenuBlockPage() function as their
 * controller callback set.
 *
 * @param string $route_name
 *   The route name to check.
 *
 * @return bool
 *   TRUE, if the given route name matches a pure admin overview page route,
 *   FALSE otherwise.
 */
function admin_toolbar_links_access_filter_is_overview_page($route_name) {
  /** @var \Drupal\Core\Routing\RouteProviderInterface $route_provider. */
  $route_provider = \Drupal::service('router.route_provider');
  $overview_page_controllers = [
    '\Drupal\system\Controller\AdminController::index',
    '\Drupal\system\Controller\SystemController::overview',
    '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage',
    '\Drupal\tasty_backend\Controller\TastyBackendController::menuBlockContents',
  ];
  try {
    $route = $route_provider->getRouteByName($route_name);
    $controller = $route->getDefault('_controller');
    return !empty($controller) && in_array($controller, $overview_page_controllers);
  }
  catch (RouteNotFoundException $ex) {

  }
  return FALSE;
}

/**
 * Checks, if the given user has admin rights.
 *
 * @param \Drupal\Core\Session\AccountInterface $account
 *   The account to check.
 *
 * @return bool
 *   TRUE, if the given user account has at least one role with admin rights
 *   assigned, FALSE otherwise.
 */
function admin_toolbar_links_access_filter_user_has_admin_role(AccountInterface $account) {
  static $user_has_admin_role = [];
  $uid = $account->id();
  if (!isset($user_has_admin_role[$uid])) {
    $user_has_admin_role[$uid] = FALSE;
    $roles = Role::loadMultiple($account->getRoles());
    // It is possible for a user account to have no roles assigned.
    if (!empty($roles)) {
      foreach ($roles as $role) {
        if ($role->isAdmin()) {
          $user_has_admin_role[$uid] = TRUE;
          break;
        }
      }
    }
  }
  return $user_has_admin_role[$uid];
}