Commit f51c227e authored by catch's avatar catch

Issue #2940899 by plach, Wim Leers, hchonov: Regression: _entity_form routes...

Issue #2940899 by plach, Wim Leers, hchonov: Regression: _entity_form routes should not get load_latest_revision_flag by default: changes default behavior (= regression) while only Content Moderation needs it
parent 9108ff09
......@@ -196,30 +196,6 @@ protected function setParametersFromEntityInformation(Route $route) {
}
}
/**
* Ensure revisionable entities load the latest revision on entity forms.
*
* @param \Symfony\Component\Routing\Route $route
* The route object.
*/
protected function setLatestRevisionFlag(Route $route) {
if (!$entity_form = $route->getDefault('_entity_form')) {
return;
}
// Only set the flag on entity types which are revisionable.
list($entity_type) = explode('.', $entity_form, 2);
if (!isset($this->getEntityTypes()[$entity_type]) || !$this->getEntityTypes()[$entity_type]->isRevisionable()) {
return;
}
$parameters = $route->getOption('parameters') ?: [];
foreach ($parameters as &$parameter) {
if ($parameter['type'] === 'entity:' . $entity_type && !isset($parameter['load_latest_revision'])) {
$parameter['load_latest_revision'] = TRUE;
}
}
$route->setOption('parameters', $parameters);
}
/**
* Set the upcasting route objects.
*
......@@ -236,7 +212,6 @@ public function setRouteOptions(Route $route) {
// Try to use _entity_* information on the route.
$this->setParametersFromEntityInformation($route);
$this->setLatestRevisionFlag($route);
}
/**
......
......@@ -20,3 +20,8 @@ services:
arguments: ['@config.manager', '@entity_type.manager']
tags:
- { name: event_subscriber }
content_moderation.route_subscriber:
class: Drupal\content_moderation\Routing\ContentModerationRouteSubscriber
arguments: ['@entity_type.manager']
tags:
- { name: event_subscriber }
......@@ -11,6 +11,8 @@
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\content_moderation\Form\EntityModerationForm;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\workflows\Entity\Workflow;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -48,6 +50,13 @@ class EntityOperations implements ContainerInjectionInterface {
*/
protected $bundleInfo;
/**
* The router builder service.
*
* @var \Drupal\Core\Routing\RouteBuilderInterface
*/
protected $routerBuilder;
/**
* Constructs a new EntityOperations object.
*
......@@ -59,12 +68,15 @@ class EntityOperations implements ContainerInjectionInterface {
* The form builder.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
* The entity bundle information service.
* @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder
* The router builder service.
*/
public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, EntityTypeBundleInfoInterface $bundle_info) {
public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, EntityTypeBundleInfoInterface $bundle_info, RouteBuilderInterface $router_builder) {
$this->moderationInfo = $moderation_info;
$this->entityTypeManager = $entity_type_manager;
$this->formBuilder = $form_builder;
$this->bundleInfo = $bundle_info;
$this->routerBuilder = $router_builder;
}
/**
......@@ -75,7 +87,8 @@ public static function create(ContainerInterface $container) {
$container->get('content_moderation.moderation_information'),
$container->get('entity_type.manager'),
$container->get('form_builder'),
$container->get('entity_type.bundle.info')
$container->get('entity_type.bundle.info'),
$container->get('router.builder')
);
}
......@@ -133,6 +146,12 @@ public function entityUpdate(EntityInterface $entity) {
if ($this->moderationInfo->isModeratedEntity($entity)) {
$this->updateOrCreateFromEntity($entity);
}
// When updating workflow settings for Content Moderation, we need to
// rebuild routes as we may be enabling new entity types and the related
// entity forms.
elseif ($entity instanceof Workflow && $entity->getTypePlugin()->getPluginId() == 'content_moderation') {
$this->routerBuilder->setRebuildNeeded();
}
}
/**
......
<?php
namespace Drupal\content_moderation\Routing;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\workflows\Entity\Workflow;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Subscriber for moderated revisionable entity forms.
*
* @internal
* There is ongoing discussion about how pending revisions should behave.
* The logic enabling pending revision support is likely to change once a
* decision is made.
*
* @see https://www.drupal.org/node/2940575
*/
class ContentModerationRouteSubscriber extends RouteSubscriberBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* An associative array of moderated entity types keyed by ID.
*
* @var \Drupal\Core\Entity\ContentEntityTypeInterface[]
*/
protected $moderatedEntityTypes;
/**
* ContentModerationRouteSubscriber constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
foreach ($collection as $route) {
$this->setLatestRevisionFlag($route);
}
}
/**
* Ensure revisionable entities load the latest revision on entity forms.
*
* @param \Symfony\Component\Routing\Route $route
* The route object.
*/
protected function setLatestRevisionFlag(Route $route) {
if (!$entity_form = $route->getDefault('_entity_form')) {
return;
}
// Only set the flag on entity types which are revisionable.
list($entity_type) = explode('.', $entity_form, 2);
if (!isset($this->getModeratedEntityTypes()[$entity_type]) || !$this->getModeratedEntityTypes()[$entity_type]->isRevisionable()) {
return;
}
$parameters = $route->getOption('parameters') ?: [];
foreach ($parameters as &$parameter) {
if ($parameter['type'] === 'entity:' . $entity_type && !isset($parameter['load_latest_revision'])) {
$parameter['load_latest_revision'] = TRUE;
}
}
$route->setOption('parameters', $parameters);
}
/**
* Returns the moderated entity types.
*
* @return \Drupal\Core\Entity\ContentEntityTypeInterface[]
* An associative array of moderated entity types keyed by ID.
*/
protected function getModeratedEntityTypes() {
if (!isset($this->moderatedEntityTypes)) {
$entity_types = $this->entityTypeManager->getDefinitions();
/** @var \Drupal\workflows\WorkflowInterface $workflow */
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
$plugin = $workflow->getTypePlugin();
foreach ($plugin->getEntityTypes() as $entity_type_id) {
$this->moderatedEntityTypes[$entity_type_id] = $entity_types[$entity_type_id];
}
}
}
return $this->moderatedEntityTypes;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
// This needs to run after that EntityResolverManager has set the route
// entity type.
$events[RoutingEvents::ALTER] = ['onAlterRoutes', -200];
return $events;
}
}
......@@ -38,6 +38,10 @@ public function setUp() {
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'moderated_bundle');
$workflow->save();
/** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
$router_builder = $this->container->get('router.builder');
$router_builder->rebuildIfNeeded();
$admin = $this->drupalCreateUser([
'access content overview',
'administer nodes',
......
......@@ -132,6 +132,9 @@ public function enableModerationThroughUi($content_type_id, $workflow_id = 'edit
// @see content_moderation_workflow_insert()
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
/** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
$router_builder = $this->container->get('router.builder');
$router_builder->rebuildIfNeeded();
}
/**
......
<?php
namespace Drupal\Tests\content_moderation\Unit;
use Drupal\content_moderation\Routing\ContentModerationRouteSubscriber;
use Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @coversDefaultClass \Drupal\content_moderation\Routing\ContentModerationRouteSubscriber
*
* @group content_moderation
*/
class ContentModerationRouteSubscriberTest extends UnitTestCase {
/**
* The test content moderation route subscriber.
*
* @var \Drupal\content_moderation\Routing\ContentModerationRouteSubscriber
*/
protected $routeSubscriber;
/**
* {@inheritdoc}
*/
protected function setUp() {
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
$this->routeSubscriber = new ContentModerationRouteSubscriber($entity_type_manager);
$this->setupEntityTypes();
}
/**
* Creates the entity manager mock returning entity type objects.
*/
protected function setupEntityTypes() {
$definition = $this->createMock(EntityTypeInterface::class);
$definition->expects($this->any())
->method('getClass')
->will($this->returnValue(SimpleTestEntity::class));
$definition->expects($this->any())
->method('isRevisionable')
->willReturn(FALSE);
$revisionable_definition = $this->createMock(EntityTypeInterface::class);
$revisionable_definition->expects($this->any())
->method('getClass')
->will($this->returnValue(SimpleTestEntity::class));
$revisionable_definition->expects($this->any())
->method('isRevisionable')
->willReturn(TRUE);
$entity_types = [
'entity_test' => $definition,
'entity_test_rev' => $revisionable_definition,
];
$reflector = new \ReflectionProperty($this->routeSubscriber, 'moderatedEntityTypes');
$reflector->setAccessible(TRUE);
$reflector->setValue($this->routeSubscriber, $entity_types);
}
/**
* Data provider for ::testSetLatestRevisionFlag.
*/
public function setLatestRevisionFlagTestCases() {
return [
'Entity parameter not on an entity form' => [
[],
[
'entity_test' => [
'type' => 'entity:entity_test_rev',
],
],
],
'Entity parameter on an entity form' => [
[
'_entity_form' => 'entity_test_rev.edit'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
],
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => TRUE,
],
],
],
'Entity form with no operation' => [
[
'_entity_form' => 'entity_test_rev'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
],
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => TRUE,
],
],
],
'Non-moderated entity form' => [
[
'_entity_form' => 'entity_test_mulrev'
],
[
'entity_test_mulrev' => [
'type' => 'entity:entity_test_mulrev',
],
],
],
'Multiple entity parameters on an entity form' => [
[
'_entity_form' => 'entity_test_rev.edit'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
],
'node' => [
'type' => 'entity:node',
],
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => TRUE,
],
'node' => [
'type' => 'entity:node',
],
],
],
'Overridden load_latest_revision flag does not change' => [
[
'_entity_form' => 'entity_test_rev.edit'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => FALSE,
],
],
],
'Non-revisionable entity type will not change' => [
[
'_entity_form' => 'entity_test.edit'
],
[
'entity_test' => [
'type' => 'entity:entity_test',
],
],
FALSE,
FALSE,
],
'Overridden load_latest_revision flag does not change with multiple parameters' => [
[
'_entity_form' => 'entity_test_rev.edit'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
],
'node' => [
'type' => 'entity:node',
'load_latest_revision' => FALSE,
],
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => TRUE,
],
'node' => [
'type' => 'entity:node',
'load_latest_revision' => FALSE,
],
],
],
];
}
/**
* Tests that the "load_latest_revision" flag is handled correctly.
*
* @param array $defaults
* The route defaults.
* @param array $parameters
* The route parameters.
* @param array|bool $expected_parameters
* (optional) The expected route parameters. Defaults to FALSE.
*
* @covers ::setLatestRevisionFlag
*
* @dataProvider setLatestRevisionFlagTestCases
*/
public function testSetLatestRevisionFlag($defaults, $parameters, $expected_parameters = FALSE) {
$route = new Route('/foo/{entity_test}', $defaults, [], [
'parameters' => $parameters,
]);
$route_collection = new RouteCollection();
$route_collection->add('test', $route);
$event = new RouteBuildEvent($route_collection);
$this->routeSubscriber->onAlterRoutes($event);
// If expected parameters have not been provided, assert they are unchanged.
$this->assertEquals($expected_parameters ?: $parameters, $route->getOption('parameters'));
}
}
/**
* A concrete entity.
*/
class SimpleTestEntity extends Entity {
}
......@@ -476,138 +476,6 @@ protected function setupEntityTypes() {
}));
}
/**
* @covers ::setLatestRevisionFlag
*
* @dataProvider setLatestRevisionFlagTestCases
*/
public function testSetLatestRevisionFlag($defaults, $parameters, $expected_parameters = FALSE) {
$route = new Route('/foo/{entity_test}', $defaults, [], [
'parameters' => $parameters,
]);
$this->setupEntityTypes();
$this->entityResolverManager->setRouteOptions($route);
// If expected parameters have not been provided, assert they are unchanged.
$this->assertEquals($expected_parameters ?: $parameters, $route->getOption('parameters'));
}
/**
* Data provider for ::testSetLatestRevisionFlag.
*/
public function setLatestRevisionFlagTestCases() {
return [
'Entity parameter not on an entity form' => [
[],
[
'entity_test' => [
'type' => 'entity:entity_test_rev',
],
],
],
'Entity parameter on an entity form' => [
[
'_entity_form' => 'entity_test_rev.edit'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
],
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => TRUE,
],
],
],
'Entity form with no operation' => [
[
'_entity_form' => 'entity_test_rev'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
],
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => TRUE,
],
],
],
'Multiple entity parameters on an entity form' => [
[
'_entity_form' => 'entity_test_rev.edit'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
],
'node' => [
'type' => 'entity:node',
],
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => TRUE,
],
'node' => [
'type' => 'entity:node',
],
],
],
'Overriden load_latest_revision flag does not change' => [
[
'_entity_form' => 'entity_test_rev.edit'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => FALSE,
],
],
],
'Non-revisionable entity type will not change' => [
[
'_entity_form' => 'entity_test.edit'
],
[
'entity_test' => [
'type' => 'entity:entity_test',
],
],
FALSE,
FALSE,
],
'Overriden load_latest_revision flag does not change with multiple parameters' => [
[
'_entity_form' => 'entity_test_rev.edit'
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
],
'node' => [
'type' => 'entity:node',
'load_latest_revision' => FALSE,
],
],
[
'entity_test_rev' => [
'type' => 'entity:entity_test_rev',
'load_latest_revision' => TRUE,
],
'node' => [
'type' => 'entity:node',
'load_latest_revision' => FALSE,
],
],
],
];
}
}
/**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment