block_editor-1.0.x-dev/src/EventSubscriber/BlockEditorRequestSubscriber.php
src/EventSubscriber/BlockEditorRequestSubscriber.php
<?php
namespace Drupal\block_editor\EventSubscriber;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Url;
use Drupal\block_editor\Controller\BlockEditorController;
use Drupal\block_editor\Service\EntityManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* Redirects canonical entity routes to dedicated Block Editor routes.
*/
class BlockEditorRequestSubscriber implements EventSubscriberInterface {
public function __construct(
protected EntityManager $entityManager,
protected EntityTypeManagerInterface $entityTypeManager,
protected RouteProviderInterface $routeProvider,
) {
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
// Priority 31 runs after Drupal's RouterListener (priority 32)
// but before most other listeners, ensuring routing is resolved.
KernelEvents::REQUEST => ['onRequest', 31],
];
}
/**
* Redirects canonical edit routes to the isolated Block Editor routes.
*/
public function onRequest(RequestEvent $event): void {
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
$route_name = (string) $request->attributes->get('_route');
if ($route_name === '') {
return;
}
if ($this->maybeRedirectToBlockEditor($event, $route_name)) {
return;
}
if ($this->maybeRedirectToBlockEditorAddForm($event, $route_name)) {
return;
}
if ($this->isBlockEditorRoute($route_name, $request)) {
$request->attributes->set('_admin_route', FALSE);
$request->attributes->set('_block_editor_route', TRUE);
}
}
/**
* Determines whether the matched route should be treated as Block Editor.
*/
protected function isBlockEditorRoute(string $route_name, Request $request): bool {
if ($request->attributes->get('_block_editor_route') === TRUE) {
return TRUE;
}
if (str_starts_with($route_name, 'block_editor.')) {
return TRUE;
}
$controller = (string) $request->attributes->get('_controller');
if ($controller === BlockEditorController::class . '::addForm') {
return TRUE;
}
if (!str_starts_with($route_name, 'entity.')) {
return FALSE;
}
return $request->attributes->get('_entity_form') === 'block_editor';
}
/**
* Redirects entity edit forms to the Block Editor edit form routes.
*/
protected function maybeRedirectToBlockEditor(RequestEvent $event, string $route_name): bool {
if (!str_starts_with($route_name, 'entity.') || !str_ends_with($route_name, '.edit_form')) {
return FALSE;
}
$entity_info = $this->extractContentEntity($event->getRequest());
if (!$entity_info) {
return FALSE;
}
[$parameter_name, $entity] = $entity_info;
if (!$this->shouldUseBlockEditor($entity)) {
return FALSE;
}
$entity_type_id = $entity->getEntityTypeId();
$target_route = 'block_editor.entity.' . $entity_type_id . '.edit_form';
if ($route_name === $target_route) {
return FALSE;
}
try {
$this->routeProvider->getRouteByName($target_route);
}
catch (RouteNotFoundException $exception) {
return FALSE;
}
$url = Url::fromRoute($target_route, [$parameter_name => $entity->id()]);
$event->setResponse(new RedirectResponse($url->toString()));
return TRUE;
}
/**
* Redirects canonical add routes to the Block Editor add routes.
*/
protected function maybeRedirectToBlockEditorAddForm(RequestEvent $event, string $route_name): bool {
// Skip if already on a Block Editor route.
if (str_starts_with($route_name, 'block_editor.')) {
return FALSE;
}
// Check for various add route patterns.
// Common patterns: entity.{type}.add_form, {type}.add, node.add.
$is_add_route = str_contains($route_name, '.add_form')
|| str_contains($route_name, '.add')
|| $route_name === 'node.add';
if (!$is_add_route) {
return FALSE;
}
$request = $event->getRequest();
$bundle_info = $this->extractBundleEntity($request);
// If bundle entity is not in the request, try to extract and load it
// from route parameters.
if (!$bundle_info) {
$bundle_info = $this->extractAndLoadBundleFromRouteParameters($request, $route_name);
}
if (!$bundle_info) {
return FALSE;
}
[$parameter_name, $bundle] = $bundle_info;
if (!$this->entityManager->isBlockEditorEnabledForEntity($bundle)) {
return FALSE;
}
$content_entity_type_id = $bundle->getEntityType()->getBundleOf();
if (!$content_entity_type_id) {
return FALSE;
}
$target_route = 'block_editor.entity.' . $content_entity_type_id . '.add_form';
if ($route_name === $target_route) {
return FALSE;
}
try {
$this->routeProvider->getRouteByName($target_route);
}
catch (RouteNotFoundException $exception) {
return FALSE;
}
$url = Url::fromRoute($target_route, [$parameter_name => $bundle->id()]);
$event->setResponse(new RedirectResponse($url->toString()));
return TRUE;
}
/**
* Extracts the content entity (if any) from the route request attributes.
*/
protected function extractContentEntity(Request $request): ?array {
foreach ($request->attributes->all() as $key => $value) {
if ($value instanceof ContentEntityInterface) {
return [$key, $value];
}
}
return NULL;
}
/**
* Extracts the bundle config entity from the current request.
*/
protected function extractBundleEntity(Request $request): ?array {
foreach ($request->attributes->all() as $key => $value) {
if ($value instanceof ConfigEntityInterface) {
return [$key, $value];
}
}
return NULL;
}
/**
* Extracts and loads bundle entity from route parameters.
*
* This handles cases where the bundle is passed as a string parameter
* rather than an upcasted entity object.
*/
protected function extractAndLoadBundleFromRouteParameters(Request $request, string $route_name): ?array {
// Common bundle parameter names by entity type.
$bundle_parameter_mapping = [
'node_type' => 'node',
'taxonomy_vocabulary' => 'taxonomy_term',
'block_content_type' => 'block_content',
'media_type' => 'media',
];
// First, try the standard bundle parameter names.
foreach ($bundle_parameter_mapping as $bundle_entity_type_id => $content_entity_type_id) {
$bundle_id = $request->attributes->get($bundle_entity_type_id);
if ($bundle_id && is_string($bundle_id)) {
try {
$bundle_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
$bundle = $bundle_storage->load($bundle_id);
if ($bundle instanceof ConfigEntityInterface) {
return [$bundle_entity_type_id, $bundle];
}
}
catch (\Exception $e) {
// Continue to next mapping.
continue;
}
}
}
// Fallback: Try to find any string parameter in the request that
// might be a bundle ID and check against known bundle entity types.
foreach ($request->attributes->all() as $param_name => $param_value) {
// Skip special parameters.
if (str_starts_with($param_name, '_') || !is_string($param_value)) {
continue;
}
// Try each bundle entity type to see if this parameter is a valid bundle.
foreach (array_keys($bundle_parameter_mapping) as $bundle_entity_type_id) {
try {
$bundle_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
$bundle = $bundle_storage->load($param_value);
if ($bundle instanceof ConfigEntityInterface) {
return [$bundle_entity_type_id, $bundle];
}
}
catch (\Exception $e) {
// This entity type doesn't exist or failed to load, continue.
continue;
}
}
}
return NULL;
}
/**
* Determines whether a content entity's bundle has Block Editor enabled.
*/
protected function shouldUseBlockEditor(ContentEntityInterface $entity): bool {
return $this->entityManager->isBlockEditorEnabledForContentEntity($entity);
}
}
