View source
<?php
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
use Drupal\Core\Plugin\Factory\ContainerFactory;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerInterface {
protected $defaults = [
'route_name' => '',
'route_parameters' => [],
'title' => '',
'base_route' => '',
'parent_id' => NULL,
'weight' => NULL,
'options' => [],
'class' => 'Drupal\\Core\\Menu\\LocalTaskDefault',
'id' => '',
];
protected $argumentResolver;
protected $requestStack;
protected $routeMatch;
protected $instances = [];
protected $taskData;
protected $routeProvider;
protected $accessManager;
protected $account;
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',
]);
}
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;
}
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
if (empty($definition['route_name'])) {
throw new PluginException(sprintf('Plugin (%s) definition must include "route_name"', $plugin_id));
}
}
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);
}
public function getDefinitions() {
$definitions = parent::getDefinitions();
$count = 0;
foreach ($definitions as &$definition) {
if (isset($definition['weight'])) {
$definition['weight'] += $count++ * 1.0E-6;
}
}
return $definitions;
}
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();
$base_routes = [];
$parents = [];
$children = [];
foreach ($definitions as $plugin_id => $task_info) {
if (!empty($task_info['parent_id']) && !empty($definitions[$task_info['parent_id']])) {
$task_info['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
$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'];
}
$parents[$plugin_id] = TRUE;
if (!empty($task_info['parent_id'])) {
$parents[$task_info['parent_id']] = TRUE;
}
}
}
if ($base_routes) {
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']]))) {
$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);
}
foreach ($base_routes as $base_route) {
$level = 0;
$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;
if (!empty($parents[$plugin_id]) && $route_name != $task_info['route_name']) {
$plugin
->setActive();
}
if (isset($children[$plugin_id])) {
$next_parent = $plugin_id;
}
}
$level++;
} while ($next_parent);
}
}
return $this->instances[$route_name];
}
public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheability) {
$tree = $this
->getLocalTasksForRoute($current_route_name);
$build = [];
$route_names = [];
foreach ($tree as $instances) {
foreach ($instances as $child) {
$route_names[] = $child
->getRouteName();
}
}
if ($route_names) {
$this->routeProvider
->getRoutesByNames($route_names);
}
foreach ($tree as $level => $instances) {
foreach ($instances as $plugin_id => $child) {
$route_name = $child
->getRouteName();
$route_parameters = $child
->getRouteParameters($this->routeMatch);
$cacheability
->addCacheContexts([
'route',
]);
$active = $this
->isRouteActive($current_route_name, $route_name, $route_parameters);
$active = $active || $child
->getActive();
$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;
}
public function getLocalTasks($route_name, $level = 0) {
if (!isset($this->taskData[$route_name])) {
$cacheability = new CacheableMetadata();
$cacheability
->addCacheContexts([
'route',
]);
$this->taskData[$route_name] = [
'tabs' => [],
'cacheability' => $cacheability,
];
if (!$this->requestStack
->getCurrentRequest()->attributes
->has('exception')) {
$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;
$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'],
];
}
protected function isRouteActive($current_route_name, $route_name, $route_parameters) {
$active = $current_route_name == $route_name;
if ($active) {
$raw_variables_bag = $this->routeMatch
->getRawParameters();
$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;
}
}