LocalActionManager.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <?php
  2. namespace Drupal\Core\Menu;
  3. use Drupal\Core\Access\AccessManagerInterface;
  4. use Drupal\Core\Cache\CacheableMetadata;
  5. use Drupal\Core\Cache\CacheBackendInterface;
  6. use Drupal\Core\Extension\ModuleHandlerInterface;
  7. use Drupal\Core\Language\LanguageManagerInterface;
  8. use Drupal\Core\Plugin\DefaultPluginManager;
  9. use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
  10. use Drupal\Core\Plugin\Discovery\YamlDiscovery;
  11. use Drupal\Core\Plugin\Factory\ContainerFactory;
  12. use Drupal\Core\Routing\RouteMatchInterface;
  13. use Drupal\Core\Routing\RouteProviderInterface;
  14. use Drupal\Core\Url;
  15. use Symfony\Component\HttpFoundation\RequestStack;
  16. use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
  17. use Drupal\Core\Session\AccountInterface;
  18. /**
  19. * Provides the default local action manager using YML as primary definition.
  20. */
  21. class LocalActionManager extends DefaultPluginManager implements LocalActionManagerInterface {
  22. /**
  23. * Provides some default values for all local action plugins.
  24. *
  25. * @var array
  26. */
  27. protected $defaults = [
  28. // The plugin id. Set by the plugin system based on the top-level YAML key.
  29. 'id' => NULL,
  30. // The static title for the local action.
  31. 'title' => '',
  32. // The weight of the local action.
  33. 'weight' => NULL,
  34. // (Required) the route name used to generate a link.
  35. 'route_name' => NULL,
  36. // Default route parameters for generating links.
  37. 'route_parameters' => [],
  38. // Associative array of link options.
  39. 'options' => [],
  40. // The route names where this local action appears.
  41. 'appears_on' => [],
  42. // Default class for local action implementations.
  43. 'class' => 'Drupal\Core\Menu\LocalActionDefault',
  44. ];
  45. /**
  46. * A controller resolver object.
  47. *
  48. * @var \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface
  49. */
  50. protected $controllerResolver;
  51. /**
  52. * The request stack.
  53. *
  54. * @var \Symfony\Component\HttpFoundation\RequestStack
  55. */
  56. protected $requestStack;
  57. /**
  58. * The current route match.
  59. *
  60. * @var \Drupal\Core\Routing\RouteMatchInterface
  61. */
  62. protected $routeMatch;
  63. /**
  64. * The route provider to load routes by name.
  65. *
  66. * @var \Drupal\Core\Routing\RouteProviderInterface
  67. */
  68. protected $routeProvider;
  69. /**
  70. * The access manager.
  71. *
  72. * @var \Drupal\Core\Access\AccessManagerInterface
  73. */
  74. protected $accessManager;
  75. /**
  76. * The current user.
  77. *
  78. * @var \Drupal\Core\Session\AccountInterface
  79. */
  80. protected $account;
  81. /**
  82. * The plugin instances.
  83. *
  84. * @var \Drupal\Core\Menu\LocalActionInterface[]
  85. */
  86. protected $instances = [];
  87. /**
  88. * Constructs a LocalActionManager object.
  89. *
  90. * @param \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface $controller_resolver
  91. * An object to use in introspecting route methods.
  92. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
  93. * The request stack.
  94. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  95. * The current route match.
  96. * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
  97. * The route provider.
  98. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  99. * The module handler.
  100. * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
  101. * Cache backend instance to use.
  102. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  103. * The language manager.
  104. * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
  105. * The access manager.
  106. * @param \Drupal\Core\Session\AccountInterface $account
  107. * The current user.
  108. */
  109. public function __construct(ControllerResolverInterface $controller_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account) {
  110. // Skip calling the parent constructor, since that assumes annotation-based
  111. // discovery.
  112. $this->factory = new ContainerFactory($this, 'Drupal\Core\Menu\LocalActionInterface');
  113. $this->controllerResolver = $controller_resolver;
  114. $this->requestStack = $request_stack;
  115. $this->routeMatch = $route_match;
  116. $this->routeProvider = $route_provider;
  117. $this->accessManager = $access_manager;
  118. $this->moduleHandler = $module_handler;
  119. $this->account = $account;
  120. $this->alterInfo('menu_local_actions');
  121. $this->setCacheBackend($cache_backend, 'local_action_plugins:' . $language_manager->getCurrentLanguage()->getId(), ['local_action']);
  122. }
  123. /**
  124. * {@inheritdoc}
  125. */
  126. protected function getDiscovery() {
  127. if (!isset($this->discovery)) {
  128. $yaml_discovery = new YamlDiscovery('links.action', $this->moduleHandler->getModuleDirectories());
  129. $yaml_discovery->addTranslatableProperty('title', 'title_context');
  130. $this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml_discovery);
  131. }
  132. return $this->discovery;
  133. }
  134. /**
  135. * {@inheritdoc}
  136. */
  137. public function getTitle(LocalActionInterface $local_action) {
  138. $controller = [$local_action, 'getTitle'];
  139. $arguments = $this->controllerResolver->getArguments($this->requestStack->getCurrentRequest(), $controller);
  140. return call_user_func_array($controller, $arguments);
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. public function getActionsForRoute($route_appears) {
  146. if (!isset($this->instances[$route_appears])) {
  147. $route_names = [];
  148. $this->instances[$route_appears] = [];
  149. // @todo - optimize this lookup by compiling or caching.
  150. foreach ($this->getDefinitions() as $plugin_id => $action_info) {
  151. if (in_array($route_appears, $action_info['appears_on'])) {
  152. $plugin = $this->createInstance($plugin_id);
  153. $route_names[] = $plugin->getRouteName();
  154. $this->instances[$route_appears][$plugin_id] = $plugin;
  155. }
  156. }
  157. // Pre-fetch all the action route objects. This reduces the number of SQL
  158. // queries that would otherwise be triggered by the access manager.
  159. if (!empty($route_names)) {
  160. $this->routeProvider->getRoutesByNames($route_names);
  161. }
  162. }
  163. $links = [];
  164. /** @var $plugin \Drupal\Core\Menu\LocalActionInterface */
  165. foreach ($this->instances[$route_appears] as $plugin_id => $plugin) {
  166. $cacheability = new CacheableMetadata();
  167. $route_name = $plugin->getRouteName();
  168. $route_parameters = $plugin->getRouteParameters($this->routeMatch);
  169. $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE);
  170. $links[$plugin_id] = [
  171. '#theme' => 'menu_local_action',
  172. '#link' => [
  173. 'title' => $this->getTitle($plugin),
  174. 'url' => Url::fromRoute($route_name, $route_parameters),
  175. 'localized_options' => $plugin->getOptions($this->routeMatch),
  176. ],
  177. '#access' => $access,
  178. '#weight' => $plugin->getWeight(),
  179. ];
  180. $cacheability->addCacheableDependency($access)->addCacheableDependency($plugin);
  181. $cacheability->applyTo($links[$plugin_id]);
  182. }
  183. $links['#cache']['contexts'][] = 'route';
  184. return $links;
  185. }
  186. }