Local tasks on overrided node/taxonomy pages were disappeared

Created on 7 February 2017, almost 8 years ago
Updated 8 November 2023, about 1 year ago

Problem/Motivation

When you override node/taxonomy pages by page manager and add page variants the local tasks will disappear.
Caused by this check in \Drupal\Core\Menu\LocalTaskManager on line 220:

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.

and this \Drupal\page_manager\Routing\PageManagerRoutes

$route = new Route(
          $path,
          [
            '_entity_view' => 'page_manager_page_variant',
            '_title' => $variant->getTitle(),
            'page_manager_page_variant' => $variant_id,
            'page_manager_page' => $page_id,
            'page_manager_page_variant_weight' => $variant->getWeight(),
            // When adding multiple variants, the variant ID is added to the
            // route name. In order to convey the base route name for this set
            // of variants, add it as a parameter.
            'base_route_name' => $route_name,
          ],
          $requirements,
          [
            'parameters' => $parameters,
            '_admin_route' => $entity->usesAdminTheme(),
          ]
        );
        $collection->add($first ? $route_name : $route_name . '_' . $variant_id, $route);

So LocalTaskManager can`t find local tasks for overrided node page variant

Proposed resolution

Use 2 hooks: hook_menu_local_tasks_alter(), hook_local_tasks_alter().

/**
 * Implements hook_local_tasks_alter().
 * Add canonical tasks for page variants
 */
function MYMODULE_local_tasks_alter(&$local_tasks) {
  /* @var $entityStorage \Drupal\page_manager\Entity\Page */
  $entityStorage = Drupal::entityTypeManager()->getStorage('page');
  foreach ($entityStorage->loadMultiple() as $entity) {
    /** @var \Drupal\page_manager\PageInterface $entity */
    // If the page is disabled skip making a route for it.
    if (!$entity->status() || !$entity->getVariants()) {
      continue;
    }

    $entityType = FALSE;
    foreach ($entity->getParameters() as $param) { // Get an entity type name
      if (strpos($param['type'], 'entity:') === 0) {
        $entityType = substr($param['type'], 7);
        break;
      }
    }

    if ($entityType) {
      $entityTasks = [];
      $entityTaskStr = 'entity.' . $entityType . '.canonical';
      // Find all canonical tasks
      foreach ($local_tasks as $task) {
        if (strpos($task['route_name'], $entityTaskStr) === 0) {
          $entityTasks[] = $task;
        }
      }

      $first = TRUE;
      /* @var \Drupal\Core\Routing\RouteProviderInterface $route_provider */
      $route_provider = \Drupal::service('router.route_provider');
      foreach ($entity->getVariants() as $variantId => $variant) {
        /* @see \Drupal\page_manager\Routing\PageManagerRoutes::alterRoutes() */
        // This check prevent route not found exception
        if (!$first) {
          foreach ($entityTasks as $task) {
            $routeName = $task['route_name'] . '_' . $variantId;
            $task['route_name'] = $routeName;
            $local_tasks[$task['route_name']] = $task;
          }
        }
        $first = FALSE;
      }
    }
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 * Remove duplicates of canonical tasks for page variants
 */
function MYMODULE_menu_local_tasks_alter(&$data, $route_name) {
  /* @var $entityStorage \Drupal\page_manager\Entity\Page */
  $entityStorage = Drupal::entityTypeManager()->getStorage('page');
  foreach ($entityStorage->loadMultiple() as $entity) {
    /** @var \Drupal\page_manager\PageInterface $entity */
    // If the page is disabled skip making a route for it.
    if (!$entity->status() || !$entity->getVariants()) {
      continue;
    }

    $entityType = FALSE;
    foreach ($entity->getParameters() as $param) {
      if (strpos($param['type'], 'entity:') === 0) {
        $entityType = substr($param['type'], 7);
        break;
      }
    }

    if ($entityType) {
      $entityTaskStr = 'entity.' . $entityType . '.canonical_';

      $newTabs = [];
      foreach ($data['tabs'][0] as $key => $tab) {
        if (strpos($key, $entityTaskStr) === FALSE) {
          $newTabs[$key] = $tab;
        }
      }
      $data['tabs'][0] = $newTabs;
    }
  }
}

Remaining tasks

  1. Find better solutions if can
  2. Review & commit
πŸ› Bug report
Status

Needs review

Version

4.0

Component

User interface

Created by

πŸ‡ΊπŸ‡¦Ukraine pnagornyak

Live updates comments and jobs are added and updated live.
  • Accessibility

    It affects the ability of people with disabilities or special needs (such as blindness or color-blindness) to use Drupal.

  • Quick fix

    Very small and simple change. Preferred over Quickfix.

  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024