Chris@0: NULL, Chris@0: // The static title for the local action. Chris@0: 'title' => '', Chris@0: // The weight of the local action. Chris@0: 'weight' => NULL, Chris@0: // (Required) the route name used to generate a link. Chris@0: 'route_name' => NULL, Chris@0: // Default route parameters for generating links. Chris@0: 'route_parameters' => [], Chris@0: // Associative array of link options. Chris@0: 'options' => [], Chris@0: // The route names where this local action appears. Chris@0: 'appears_on' => [], Chris@0: // Default class for local action implementations. Chris@0: 'class' => 'Drupal\Core\Menu\LocalActionDefault', Chris@0: ]; Chris@0: Chris@0: /** Chris@17: * An argument resolver object. Chris@17: * Chris@17: * @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface Chris@17: */ Chris@17: protected $argumentResolver; Chris@17: Chris@17: /** Chris@0: * A controller resolver object. Chris@0: * Chris@0: * @var \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface Chris@17: * Chris@17: * @deprecated Chris@17: * Using the 'controller_resolver' service as the first argument is Chris@17: * deprecated, use the 'http_kernel.controller.argument_resolver' instead. Chris@17: * If your subclass requires the 'controller_resolver' service add it as an Chris@17: * additional argument. Chris@17: * Chris@17: * @see https://www.drupal.org/node/2959408 Chris@0: */ Chris@0: protected $controllerResolver; Chris@0: Chris@0: /** Chris@0: * The request stack. Chris@0: * Chris@0: * @var \Symfony\Component\HttpFoundation\RequestStack Chris@0: */ Chris@0: protected $requestStack; Chris@0: Chris@0: /** Chris@0: * The current route match. Chris@0: * Chris@0: * @var \Drupal\Core\Routing\RouteMatchInterface Chris@0: */ Chris@0: protected $routeMatch; Chris@0: Chris@0: /** Chris@0: * The route provider to load routes by name. Chris@0: * Chris@0: * @var \Drupal\Core\Routing\RouteProviderInterface Chris@0: */ Chris@0: protected $routeProvider; Chris@0: Chris@0: /** Chris@0: * The access manager. Chris@0: * Chris@0: * @var \Drupal\Core\Access\AccessManagerInterface Chris@0: */ Chris@0: protected $accessManager; Chris@0: Chris@0: /** Chris@0: * The current user. Chris@0: * Chris@0: * @var \Drupal\Core\Session\AccountInterface Chris@0: */ Chris@0: protected $account; Chris@0: Chris@0: /** Chris@0: * The plugin instances. Chris@0: * Chris@0: * @var \Drupal\Core\Menu\LocalActionInterface[] Chris@0: */ Chris@0: protected $instances = []; Chris@0: Chris@0: /** Chris@0: * Constructs a LocalActionManager object. Chris@0: * Chris@17: * @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver Chris@17: * An object to use in resolving route arguments. Chris@0: * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack Chris@0: * The request stack. Chris@0: * @param \Drupal\Core\Routing\RouteMatchInterface $route_match Chris@0: * The current route match. Chris@0: * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider Chris@0: * The route provider. Chris@0: * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler Chris@0: * The module handler. Chris@0: * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend Chris@0: * Cache backend instance to use. Chris@0: * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager Chris@0: * The language manager. Chris@0: * @param \Drupal\Core\Access\AccessManagerInterface $access_manager Chris@0: * The access manager. Chris@0: * @param \Drupal\Core\Session\AccountInterface $account Chris@0: * The current user. Chris@0: */ Chris@17: public function __construct(ArgumentResolverInterface $argument_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account) { Chris@0: // Skip calling the parent constructor, since that assumes annotation-based Chris@0: // discovery. Chris@0: $this->factory = new ContainerFactory($this, 'Drupal\Core\Menu\LocalActionInterface'); Chris@17: $this->argumentResolver = $argument_resolver; Chris@17: if ($argument_resolver instanceof ControllerResolverInterface) { Chris@17: @trigger_error("Using the 'controller_resolver' service as the first argument is deprecated, use the 'http_kernel.controller.argument_resolver' instead. If your subclass requires the 'controller_resolver' service add it as an additional argument. See https://www.drupal.org/node/2959408.", E_USER_DEPRECATED); Chris@17: $this->controllerResolver = $argument_resolver; Chris@17: } Chris@0: $this->requestStack = $request_stack; Chris@0: $this->routeMatch = $route_match; Chris@0: $this->routeProvider = $route_provider; Chris@0: $this->accessManager = $access_manager; Chris@0: $this->moduleHandler = $module_handler; Chris@0: $this->account = $account; Chris@0: $this->alterInfo('menu_local_actions'); Chris@0: $this->setCacheBackend($cache_backend, 'local_action_plugins:' . $language_manager->getCurrentLanguage()->getId(), ['local_action']); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function getDiscovery() { Chris@0: if (!isset($this->discovery)) { Chris@0: $yaml_discovery = new YamlDiscovery('links.action', $this->moduleHandler->getModuleDirectories()); Chris@0: $yaml_discovery->addTranslatableProperty('title', 'title_context'); Chris@0: $this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml_discovery); Chris@0: } Chris@0: return $this->discovery; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getTitle(LocalActionInterface $local_action) { Chris@0: $controller = [$local_action, 'getTitle']; Chris@17: $arguments = $this->argumentResolver->getArguments($this->requestStack->getCurrentRequest(), $controller); Chris@0: return call_user_func_array($controller, $arguments); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getActionsForRoute($route_appears) { Chris@0: if (!isset($this->instances[$route_appears])) { Chris@0: $route_names = []; Chris@0: $this->instances[$route_appears] = []; Chris@0: // @todo - optimize this lookup by compiling or caching. Chris@0: foreach ($this->getDefinitions() as $plugin_id => $action_info) { Chris@0: if (in_array($route_appears, $action_info['appears_on'])) { Chris@0: $plugin = $this->createInstance($plugin_id); Chris@0: $route_names[] = $plugin->getRouteName(); Chris@0: $this->instances[$route_appears][$plugin_id] = $plugin; Chris@0: } Chris@0: } Chris@0: // Pre-fetch all the action route objects. This reduces the number of SQL Chris@0: // queries that would otherwise be triggered by the access manager. Chris@0: if (!empty($route_names)) { Chris@0: $this->routeProvider->getRoutesByNames($route_names); Chris@0: } Chris@0: } Chris@0: $links = []; Chris@17: $cacheability = new CacheableMetadata(); Chris@17: $cacheability->addCacheContexts(['route']); Chris@0: /** @var $plugin \Drupal\Core\Menu\LocalActionInterface */ Chris@0: foreach ($this->instances[$route_appears] as $plugin_id => $plugin) { Chris@0: $route_name = $plugin->getRouteName(); Chris@0: $route_parameters = $plugin->getRouteParameters($this->routeMatch); Chris@0: $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE); Chris@0: $links[$plugin_id] = [ Chris@0: '#theme' => 'menu_local_action', Chris@0: '#link' => [ Chris@0: 'title' => $this->getTitle($plugin), Chris@0: 'url' => Url::fromRoute($route_name, $route_parameters), Chris@0: 'localized_options' => $plugin->getOptions($this->routeMatch), Chris@0: ], Chris@0: '#access' => $access, Chris@0: '#weight' => $plugin->getWeight(), Chris@0: ]; Chris@0: $cacheability->addCacheableDependency($access)->addCacheableDependency($plugin); Chris@0: } Chris@17: $cacheability->applyTo($links); Chris@0: Chris@0: return $links; Chris@0: } Chris@0: Chris@0: }