Commit d55b420a authored by alexpott's avatar alexpott
Browse files

Issue #2812811 by Sam152, timmillwood, amateescu, alexpott, catch: Use...

Issue #2812811 by Sam152, timmillwood, amateescu, alexpott, catch: Use EntityPublishedInterface during moderation of entities to add support beyond nodes
parent b1500e07
......@@ -4,6 +4,7 @@
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
......@@ -32,6 +33,11 @@ public function onPresave(ContentEntityInterface $entity, $default_revision, $pu
// This is probably not necessary if configuration is setup correctly.
$entity->setNewRevision(TRUE);
$entity->isDefaultRevision($default_revision);
// Update publishing status if it can be updated and if it needs updating.
if (($entity instanceof EntityPublishedInterface) && $entity->isPublished() !== $published_state) {
$published_state ? $entity->setPublished() : $entity->setUnpublished();
}
}
/**
......
......@@ -3,7 +3,6 @@
namespace Drupal\content_moderation\Entity\Handler;
use Drupal\content_moderation\ModerationInformationInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -39,18 +38,6 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
);
}
/**
* {@inheritdoc}
*/
public function onPresave(ContentEntityInterface $entity, $default_revision, $published_state) {
if ($this->shouldModerate($entity, $published_state)) {
parent::onPresave($entity, $default_revision, $published_state);
// Only nodes have a concept of published.
/** @var \Drupal\node\NodeInterface $entity */
$entity->setPublished($published_state);
}
}
/**
* {@inheritdoc}
*/
......@@ -74,22 +61,4 @@ public function enforceRevisionsBundleFormAlter(array &$form, FormStateInterface
}
}
/**
* Check if an entity's default revision and/or state needs adjusting.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity to check.
* @param bool $published_state
* Whether the state being transitioned to is a published state or not.
*
* @return bool
* TRUE when either the default revision or the state needs to be updated.
*/
protected function shouldModerate(ContentEntityInterface $entity, $published_state) {
// @todo clarify the first condition.
// First condition is needed so you can add a translation.
// Second condition checks to see if the published status has changed.
return $entity->isDefaultTranslation() || $entity->isPublished() !== $published_state;
}
}
......@@ -3,14 +3,14 @@
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\content_moderation\Entity\ContentModerationState;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\entity_test\Entity\EntityTestRev;
use Drupal\entity_test\Entity\EntityTestWithBundle;
use Drupal\Core\Entity\EntityInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\workflows\Entity\Workflow;
/**
......@@ -26,6 +26,7 @@ class ContentModerationStateTest extends KernelTestBase {
public static $modules = [
'entity_test',
'node',
'block_content',
'content_moderation',
'user',
'system',
......@@ -35,6 +36,11 @@ class ContentModerationStateTest extends KernelTestBase {
'workflows',
];
/**
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
......@@ -46,36 +52,56 @@ protected function setUp() {
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test_with_bundle');
$this->installEntitySchema('entity_test_rev');
$this->installEntitySchema('entity_test_mulrevpub');
$this->installEntitySchema('block_content');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
$this->entityTypeManager = $this->container->get('entity_type.manager');
}
/**
* Tests basic monolingual content moderation through the API.
*
* @dataProvider basicModerationTestCases
*/
public function testBasicModeration() {
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->save();
public function testBasicModeration($entity_type_id) {
// Make the 'entity_test_with_bundle' entity type revisionable.
if ($entity_type_id == 'entity_test_with_bundle') {
$this->setEntityTestWithBundleKeys(['revision' => 'revision_id']);
}
$entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
$bundle_id = $entity_type_id;
$bundle_entity_type_id = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType();
if ($bundle_entity_type_id) {
$bundle_entity_type_definition = $this->entityTypeManager->getDefinition($bundle_entity_type_id);
$entity_type_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
$entity_type = $entity_type_storage->create([
$bundle_entity_type_definition->getKey('id') => 'example',
]);
$entity_type->save();
$bundle_id = $entity_type->id();
}
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
$workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id);
$workflow->save();
$node = Node::create([
'type' => 'example',
$entity = $entity_storage->create([
'title' => 'Test title',
$this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id,
]);
$node->save();
$node = $this->reloadNode($node);
$this->assertEquals('draft', $node->moderation_state->value);
$entity->save();
$entity = $this->reloadEntity($entity);
$this->assertEquals('draft', $entity->moderation_state->value);
$node->moderation_state->value = 'published';
$node->save();
$entity->moderation_state->value = 'published';
$entity->save();
$node = $this->reloadNode($node);
$this->assertEquals('published', $node->moderation_state->value);
$entity = $this->reloadEntity($entity);
$this->assertEquals('published', $entity->moderation_state->value);
// Change the state without saving the node.
$content_moderation_state = ContentModerationState::load(1);
......@@ -83,40 +109,71 @@ public function testBasicModeration() {
$content_moderation_state->setNewRevision(TRUE);
$content_moderation_state->save();
$node = $this->reloadNode($node, 3);
$this->assertEquals('draft', $node->moderation_state->value);
$this->assertFalse($node->isPublished());
$entity = $this->reloadEntity($entity, 3);
$this->assertEquals('draft', $entity->moderation_state->value);
if ($entity instanceof EntityPublishedInterface) {
$this->assertFalse($entity->isPublished());
}
// Get the default revision.
$node = $this->reloadNode($node);
$this->assertTrue($node->isPublished());
$this->assertEquals(2, $node->getRevisionId());
$entity = $this->reloadEntity($entity);
if ($entity instanceof EntityPublishedInterface) {
$this->assertTrue((bool) $entity->isPublished());
}
$this->assertEquals(2, $entity->getRevisionId());
$node->moderation_state->value = 'published';
$node->save();
$entity->moderation_state->value = 'published';
$entity->save();
$node = $this->reloadNode($node, 4);
$this->assertEquals('published', $node->moderation_state->value);
$entity = $this->reloadEntity($entity, 4);
$this->assertEquals('published', $entity->moderation_state->value);
// Get the default revision.
$node = $this->reloadNode($node);
$this->assertTrue($node->isPublished());
$this->assertEquals(4, $node->getRevisionId());
$entity = $this->reloadEntity($entity);
if ($entity instanceof EntityPublishedInterface) {
$this->assertTrue((bool) $entity->isPublished());
}
$this->assertEquals(4, $entity->getRevisionId());
// Update the node to archived which will then be the default revision.
$node->moderation_state->value = 'archived';
$node->save();
$entity->moderation_state->value = 'archived';
$entity->save();
// Revert to the previous (published) revision.
$previous_revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision(4);
$previous_revision = $entity_storage->loadRevision(4);
$previous_revision->isDefaultRevision(TRUE);
$previous_revision->setNewRevision(TRUE);
$previous_revision->save();
// Get the default revision.
$node = $this->reloadNode($node);
$this->assertEquals('published', $node->moderation_state->value);
$this->assertTrue($node->isPublished());
$entity = $this->reloadEntity($entity);
$this->assertEquals('published', $entity->moderation_state->value);
if ($entity instanceof EntityPublishedInterface) {
$this->assertTrue($entity->isPublished());
}
}
/**
* Test cases for basic moderation test.
*/
public function basicModerationTestCases() {
return [
'Nodes' => [
'node',
],
'Block content' => [
'block_content',
],
'Test Entity with Bundle' => [
'entity_test_with_bundle',
],
'Test entity - revisions, data table, and published interface' => [
'entity_test_mulrevpub',
],
'Entity Test with revisions' => [
'entity_test_rev',
]
];
}
/**
......@@ -140,37 +197,37 @@ public function testMultilingualModeration() {
]);
// Revision 1 (en).
$english_node
->setPublished(FALSE)
->setUnpublished()
->save();
$this->assertEquals('draft', $english_node->moderation_state->value);
$this->assertFalse($english_node->isPublished());
// Create a French translation.
$french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
$french_node->setPublished(FALSE);
$french_node->setUnpublished();
// Revision 1 (fr).
$french_node->save();
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
$this->assertEquals('draft', $french_node->moderation_state->value);
$this->assertFalse($french_node->isPublished());
// Move English node to create another draft.
$english_node = $this->reloadNode($english_node);
$english_node = $this->reloadEntity($english_node);
$english_node->moderation_state->value = 'draft';
// Revision 2 (en, fr).
$english_node->save();
$english_node = $this->reloadNode($english_node);
$english_node = $this->reloadEntity($english_node);
$this->assertEquals('draft', $english_node->moderation_state->value);
// French node should still be in draft.
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
$this->assertEquals('draft', $french_node->moderation_state->value);
// Publish the French node.
$french_node->moderation_state->value = 'published';
// Revision 3 (en, fr).
$french_node->save();
$french_node = $this->reloadNode($french_node)->getTranslation('fr');
$french_node = $this->reloadEntity($french_node)->getTranslation('fr');
$this->assertTrue($french_node->isPublished());
$this->assertEquals('published', $french_node->moderation_state->value);
$this->assertTrue($french_node->isPublished());
......@@ -181,16 +238,16 @@ public function testMultilingualModeration() {
$english_node->moderation_state->value = 'published';
// Revision 4 (en, fr).
$english_node->save();
$english_node = $this->reloadNode($english_node);
$english_node = $this->reloadEntity($english_node);
$this->assertTrue($english_node->isPublished());
// Move the French node back to draft.
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
$this->assertTrue($french_node->isPublished());
$french_node->moderation_state->value = 'draft';
// Revision 5 (en, fr).
$french_node->save();
$french_node = $this->reloadNode($english_node, 5)->getTranslation('fr');
$french_node = $this->reloadEntity($english_node, 5)->getTranslation('fr');
$this->assertFalse($french_node->isPublished());
$this->assertTrue($french_node->getTranslation('en')->isPublished());
......@@ -198,7 +255,7 @@ public function testMultilingualModeration() {
$french_node->moderation_state->value = 'published';
// Revision 6 (en, fr).
$french_node->save();
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
$this->assertTrue($french_node->isPublished());
// Change the EN state without saving the node.
......@@ -207,10 +264,10 @@ public function testMultilingualModeration() {
$content_moderation_state->setNewRevision(TRUE);
// Revision 7 (en, fr).
$content_moderation_state->save();
$english_node = $this->reloadNode($french_node, $french_node->getRevisionId() + 1);
$english_node = $this->reloadEntity($french_node, $french_node->getRevisionId() + 1);
$this->assertEquals('draft', $english_node->moderation_state->value);
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
$this->assertEquals('published', $french_node->moderation_state->value);
// This should unpublish the French node.
......@@ -221,16 +278,16 @@ public function testMultilingualModeration() {
// Revision 8 (en, fr).
$content_moderation_state->save();
$english_node = $this->reloadNode($english_node, $english_node->getRevisionId());
$english_node = $this->reloadEntity($english_node, $english_node->getRevisionId());
$this->assertEquals('draft', $english_node->moderation_state->value);
$french_node = $this->reloadNode($english_node, '8')->getTranslation('fr');
$french_node = $this->reloadEntity($english_node, '8')->getTranslation('fr');
$this->assertEquals('draft', $french_node->moderation_state->value);
// Switching the moderation state to an unpublished state should update the
// entity.
$this->assertFalse($french_node->isPublished());
// Get the default english node.
$english_node = $this->reloadNode($english_node);
$english_node = $this->reloadEntity($english_node);
$this->assertTrue($english_node->isPublished());
$this->assertEquals(6, $english_node->getRevisionId());
}
......@@ -240,12 +297,7 @@ public function testMultilingualModeration() {
*/
public function testNonTranslatableEntityTypeModeration() {
// Make the 'entity_test_with_bundle' entity type revisionable.
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
$keys = $entity_type->getKeys();
$keys['revision'] = 'revision_id';
$entity_type->set('entity_keys', $keys);
\Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
$this->setEntityTestWithBundleKeys(['revision' => 'revision_id']);
// Create a test bundle.
$entity_test_bundle = EntityTestBundle::create([
......@@ -281,13 +333,7 @@ public function testNonTranslatableEntityTypeModeration() {
public function testNonLangcodeEntityTypeModeration() {
// Make the 'entity_test_with_bundle' entity type revisionable and unset
// the langcode entity key.
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
$keys = $entity_type->getKeys();
$keys['revision'] = 'revision_id';
unset($keys['langcode']);
$entity_type->set('entity_keys', $keys);
\Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
$this->setEntityTestWithBundleKeys(['revision' => 'revision_id'], ['langcode']);
// Create a test bundle.
$entity_test_bundle = EntityTestBundle::create([
......@@ -317,50 +363,43 @@ public function testNonLangcodeEntityTypeModeration() {
}
/**
* Tests that entity types without config bundles can be moderated.
* Set the keys on the test entity type.
*
* @param array $keys
* The entity keys to override
* @param array $remove_keys
* Keys to remove.
*/
public function testNonBundleConfigEntityTypeModeration() {
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
$workflow->save();
// Check that the tested entity type does not have bundles managed by a
// config entity type.
$entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
$this->assertNull($entity_type->getBundleEntityType(), 'The test entity type does not have config bundles.');
// Create a test entity.
$entity_test = EntityTestRev::create([
'type' => 'entity_test_rev'
]);
$entity_test->save();
$this->assertEquals('draft', $entity_test->moderation_state->value);
$entity_test->moderation_state->value = 'published';
$entity_test->save();
$this->assertEquals('published', EntityTestRev::load($entity_test->id())->moderation_state->value);
protected function setEntityTestWithBundleKeys($keys, $remove_keys = []) {
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
$original_keys = $entity_type->getKeys();
foreach ($remove_keys as $remove_key) {
unset($original_keys[$remove_key]);
}
$entity_type->set('entity_keys', $keys + $original_keys);
\Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
}
/**
* Reloads the node after clearing the static cache.
* Reloads the entity after clearing the static cache.
*
* @param \Drupal\node\NodeInterface $node
* The node to reload.
* @param int|false $revision_id
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to reload.
* @param int|bool $revision_id
* The specific revision ID to load. Defaults FALSE and just loads the
* default revision.
*
* @return \Drupal\node\NodeInterface
* The reloaded node.
* @return \Drupal\Core\Entity\EntityInterface
* The reloaded entity.
*/
protected function reloadNode(NodeInterface $node, $revision_id = FALSE) {
$storage = \Drupal::entityTypeManager()->getStorage('node');
$storage->resetCache([$node->id()]);
protected function reloadEntity(EntityInterface $entity, $revision_id = FALSE) {
$storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
$storage->resetCache([$entity->id()]);
if ($revision_id) {
return $storage->loadRevision($revision_id);
}
return $storage->load($node->id());
return $storage->load($entity->id());
}
}
<?php
namespace Drupal\entity_test\Entity;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityPublishedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Defines the test entity class.
*
* @ContentEntityType(
* id = "entity_test_mulrevpub",
* label = @Translation("Test entity - revisions, data table, and published interface"),
* handlers = {
* "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
* "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
* "form" = {
* "default" = "Drupal\entity_test\EntityTestForm",
* "delete" = "Drupal\entity_test\EntityTestDeleteForm"
* },
* "translation" = "Drupal\content_translation\ContentTranslationHandler",
* "views_data" = "Drupal\views\EntityViewsData",
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* base_table = "entity_test_mulrevpub",
* data_table = "entity_test_mulrevpub_property_data",
* revision_table = "entity_test_mulrevpub_revision",
* revision_data_table = "entity_test_mulrevpub_property_revision",
* admin_permission = "administer entity_test content",
* translatable = TRUE,
* show_revision_ui = TRUE,
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "bundle" = "type",
* "revision" = "revision_id",
* "label" = "name",
* "langcode" = "langcode",
* "published" = "status",
* },
* links = {
* "add-form" = "/entity_test_mulrevpub/add",
* "canonical" = "/entity_test_mulrevpub/manage/{entity_test_mulrevpub}",
* "delete-form" = "/entity_test/delete/entity_test_mulrevpub/{entity_test_mulrevpub}",
* "edit-form" = "/entity_test_mulrevpub/manage/{entity_test_mulrevpub}/edit",
* "revision" = "/entity_test_mulrevpub/{entity_test_mulrevpub}/revision/{entity_test_mulrevpub_revision}/view",
* }
* )
*/
class EntityTestMulRevPub extends EntityTestMulRev implements EntityPublishedInterface {
use EntityPublishedTrait;
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
return parent::baseFieldDefinitions($entity_type) + EntityPublishedTrait::publishedBaseFieldDefinitions($entity_type);
}
}
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