Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. LocalTaskManager.php

class LocalTaskManager

Provides the default local task manager using YML as primary definition.

Hierarchy

  • class \Drupal\Component\Plugin\PluginManagerBase implements \Drupal\Component\Plugin\PluginManagerInterface uses \Drupal\Component\Plugin\Discovery\DiscoveryTrait
    • class \Drupal\Core\Plugin\DefaultPluginManager extends \Drupal\Component\Plugin\PluginManagerBase implements \Drupal\Component\Plugin\PluginManagerInterface, \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface, \Drupal\Core\Cache\CacheableDependencyInterface uses \Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait, \Drupal\Core\Cache\UseCacheBackendTrait
      • class \Drupal\Core\Menu\LocalTaskManager extends \Drupal\Core\Plugin\DefaultPluginManager implements \Drupal\Core\Menu\LocalTaskManagerInterface

Expanded class hierarchy of LocalTaskManager

File

core/lib/Drupal/Core/Menu/LocalTaskManager.php, line 27

Namespace

Drupal\Core\Menu
View source
class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerInterface {
    
    /**
     * {@inheritdoc}
     */
    protected $defaults = [
        // (required) The name of the route this task links to.
'route_name' => '',
        // Parameters for route variables when generating a link.
'route_parameters' => [],
        // The static title for the local task.
'title' => '',
        // The route name where the root tab appears.
'base_route' => '',
        // The plugin ID of the parent tab (or NULL for the top-level tab).
'parent_id' => NULL,
        // The weight of the tab.
'weight' => NULL,
        // The default link options.
'options' => [],
        // Default class for local task implementations.
'class' => 'Drupal\\Core\\Menu\\LocalTaskDefault',
        // The plugin id. Set by the plugin system based on the top-level YAML key.
'id' => '',
    ];
    
    /**
     * An argument resolver object.
     *
     * @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface
     */
    protected $argumentResolver;
    
    /**
     * The request stack.
     *
     * @var \Symfony\Component\HttpFoundation\RequestStack
     */
    protected $requestStack;
    
    /**
     * The current route match.
     *
     * @var \Drupal\Core\Routing\RouteMatchInterface
     */
    protected $routeMatch;
    
    /**
     * The plugin instances.
     *
     * @var array
     */
    protected $instances = [];
    
    /**
     * The local task render arrays for the current route.
     *
     * @var array
     */
    protected $taskData;
    
    /**
     * The route provider to load routes by name.
     *
     * @var \Drupal\Core\Routing\RouteProviderInterface
     */
    protected $routeProvider;
    
    /**
     * The access manager.
     *
     * @var \Drupal\Core\Access\AccessManagerInterface
     */
    protected $accessManager;
    
    /**
     * The current user.
     *
     * @var \Drupal\Core\Session\AccountInterface
     */
    protected $account;
    
    /**
     * Constructs a \Drupal\Core\Menu\LocalTaskManager object.
     *
     * @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver
     *   An object to use in resolving route arguments.
     * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
     *   The request object to use for building titles and paths for plugin instances.
     * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
     *   The current route match.
     * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
     *   The route provider to load routes by name.
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
     *   The module handler.
     * @param \Drupal\Core\Cache\CacheBackendInterface $cache
     *   The cache backend.
     * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
     *   The language manager.
     * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
     *   The access manager.
     * @param \Drupal\Core\Session\AccountInterface $account
     *   The current user.
     */
    public function __construct(ArgumentResolverInterface $argument_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account) {
        $this->factory = new ContainerFactory($this, '\\Drupal\\Core\\Menu\\LocalTaskInterface');
        $this->argumentResolver = $argument_resolver;
        $this->requestStack = $request_stack;
        $this->routeMatch = $route_match;
        $this->routeProvider = $route_provider;
        $this->accessManager = $access_manager;
        $this->account = $account;
        $this->moduleHandler = $module_handler;
        $this->alterInfo('local_tasks');
        $this->setCacheBackend($cache, 'local_task_plugins:' . $language_manager->getCurrentLanguage()
            ->getId(), [
            'local_task',
        ]);
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getDiscovery() {
        if (!isset($this->discovery)) {
            $yaml_discovery = new YamlDiscovery('links.task', $this->moduleHandler
                ->getModuleDirectories());
            $yaml_discovery->addTranslatableProperty('title', 'title_context');
            $this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml_discovery);
        }
        return $this->discovery;
    }
    
    /**
     * {@inheritdoc}
     */
    public function processDefinition(&$definition, $plugin_id) {
        parent::processDefinition($definition, $plugin_id);
        // If there is no route name, this is a broken definition.
        if (empty($definition['route_name'])) {
            throw new PluginException(sprintf('Plugin (%s) definition must include "route_name"', $plugin_id));
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function getTitle(LocalTaskInterface $local_task) {
        $controller = [
            $local_task,
            'getTitle',
        ];
        $request = $this->requestStack
            ->getCurrentRequest();
        $arguments = $this->argumentResolver
            ->getArguments($request, $controller);
        return call_user_func_array($controller, $arguments);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getDefinitions() {
        $definitions = parent::getDefinitions();
        $count = 0;
        foreach ($definitions as &$definition) {
            if (isset($definition['weight'])) {
                // Add some micro weight.
                $definition['weight'] += $count++ * 1.0E-6;
            }
        }
        return $definitions;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getLocalTasksForRoute($route_name) {
        if (!isset($this->instances[$route_name])) {
            $this->instances[$route_name] = [];
            if ($cache = $this->cacheBackend
                ->get($this->cacheKey . ':' . $route_name)) {
                $base_routes = $cache->data['base_routes'];
                $parents = $cache->data['parents'];
                $children = $cache->data['children'];
            }
            else {
                $definitions = $this->getDefinitions();
                // We build the hierarchy by finding all tabs that should
                // appear on the current route.
                $base_routes = [];
                $parents = [];
                $children = [];
                foreach ($definitions as $plugin_id => $task_info) {
                    // Fill in the base_route from the parent to insure consistency.
                    if (!empty($task_info['parent_id']) && !empty($definitions[$task_info['parent_id']])) {
                        $task_info['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
                        // Populate the definitions we use in the next loop. Using a
                        // reference like &$task_info causes bugs.
                        $definitions[$plugin_id]['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
                    }
                    if ($route_name == $task_info['route_name']) {
                        if (!empty($task_info['base_route'])) {
                            $base_routes[$task_info['base_route']] = $task_info['base_route'];
                        }
                        // Tabs that link to the current route are viable parents
                        // and their parent and children should be visible also.
                        // @todo This only works for 2 levels of tabs instead
                        //   need to iterate up.
                        $parents[$plugin_id] = TRUE;
                        if (!empty($task_info['parent_id'])) {
                            $parents[$task_info['parent_id']] = TRUE;
                        }
                    }
                }
                if ($base_routes) {
                    // Find all the plugins with the same root and that are at the top
                    // level or that have a visible parent.
                    foreach ($definitions as $plugin_id => $task_info) {
                        if (!empty($base_routes[$task_info['base_route']]) && (empty($task_info['parent_id']) || !empty($parents[$task_info['parent_id']]))) {
                            // Concat '> ' with root ID for the parent of top-level tabs.
                            $parent = empty($task_info['parent_id']) ? '> ' . $task_info['base_route'] : $task_info['parent_id'];
                            $children[$parent][$plugin_id] = $task_info;
                        }
                    }
                }
                $data = [
                    'base_routes' => $base_routes,
                    'parents' => $parents,
                    'children' => $children,
                ];
                $this->cacheBackend
                    ->set($this->cacheKey . ':' . $route_name, $data, Cache::PERMANENT, $this->cacheTags);
            }
            // Create a plugin instance for each element of the hierarchy.
            foreach ($base_routes as $base_route) {
                // Convert the tree keyed by plugin IDs into a simple one with
                // integer depth.  Create instances for each plugin along the way.
                $level = 0;
                // We used this above as the top-level parent array key.
                $next_parent = '> ' . $base_route;
                do {
                    $parent = $next_parent;
                    $next_parent = FALSE;
                    foreach ($children[$parent] as $plugin_id => $task_info) {
                        $plugin = $this->createInstance($plugin_id);
                        $this->instances[$route_name][$level][$plugin_id] = $plugin;
                        // Normally, the link generator compares the href of every link with
                        // the current path and sets the active class accordingly. But the
                        // parents of the current local task may be on a different route in
                        // which case we have to set the class manually by flagging it
                        // active.
                        if (!empty($parents[$plugin_id]) && $route_name != $task_info['route_name']) {
                            $plugin->setActive();
                        }
                        if (isset($children[$plugin_id])) {
                            // This tab has visible children.
                            $next_parent = $plugin_id;
                        }
                    }
                    $level++;
                } while ($next_parent);
            }
        }
        return $this->instances[$route_name];
    }
    
    /**
     * {@inheritdoc}
     */
    public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheability) {
        $tree = $this->getLocalTasksForRoute($current_route_name);
        $build = [];
        // Collect all route names.
        $route_names = [];
        foreach ($tree as $instances) {
            foreach ($instances as $child) {
                $route_names[] = $child->getRouteName();
            }
        }
        // Pre-fetch all routes involved in the tree. This reduces the number
        // of SQL queries that would otherwise be triggered by the access manager.
        if ($route_names) {
            $this->routeProvider
                ->getRoutesByNames($route_names);
        }
        foreach ($tree as $level => $instances) {
            
            /** @var \Drupal\Core\Menu\LocalTaskInterface[] $instances */
            foreach ($instances as $plugin_id => $child) {
                $route_name = $child->getRouteName();
                $route_parameters = $child->getRouteParameters($this->routeMatch);
                // Given that the active flag depends on the route we have to add the
                // route cache context.
                $cacheability->addCacheContexts([
                    'route',
                ]);
                $active = $this->isRouteActive($current_route_name, $route_name, $route_parameters);
                // The plugin may have been set active in getLocalTasksForRoute() if
                // one of its child tabs is the active tab.
                $active = $active || $child->getActive();
                // @todo It might make sense to use link render elements instead.
                $link = [
                    'title' => $this->getTitle($child),
                    'url' => Url::fromRoute($route_name, $route_parameters),
                    'localized_options' => $child->getOptions($this->routeMatch),
                ];
                $access = $this->accessManager
                    ->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE);
                $build[$level][$plugin_id] = [
                    '#theme' => 'menu_local_task',
                    '#link' => $link,
                    '#active' => $active,
                    '#weight' => $child->getWeight(),
                    '#access' => $access,
                ];
                $cacheability->addCacheableDependency($access)
                    ->addCacheableDependency($child);
            }
        }
        return $build;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getLocalTasks($route_name, $level = 0) {
        if (!isset($this->taskData[$route_name])) {
            $cacheability = new CacheableMetadata();
            $cacheability->addCacheContexts([
                'route',
            ]);
            // Look for route-based tabs.
            $this->taskData[$route_name] = [
                'tabs' => [],
                'cacheability' => $cacheability,
            ];
            if (!$this->requestStack
                ->getCurrentRequest()->attributes
                ->has('exception')) {
                // Safe to build tasks only when no exceptions raised.
                $data = [];
                $local_tasks = $this->getTasksBuild($route_name, $cacheability);
                foreach ($local_tasks as $tab_level => $items) {
                    $data[$tab_level] = empty($data[$tab_level]) ? $items : array_merge($data[$tab_level], $items);
                }
                $this->taskData[$route_name]['tabs'] = $data;
                // Allow modules to alter local tasks.
                $this->moduleHandler
                    ->alter('menu_local_tasks', $this->taskData[$route_name], $route_name, $cacheability);
                $this->taskData[$route_name]['cacheability'] = $cacheability;
            }
        }
        if (isset($this->taskData[$route_name]['tabs'][$level])) {
            return [
                'tabs' => $this->taskData[$route_name]['tabs'][$level],
                'route_name' => $route_name,
                'cacheability' => $this->taskData[$route_name]['cacheability'],
            ];
        }
        return [
            'tabs' => [],
            'route_name' => $route_name,
            'cacheability' => $this->taskData[$route_name]['cacheability'],
        ];
    }
    
    /**
     * Determines whether the route of a certain local task is currently active.
     *
     * @param string $current_route_name
     *   The route name of the current main request.
     * @param string $route_name
     *   The route name of the local task to determine the active status.
     * @param array $route_parameters
     *   The parameter for the route.
     *
     * @return bool
     *   Returns TRUE if the passed route_name and route_parameters is considered
     *   as the same as the one from the request, otherwise FALSE.
     */
    protected function isRouteActive($current_route_name, $route_name, $route_parameters) {
        // Flag the list element as active if this tab's route and parameters match
        // the current request's route and route variables.
        $active = $current_route_name == $route_name;
        if ($active) {
            // The request is injected, so we need to verify that we have the expected
            // _raw_variables attribute.
            $raw_variables_bag = $this->routeMatch
                ->getRawParameters();
            // If we don't have _raw_variables, we assume the attributes are still the
            // original values.
            $raw_variables = $raw_variables_bag ? $raw_variables_bag->all() : $this->routeMatch
                ->getParameters()
                ->all();
            $active = array_intersect_assoc($route_parameters, $raw_variables) == $route_parameters;
        }
        return $active;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
DefaultPluginManager::$additionalAnnotationNamespaces protected property Additional annotation namespaces.
DefaultPluginManager::$alterHook protected property Name of the alter hook if one should be invoked.
DefaultPluginManager::$cacheKey protected property The cache key.
DefaultPluginManager::$cacheTags protected property An array of cache tags to use for the cached definitions.
DefaultPluginManager::$moduleExtensionList protected property The module extension list.
DefaultPluginManager::$moduleHandler protected property The module handler to invoke the alter hook. 1
DefaultPluginManager::$namespaces protected property An object of root paths that are traversable.
DefaultPluginManager::$pluginDefinitionAnnotationName protected property The name of the annotation that contains the plugin definition.
DefaultPluginManager::$pluginDefinitionAttributeName protected property The name of the attribute that contains the plugin definition.
DefaultPluginManager::$pluginInterface protected property The interface each plugin should implement. 1
DefaultPluginManager::$subdir protected property The subdirectory within a namespace to look for plugins.
DefaultPluginManager::alterDefinitions protected function Invokes the hook to alter the definitions if the alter hook is set. 4
DefaultPluginManager::alterInfo protected function Sets the alter hook name.
DefaultPluginManager::clearCachedDefinitions public function Clears static and persistent plugin definition caches. Overrides CachedDiscoveryInterface::clearCachedDefinitions 9
DefaultPluginManager::extractProviderFromDefinition protected function Extracts the provider from a plugin definition.
DefaultPluginManager::findDefinitions protected function Finds plugin definitions. 7
DefaultPluginManager::getCacheContexts public function The cache contexts associated with this object. Overrides CacheableDependencyInterface::getCacheContexts
DefaultPluginManager::getCachedDefinitions protected function Returns the cached plugin definitions of the decorated discovery class.
DefaultPluginManager::getCacheMaxAge public function The maximum age for which this object may be cached. Overrides CacheableDependencyInterface::getCacheMaxAge
DefaultPluginManager::getCacheTags public function The cache tags associated with this object. Overrides CacheableDependencyInterface::getCacheTags
DefaultPluginManager::getFactory protected function Gets the plugin factory. Overrides PluginManagerBase::getFactory
DefaultPluginManager::providerExists protected function Determines if the provider of a definition exists. 5
DefaultPluginManager::setCacheBackend public function Initialize the cache backend.
DefaultPluginManager::setCachedDefinitions protected function Sets a cache of plugin definitions for the decorated discovery class.
DefaultPluginManager::useCaches public function Disable the use of caches. Overrides CachedDiscoveryInterface::useCaches 1
DiscoveryCachedTrait::$definitions protected property Cached definitions array. 1
DiscoveryCachedTrait::getDefinition public function Overrides DiscoveryTrait::getDefinition 3
DiscoveryTrait::doGetDefinition protected function Gets a specific plugin definition.
DiscoveryTrait::hasDefinition public function
LocalTaskManager::$accessManager protected property The access manager.
LocalTaskManager::$account protected property The current user.
LocalTaskManager::$argumentResolver protected property An argument resolver object.
LocalTaskManager::$defaults protected property A set of defaults to be referenced by $this->processDefinition(). Overrides DefaultPluginManager::$defaults
LocalTaskManager::$instances protected property The plugin instances.
LocalTaskManager::$requestStack protected property The request stack.
LocalTaskManager::$routeMatch protected property The current route match.
LocalTaskManager::$routeProvider protected property The route provider to load routes by name.
LocalTaskManager::$taskData protected property The local task render arrays for the current route.
LocalTaskManager::getDefinitions public function Gets the definition of all plugins for this type. Overrides DefaultPluginManager::getDefinitions
LocalTaskManager::getDiscovery protected function Gets the plugin discovery. Overrides DefaultPluginManager::getDiscovery
LocalTaskManager::getLocalTasks public function Renders the local tasks (tabs) for the given route. Overrides LocalTaskManagerInterface::getLocalTasks
LocalTaskManager::getLocalTasksForRoute public function Find all local tasks that appear on a named route. Overrides LocalTaskManagerInterface::getLocalTasksForRoute
LocalTaskManager::getTasksBuild public function Gets the render array for all local tasks. Overrides LocalTaskManagerInterface::getTasksBuild
LocalTaskManager::getTitle public function Gets the title for a local task. Overrides LocalTaskManagerInterface::getTitle
LocalTaskManager::isRouteActive protected function Determines whether the route of a certain local task is currently active.
LocalTaskManager::processDefinition public function Performs extra processing on plugin definitions. Overrides DefaultPluginManager::processDefinition
LocalTaskManager::__construct public function Constructs a \Drupal\Core\Menu\LocalTaskManager object. Overrides DefaultPluginManager::__construct
PluginManagerBase::$discovery protected property The object that discovers plugins managed by this manager.
PluginManagerBase::$factory protected property The object that instantiates plugins managed by this manager.
PluginManagerBase::$mapper protected property The object that returns the preconfigured plugin instance appropriate for a particular runtime condition.
PluginManagerBase::createInstance public function 14
PluginManagerBase::getFallbackPluginId protected function Gets a fallback id for a missing plugin. 5
PluginManagerBase::getInstance public function 6
PluginManagerBase::handlePluginNotFound protected function Allows plugin managers to specify custom behavior if a plugin is not found. 1
UseCacheBackendTrait::$cacheBackend protected property Cache backend instance.
UseCacheBackendTrait::$useCaches protected property Flag whether caches should be used or skipped.
UseCacheBackendTrait::cacheGet protected function Fetches from the cache backend, respecting the use caches flag.
UseCacheBackendTrait::cacheSet protected function Stores data in the persistent cache, respecting the use caches flag.
RSS feed
Powered by Drupal