Commit babf1e99 authored by catch's avatar catch

Issue #2943899 by Sam152, amateescu, tstoeckler: Moderation state field cannot...

Issue #2943899 by Sam152, amateescu, tstoeckler: Moderation state field cannot be updated via REST, because special handling in ModerationStateFieldItemList

(cherry picked from commit 1e5459eb)
parent e3829e1f
......@@ -127,7 +127,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
$transitions = $this->validator->getValidTransitions($entity, $this->currentUser);
$transition_labels = [];
$default_value = NULL;
$default_value = $items->value;
foreach ($transitions as $transition) {
$transition_to_state = $transition->to();
$transition_labels[$transition_to_state->id()] = $transition_to_state->label();
......
......@@ -16,7 +16,6 @@
class ModerationStateFieldItemList extends FieldItemList {
use ComputedItemListTrait {
ensureComputedValue as traitEnsureComputedValue;
get as traitGet;
}
......@@ -34,19 +33,6 @@ protected function computeValue() {
}
}
/**
* {@inheritdoc}
*/
protected function ensureComputedValue() {
// If the moderation state field is set to an empty value, always recompute
// the state. Empty is not a valid moderation state value, when none is
// present the default state is used.
if (!isset($this->list[0]) || $this->list[0]->isEmpty()) {
$this->valueComputed = FALSE;
}
$this->traitEnsureComputedValue();
}
/**
* Gets the moderation state ID linked to a content entity revision.
*
......@@ -140,10 +126,8 @@ public function onChange($delta) {
*/
public function setValue($values, $notify = TRUE) {
parent::setValue($values, $notify);
if (isset($this->list[0])) {
$this->valueComputed = TRUE;
}
// If the parent created a field item and if the parent should be notified
// about the change (e.g. this is not initialized with the current value),
// update the moderated entity.
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\content_moderation\ModerationInformationInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraint;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
......@@ -89,6 +90,13 @@ public function validate($value, Constraint $constraint) {
return;
}
// If the entity is moderated and the item list is empty, ensure users see
// the same required message as typical NotNull constraints.
if ($value->isEmpty()) {
$this->context->addViolation((new NotNullConstraint())->message);
return;
}
$workflow = $this->moderationInformation->getWorkflowForEntity($entity);
if (!$workflow->getTypePlugin()->hasState($entity->moderation_state->value)) {
......
......@@ -102,20 +102,70 @@ public function testGet() {
}
/**
* Tests the computed field when it is unset or set to an empty value.
* Tests the item list when it is emptied and appended to.
*/
public function testSetEmptyState() {
public function testEmptyStateAndAppend() {
// This test case mimics the lifecycle of an entity that is being patched in
// a rest resource.
$this->testNode->moderation_state->setValue([]);
$this->assertTrue($this->testNode->moderation_state->isEmpty());
$this->assertEmptiedModerationFieldItemList();
$this->testNode->moderation_state->appendItem();
$this->assertEquals(1, $this->testNode->moderation_state->count());
$this->assertEquals(NULL, $this->testNode->moderation_state->value);
$this->assertEmptiedModerationFieldItemList();
}
/**
* Test an empty value assigned to the field item.
*/
public function testEmptyFieldItem() {
$this->testNode->moderation_state->value = '';
$this->assertEquals('draft', $this->testNode->moderation_state->value);
$this->assertEquals('', $this->testNode->moderation_state->value);
$this->assertEmptiedModerationFieldItemList();
}
/**
* Test an empty value assigned to the field item list.
*/
public function testEmptyFieldItemList() {
$this->testNode->moderation_state = '';
$this->assertEquals('draft', $this->testNode->moderation_state->value);
$this->assertEquals('', $this->testNode->moderation_state->value);
$this->assertEmptiedModerationFieldItemList();
}
/**
* Test the field item when it is unset.
*/
public function testUnsetItemList() {
unset($this->testNode->moderation_state);
$this->assertEquals('draft', $this->testNode->moderation_state->value);
$this->assertEquals(NULL, $this->testNode->moderation_state->value);
$this->assertEmptiedModerationFieldItemList();
}
/**
* Test the field item when it is assigned NULL.
*/
public function testAssignNullItemList() {
$this->testNode->moderation_state = NULL;
$this->assertEquals('draft', $this->testNode->moderation_state->value);
$this->assertEquals(NULL, $this->testNode->moderation_state->value);
$this->assertEmptiedModerationFieldItemList();
}
/**
* Assert the set of expectations when the moderation state field is emptied.
*/
protected function assertEmptiedModerationFieldItemList() {
$this->assertTrue($this->testNode->moderation_state->isEmpty());
// Test the empty value causes a violation in the entity.
$violations = $this->testNode->validate();
$this->assertCount(1, $violations);
$this->assertEquals('This value should not be null.', $violations->get(0)->getMessage());
// Test that incorrectly saving the entity regardless will not produce a
// change in the moderation state.
$this->testNode->save();
$this->assertEquals('draft', Node::load($this->testNode->id())->moderation_state->value);
}
/**
......@@ -131,6 +181,7 @@ public function testNonModeratedEntity() {
$unmoderated_node->moderation_state = NULL;
$this->assertEquals(0, $unmoderated_node->moderation_state->count());
$this->assertCount(0, $unmoderated_node->validate());
}
/**
......
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ModeratedNodeJsonAnonTest extends ModeratedNodeResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ModeratedNodeJsonBasicAuthTest extends ModeratedNodeResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ModeratedNodeJsonCookieTest extends ModeratedNodeResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
use Drupal\Tests\node\Functional\Rest\NodeResourceTestBase;
/**
* Extend the Node resource test base and apply moderation to the entity.
*/
abstract class ModeratedNodeResourceTestBase extends NodeResourceTestBase {
use ContentModerationTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['content_moderation'];
/**
* The test editorial workflow.
*
* @var \Drupal\workflows\WorkflowInterface
*/
protected $workflow;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
parent::setUpAuthorization($method);
switch ($method) {
case 'POST':
case 'PATCH':
case 'DELETE':
$this->grantPermissionsToTestedRole(['use editorial transition publish', 'use editorial transition create_new_draft']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$entity = parent::createEntity();
if (!$this->workflow) {
$this->workflow = $this->createEditorialWorkflow();
}
$this->workflow->getTypePlugin()->addEntityTypeAndBundle($entity->getEntityTypeId(), $entity->bundle());
$this->workflow->save();
return $entity;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return array_merge(parent::getExpectedNormalizedEntity(), [
'moderation_state' => [
[
'value' => 'published',
],
],
'vid' => [
[
'value' => (int) $this->entity->getRevisionId(),
],
],
]);
}
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ModeratedNodeXmlAnonTest extends ModeratedNodeResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
public function testPatchPath() {
// Deserialization of the XML format is not supported.
$this->markTestSkipped();
}
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ModeratedNodeXmlBasicAuthTest extends ModeratedNodeResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
/**
* {@inheritdoc}
*/
public function testPatchPath() {
// Deserialization of the XML format is not supported.
$this->markTestSkipped();
}
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ModeratedNodeXmlCookieTest extends ModeratedNodeResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
public function testPatchPath() {
// Deserialization of the XML format is not supported.
$this->markTestSkipped();
}
}
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