block_editor-1.0.x-dev/src/Hook/MenuHooks.php
src/Hook/MenuHooks.php
<?php
namespace Drupal\block_editor\Hook;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\block_editor\Service\EntityManager;
/**
* Menu-related hooks for Block Editor.
*/
class MenuHooks {
/**
* The current route match.
*/
protected RouteMatchInterface $routeMatch;
/**
* The entity type manager.
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The Block Editor entity manager.
*/
protected EntityManager $entityManager;
/**
* Constructs a new MenuHooks instance.
*/
public function __construct(RouteMatchInterface $route_match, EntityTypeManagerInterface $entity_type_manager, EntityManager $entity_manager) {
$this->routeMatch = $route_match;
$this->entityTypeManager = $entity_type_manager;
$this->entityManager = $entity_manager;
}
/**
* Implements hook_menu_local_tasks_alter().
*/
#[Hook('menu_local_tasks_alter')]
public function menuLocalTasksAlter(array &$data, string $route_name): void {
if (!$this->isCanonicalRoute($route_name)) {
return;
}
$entity = $this->extractContentEntity();
if (!$entity) {
return;
}
$bundle_entity = $this->loadBundleEntity($entity);
if (!$bundle_entity || !$this->entityManager->isBlockEditorEnabledForEntity($bundle_entity)) {
return;
}
$tab_collection = $data['tabs'][0] ?? [];
if (!$tab_collection) {
return;
}
$entity_type_id = $entity->getEntityTypeId();
$entity_type = $entity->getEntityType();
$default_tab_key = 'entity.' . $entity_type_id . '.edit_form';
$default_tab_key_with_suffix = 'entity.' . $entity_type_id . '.edit_form_tab';
$canonical_tab_key = 'entity.' . $entity_type_id . '.canonical';
$block_editor_tab_key = 'block_editor.entity.' . $entity_type_id . '.edit_form';
// Check if canonical and edit-form are the same for this entity type.
$canonical_link = $entity_type->getLinkTemplate('canonical');
$edit_form_link = $entity_type->getLinkTemplate('edit-form');
$is_canonical_same_as_edit = ($canonical_link === $edit_form_link);
// Always remove the default Edit tab when Block Editor is enabled.
// Some entity types use 'edit_form' and others use 'edit_form_tab'.
if (isset($data['tabs'][0][$default_tab_key])) {
unset($data['tabs'][0][$default_tab_key]);
}
if (isset($data['tabs'][0][$default_tab_key_with_suffix])) {
unset($data['tabs'][0][$default_tab_key_with_suffix]);
}
// For entity types where canonical IS the edit form (like block_content),
// also remove the canonical tab (which is essentially the edit tab).
if ($is_canonical_same_as_edit && isset($data['tabs'][0][$canonical_tab_key])) {
unset($data['tabs'][0][$canonical_tab_key]);
}
// Only move Block Editor tab to the beginning for entity types where
// canonical IS the edit form (like block_content).
if ($is_canonical_same_as_edit && isset($data['tabs'][0][$block_editor_tab_key])) {
$block_editor_tab = $data['tabs'][0][$block_editor_tab_key];
unset($data['tabs'][0][$block_editor_tab_key]);
// Set weight to -100 to ensure it appears first.
$block_editor_tab['#weight'] = -100;
// Mark as the active/default tab.
$block_editor_tab['#active'] = TRUE;
// Re-add at the beginning by prepending.
$data['tabs'][0] = [$block_editor_tab_key => $block_editor_tab] + $data['tabs'][0];
}
}
/**
* Determines if the current route should invoke tab adjustments.
*/
protected function isCanonicalRoute(string $route_name): bool {
// Handle entity types with separate canonical routes.
if ($route_name === 'entity.node.canonical') {
return TRUE;
}
// Handle entity types where canonical IS the edit form
// (like block_content).
if (preg_match('/^entity\.([a-z_]+)\.canonical$/', $route_name, $matches)) {
return TRUE;
}
// Handle edit form routes (like entity.comment.edit_form).
// Some entity types have edit forms as separate routes that are tabs
// on the canonical route.
if (preg_match('/^entity\.([a-z_]+)\.edit_form$/', $route_name, $matches)) {
return TRUE;
}
// Handle Block Editor routes.
if (\str_starts_with($route_name, 'block_editor.entity.')) {
return TRUE;
}
// Handle related entity routes (revisions, delete, etc.) that use
// canonical as their base route. These routes follow patterns like:
// - entity.{entity_type}.version_history
// - entity.{entity_type}.revision
// - entity.{entity_type}.delete_form
// - entity.{entity_type}.revision_revert_form
// - entity.{entity_type}.revision_delete_form.
if (preg_match('/^entity\.([a-z_]+)\.(version_history|revision|delete_form|revision_revert_form|revision_delete_form)/', $route_name, $matches)) {
return TRUE;
}
return FALSE;
}
/**
* Extracts the content entity from the current route.
*/
protected function extractContentEntity(): ?ContentEntityInterface {
foreach ($this->routeMatch->getParameters() as $parameter) {
if ($parameter instanceof ContentEntityInterface) {
return $parameter;
}
}
return NULL;
}
/**
* Loads the bundle entity corresponding to a content entity.
*/
protected function loadBundleEntity(ContentEntityInterface $entity): ?object {
$entity_type = $entity->getEntityType();
$bundle_entity_type_id = $entity_type->getBundleEntityType();
if (!$bundle_entity_type_id) {
return NULL;
}
$bundle_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
return $bundle_storage->load($entity->bundle());
}
}
