block_editor-1.0.x-dev/src/Routing/BlockEditorRouteSubscriber.php
src/Routing/BlockEditorRouteSubscriber.php
<?php
namespace Drupal\block_editor\Routing;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\block_editor\Controller\BlockEditorController;
use Drupal\block_editor\Service\EntityManager;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
/**
* Subscribes to entity edit routes to provide Block Editor forms.
*/
class BlockEditorRouteSubscriber extends RouteSubscriberBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity manager.
*
* @var \Drupal\block_editor\Service\EntityManager
*/
protected $entityManager;
/**
* Constructs a new BlockEditorRouteSubscriber.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\block_editor\Service\EntityManager $entity_manager
* The entity manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityManager $entity_manager) {
$this->entityTypeManager = $entity_type_manager;
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
// Add Block Editor settings routes for all supported bundle entity types.
$this->addBlockEditorSettingsRoutes($collection);
/*
* Instead of altering edit form route, we are adding a new route
* for Block Editor edit form and add an access check to default edit route.
* This is to avoid potential changes from other modules and/or themes.
*/
$entity_types = $this->entityTypeManager->getDefinitions();
// This will handle all entity types that follow the "standard" pattern.
// For example, node.
foreach ($entity_types as $entity_type_id => $entity_type) {
if ($entity_type instanceof ContentEntityType) {
// We need to check if the entity type is supported by Block Editor.
// If not, we skip it.
$config_entity_type_id = $entity_type->getBundleEntityType();
if (empty($config_entity_type_id)) {
continue;
}
$config_entity_type = $this->entityTypeManager
->getDefinition($config_entity_type_id);
if (!$this->entityManager->entityTypeSupportsBlockEditor($config_entity_type)) {
continue;
}
// Always provide dedicated Block Editor routes for supported entity
// types.
$this->ensureBlockEditorAddRoute($collection, $entity_type_id, $config_entity_type_id);
$this->ensureBlockEditorEditRoute($collection, $entity_type_id);
// For entity types where canonical IS the edit form
// (like block_content), alter the canonical route to point to Block
// Editor when enabled.
$this->alterCanonicalRouteIfNeeded($collection, $entity_type);
}
}
return $collection;
}
/**
* Adds a dedicated Block Editor edit route for supported entity types.
*
* @param \Symfony\Component\Routing\RouteCollection $collection
* The route collection.
* @param string $entity_type_id
* The content entity type ID (e.g. node).
*/
protected function ensureBlockEditorEditRoute(RouteCollection $collection, string $entity_type_id): void {
$default_route_name = 'entity.' . $entity_type_id . '.edit_form';
$default_route = $collection->get($default_route_name);
if (!$default_route) {
return;
}
$block_editor_route_name = 'block_editor.entity.' . $entity_type_id . '.edit_form';
if ($collection->get($block_editor_route_name)) {
return;
}
$block_editor_route = new Route(
$this->deriveBlockEditorPath($default_route->getPath()),
$default_route->getDefaults(),
$default_route->getRequirements(),
$default_route->getOptions(),
$default_route->getHost(),
$default_route->getSchemes(),
$default_route->getMethods()
);
$defaults = $default_route->getDefaults();
unset($defaults['_controller']);
$defaults['_entity_form'] = $entity_type_id . '.block_editor';
if (!isset($defaults['_title_callback'])) {
$defaults['_title_callback'] = BlockEditorController::class . '::editTitle';
}
$block_editor_route->setDefaults($defaults);
$options = $block_editor_route->getOptions();
$options['_admin_route'] = TRUE;
if ($default_route->hasOption('parameters')) {
$options['parameters'] = $default_route->getOption('parameters');
}
$block_editor_route->setOptions($options);
$block_editor_route->setRequirement('_block_editor_form_access', 'TRUE');
$collection->add($block_editor_route_name, $block_editor_route);
}
/**
* Derives the Block Editor path from the default edit path.
*
* @param string $default_path
* The default edit path.
*
* @return string
* The derived Block Editor path.
*/
protected function deriveBlockEditorPath(string $default_path): string {
if (\str_ends_with($default_path, '/edit')) {
return substr($default_path, 0, -5) . '/block-editor';
}
return rtrim($default_path, '/') . '/block-editor';
}
/**
* Adds a dedicated Block Editor add route for supported entity types.
*/
protected function ensureBlockEditorAddRoute(RouteCollection $collection, string $entity_type_id, string $config_entity_type_id): void {
$candidate_routes = [
'entity.' . $entity_type_id . '.add_form',
$entity_type_id . '.add',
$entity_type_id . '.add_form',
];
$default_route = NULL;
foreach ($candidate_routes as $candidate) {
if ($route = $collection->get($candidate)) {
$default_route = $route;
break;
}
}
if (!$default_route) {
return;
}
$block_editor_route_name = 'block_editor.entity.' . $entity_type_id . '.add_form';
if ($collection->get($block_editor_route_name)) {
return;
}
$block_editor_route = new Route(
$this->deriveBlockEditorAddPath($default_route->getPath()),
$default_route->getDefaults(),
$default_route->getRequirements(),
$default_route->getOptions(),
$default_route->getHost(),
$default_route->getSchemes(),
$default_route->getMethods()
);
$defaults = $default_route->getDefaults();
unset($defaults['_entity_form']);
unset($defaults['_controller']);
unset($defaults['_title']);
$defaults['_controller'] = BlockEditorController::class . '::addForm';
$defaults['_title_callback'] = BlockEditorController::class . '::addTitle';
$defaults['_block_editor_route'] = TRUE;
$block_editor_route->setDefaults($defaults);
$options = $block_editor_route->getOptions();
$options['_admin_route'] = TRUE;
if ($default_route->hasOption('parameters')) {
$options['parameters'] = $default_route->getOption('parameters');
}
else {
$options['parameters'][$config_entity_type_id] = [
'type' => "entity:{$config_entity_type_id}",
];
}
$block_editor_route->setOptions($options);
$block_editor_route->setRequirement('_block_editor_add_form_access', 'TRUE');
$collection->add($block_editor_route_name, $block_editor_route);
}
/**
* Derives the Block Editor path for add forms.
*/
protected function deriveBlockEditorAddPath(string $default_path): string {
return rtrim($default_path, '/') . '/block-editor';
}
/**
* Alters canonical routes for entity types where canonical equals edit-form.
*
* For entity types like block_content where the canonical route IS the
* edit form, we need to add an access check that redirects to Block Editor
* when it's enabled.
*
* @param \Symfony\Component\Routing\RouteCollection $collection
* The route collection.
* @param \Drupal\Core\Entity\ContentEntityType $entity_type
* The content entity type.
*/
protected function alterCanonicalRouteIfNeeded(RouteCollection $collection, ContentEntityType $entity_type): void {
$entity_type_id = $entity_type->id();
// Check if canonical and edit-form link templates are the same.
$canonical_link = $entity_type->getLinkTemplate('canonical');
$edit_form_link = $entity_type->getLinkTemplate('edit-form');
if ($canonical_link !== $edit_form_link) {
return;
}
// Alter both the canonical and edit_form routes to add Block Editor
// access check.
$canonical_route_name = 'entity.' . $entity_type_id . '.canonical';
$edit_form_route_name = 'entity.' . $entity_type_id . '.edit_form';
foreach ([$canonical_route_name, $edit_form_route_name] as $route_name) {
$route = $collection->get($route_name);
if (!$route) {
continue;
}
// Add our access check to these routes.
// This will redirect to Block Editor if it's enabled.
$route->setRequirement('_block_editor_canonical_access', 'TRUE');
}
}
/**
* Dynamically adds Block Editor settings routes for all supported types.
*
* This creates routes like block_editor.{bundle_entity_type}.settings
* by deriving the path from the bundle entity type's edit form route.
*
* @param \Symfony\Component\Routing\RouteCollection $collection
* The route collection.
*/
protected function addBlockEditorSettingsRoutes(RouteCollection $collection): void {
// Get all entity types that support Block Editor.
$supported_mappings = $this->entityManager->getSupportedEntityTypeMappings();
foreach ($supported_mappings as $bundle_entity_type_id => $content_entity_type_id) {
$route_name = 'block_editor.' . $bundle_entity_type_id . '.settings';
// Skip if route already exists (defined manually in routing.yml).
if ($collection->get($route_name)) {
continue;
}
// Verify the bundle entity type exists.
try {
$this->entityTypeManager->getDefinition($bundle_entity_type_id);
}
catch (\Exception $e) {
continue;
}
// Get the edit form route for this bundle entity type.
$edit_route_name = 'entity.' . $bundle_entity_type_id . '.edit_form';
$edit_route = $collection->get($edit_route_name);
if (!$edit_route) {
continue;
}
// Create the Block Editor settings route based on the edit route.
$settings_route = clone $edit_route;
// Modify the path by appending /block-editor-settings.
$edit_path = $edit_route->getPath();
$settings_path = rtrim($edit_path, '/') . '/block-editor-settings';
$settings_route->setPath($settings_path);
// Set the form controller to EntityTypeManageForm.
// Get existing defaults from the edit route and modify them.
$defaults = $edit_route->getDefaults();
$defaults['_form'] = '\Drupal\block_editor\Form\EntityTypeManageForm';
$defaults['_title_callback'] = '\Drupal\block_editor\Controller\BlockEditorController::settingsTitle';
// Remove any controller that might conflict.
unset($defaults['_controller']);
unset($defaults['_entity_form']);
unset($defaults['_title']);
$settings_route->setDefaults($defaults);
// Set requirements - only use the Block Editor access check.
// Don't copy all requirements from edit route as they may conflict.
$requirements = [];
// Keep entity parameter type validation if it exists.
$edit_requirements = $edit_route->getRequirements();
foreach ($edit_requirements as $key => $value) {
// Only copy entity type validation requirements.
if ($key === $bundle_entity_type_id) {
$requirements[$key] = $value;
}
}
// Add Block Editor settings access check.
$requirements['_block_editor_settings_access'] = 'TRUE';
$settings_route->setRequirements($requirements);
// Keep the same options (including parameter definitions).
$options = $edit_route->getOptions();
// Ensure parameter upcasting is configured.
if (!isset($options['parameters'][$bundle_entity_type_id])) {
$options['parameters'][$bundle_entity_type_id] = [
'type' => 'entity:' . $bundle_entity_type_id,
];
}
$settings_route->setOptions($options);
// Add the route to the collection.
$collection->add($route_name, $settings_route);
}
}
}
