admin_toolbar_links_access_filter.module 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. /**
  3. * @file
  4. * This module don't show menu links that you don't have access permission for.
  5. */
  6. use Drupal\Core\Session\AccountInterface;
  7. use Drupal\Core\Url;
  8. use Drupal\user\Entity\Role;
  9. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  10. use Drupal\Core\Routing\RouteMatchInterface;
  11. /**
  12. * Implements hook_help().
  13. */
  14. function admin_toolbar_links_access_filter_help($route_name, RouteMatchInterface $route_match) {
  15. switch ($route_name) {
  16. // Main module help.
  17. case 'help.page.admin_toolbar_links_access_filter':
  18. $output = '';
  19. $output .= '<h3>' . t('About') . '</h3>';
  20. $output .= '<p>' . t('The Admin Toolbar Links Access Filter module provides a workaround for the common problem that users with <em>Use the administration pages and help</em> permission see menu links they done not have access permission for.') . '</p>';
  21. return $output;
  22. }
  23. }
  24. /**
  25. * Implements hook_preprocess_menu().
  26. *
  27. * Hides links from admin menu, if user doesn't have access rights.
  28. */
  29. function admin_toolbar_links_access_filter_preprocess_menu(&$variables) {
  30. if (empty($variables['items'])) {
  31. // Additional empty check to prevent exotic situations, where the preprocess
  32. // function is entered even without items.
  33. // @see https://www.drupal.org/node/2833885
  34. return;
  35. }
  36. // Ensure that menu_name exists.
  37. if (!isset($variables['menu_name'])) {
  38. // In rare cases (for unknown reasons) menu_name may not be set.
  39. // As fallback, we can fetch it from the first menu item.
  40. $first_link = reset($variables['items']);
  41. /** @var Drupal\Core\Menu\MenuLinkDefault $original_link */
  42. // Fetch the menu_name from the original link.
  43. $original_link = $first_link['original_link'];
  44. $variables['menu_name'] = $original_link->getMenuName();
  45. }
  46. if ($variables['menu_name'] == 'admin') {
  47. if (!admin_toolbar_links_access_filter_user_has_admin_role($variables['user'])) {
  48. admin_toolbar_links_access_filter_filter_non_accessible_links($variables['items']);
  49. }
  50. }
  51. }
  52. /**
  53. * Hides links from admin menu, if user doesn't have access rights.
  54. */
  55. function admin_toolbar_links_access_filter_filter_non_accessible_links(array &$items) {
  56. foreach ($items as $route => &$item) {
  57. $route_name = $route;
  58. $route_params = [];
  59. if (!empty($item['original_link'])) {
  60. /** @var \Drupal\Core\Menu\MenuLinkBase $original_link */
  61. $original_link = $item['original_link'];
  62. if ($original_link->getUrlObject()->isExternal()) {
  63. // Do not filter external URL at all.
  64. continue;
  65. }
  66. $route_name = $original_link->getRouteName();
  67. $route_params = $original_link->getRouteParameters();
  68. }
  69. // Check, if user has access rights to the route.
  70. if (!\Drupal::accessManager()->checkNamedRoute($route_name, $route_params)) {
  71. unset($items[$route]);
  72. }
  73. else {
  74. if (!empty($items[$route]['below'])) {
  75. // Recursively call this function for the child items.
  76. admin_toolbar_links_access_filter_filter_non_accessible_links($items[$route]['below']);
  77. }
  78. if (empty($items[$route]['below']) && \Drupal::moduleHandler()->moduleExists('admin_toolbar')) {
  79. // Every child item has been cleared out.
  80. // Now check, if the given route represents an overview page only,
  81. // without having functionality on its own. In this case, we can safely
  82. // unset this item, as there aren't any children left.
  83. // This assumption is only valid, when the admin_toolbar module is
  84. // installed because otherwise we won't have child items at all.
  85. if (admin_toolbar_links_access_filter_is_overview_page($route)) {
  86. unset($items[$route]);
  87. }
  88. else {
  89. // Let's remove the expanded flag.
  90. $items[$route]['is_expanded'] = FALSE;
  91. }
  92. }
  93. }
  94. }
  95. }
  96. /**
  97. * Implements template_preprocess_admin_block_content().
  98. */
  99. function admin_toolbar_links_access_filter_admin_block_content(&$variables) {
  100. if (!admin_toolbar_links_access_filter_user_has_admin_role($variables['user'])) {
  101. foreach ($variables['content'] as $key => &$item) {
  102. if (isset($item['url']) && $item['url'] instanceof Url) {
  103. /* @var \Drupal\Core\Url $url */
  104. $url = $item['url'];
  105. if ($url->access()) {
  106. continue;
  107. }
  108. unset($variables['content'][$key]);
  109. }
  110. // The key is structured in the form: "ID title route",
  111. // concatenated with spaces.
  112. $key_parts = explode(' ', $key);
  113. $route = end($key_parts);
  114. // Special handling for Views pages, as they are not defined
  115. // system routes.
  116. // @TODO check the permission for Views + find a generic way for similar
  117. // cases. Best way would be to get the link entity somehow to properly
  118. // check permissions.
  119. if (strpos($route, 'views_view:') === 0) {
  120. continue;
  121. }
  122. // Check, if user has access rights to the route.
  123. if (!\Drupal::accessManager()->checkNamedRoute($route)) {
  124. unset($variables['content'][$key]);
  125. }
  126. }
  127. }
  128. }
  129. /**
  130. * Checks if the given route name is an overview page.
  131. *
  132. * Checks if the given route name matches a pure (admin) overview page that can
  133. * be skipped, if there are no child items set. The typical example are routes
  134. * having the SystemController::systemAdminMenuBlockPage() function as their
  135. * controller callback set.
  136. *
  137. * @param string $route_name
  138. * The route name to check.
  139. *
  140. * @return bool
  141. * TRUE, if the given route name matches a pure admin overview page route,
  142. * FALSE otherwise.
  143. */
  144. function admin_toolbar_links_access_filter_is_overview_page($route_name) {
  145. // @var \Drupal\Core\Routing\RouteProviderInterface $route_provider.
  146. $route_provider = \Drupal::service('router.route_provider');
  147. $overview_page_controllers = [
  148. '\Drupal\system\Controller\AdminController::index',
  149. '\Drupal\system\Controller\SystemController::overview',
  150. '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage',
  151. ];
  152. try {
  153. $route = $route_provider->getRouteByName($route_name);
  154. $controller = $route->getDefault('_controller');
  155. return !empty($controller) && in_array($controller, $overview_page_controllers);
  156. }
  157. catch (RouteNotFoundException $ex) {
  158. }
  159. return FALSE;
  160. }
  161. /**
  162. * Checks, if the given user has admin rights.
  163. *
  164. * @param \Drupal\Core\Session\AccountInterface $account
  165. * The account to check.
  166. *
  167. * @return bool
  168. * TRUE, if the given user account has at least one role with admin rights
  169. * assigned, FALSE otherwise.
  170. */
  171. function admin_toolbar_links_access_filter_user_has_admin_role(AccountInterface $account) {
  172. static $user_has_admin_role = [];
  173. $uid = $account->id();
  174. if (!isset($user_has_admin_role[$uid])) {
  175. $roles = Role::loadMultiple($account->getRoles());
  176. foreach ($roles as $role) {
  177. if ($role->isAdmin()) {
  178. $user_has_admin_role[$uid] = TRUE;
  179. break;
  180. }
  181. $user_has_admin_role[$uid] = FALSE;
  182. }
  183. }
  184. return $user_has_admin_role[$uid];
  185. }