diff --git a/core/lib/Drupal/Core/Entity/EditorialContentEntityBase.php b/core/lib/Drupal/Core/Entity/EditorialContentEntityBase.php new file mode 100644 index 0000000000000000000000000000000000000000..ff6abcd0d552b2970e781db6faeddc50975d3606 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EditorialContentEntityBase.php @@ -0,0 +1,31 @@ +<?php + +namespace Drupal\Core\Entity; + +/** + * Provides a base entity class with extended revision and publishing support. + * + * @ingroup entity_api + */ +abstract class EditorialContentEntityBase extends ContentEntityBase implements EntityChangedInterface, EntityPublishedInterface, RevisionLogInterface { + + use EntityChangedTrait; + use EntityPublishedTrait; + use RevisionLogEntityTrait; + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + // Add the revision metadata fields. + $fields += static::revisionLogBaseFieldDefinitions($entity_type); + + // Add the published field. + $fields += static::publishedBaseFieldDefinitions($entity_type); + + return $fields; + } + +} diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php index f9f4271e3b6acc59038824e76f43115a62736c4f..5387c7ef7e3fb7c957f3ff40f3f6d8bd0bd42b43 100644 --- a/core/lib/Drupal/Core/Entity/entity.api.php +++ b/core/lib/Drupal/Core/Entity/entity.api.php @@ -271,6 +271,11 @@ * either \Drupal\Core\Config\Entity\ConfigEntityBase or * \Drupal\Core\Entity\ContentEntityBase, with annotation for * \@ConfigEntityType or \@ContentEntityType in its documentation block. + * If you are defining a content entity type, it is recommended to extend the + * \Drupal\Core\Entity\EditorialContentEntityBase base class in order to get + * out-of-the-box support for Entity API's revisioning and publishing + * features, which will allow your entity type to be used with Drupal's + * editorial workflow provided by the Content Moderation module. * - The 'id' annotation gives the entity type ID, and the 'label' annotation * gives the human-readable name of the entity type. If you are defining a * content entity type that uses bundles, the 'bundle_label' annotation gives diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php index e92a3d664349e58c01cb299ea43ae979b19711c8..d4ed47c37c1ba58366f6bff66575e7d06da59b96 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php @@ -59,20 +59,20 @@ protected function getExpectedNormalizedEntity() { 'type' => [ 'href' => $this->baseUrl . '/rest/type/node/camelids', ], - $this->baseUrl . '/rest/relation/node/camelids/uid' => [ + $this->baseUrl . '/rest/relation/node/camelids/revision_uid' => [ [ 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', - 'lang' => 'en', ], ], - $this->baseUrl . '/rest/relation/node/camelids/revision_uid' => [ + $this->baseUrl . '/rest/relation/node/camelids/uid' => [ [ 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', + 'lang' => 'en', ], ], ], '_embedded' => [ - $this->baseUrl . '/rest/relation/node/camelids/uid' => [ + $this->baseUrl . '/rest/relation/node/camelids/revision_uid' => [ [ '_links' => [ 'self' => [ @@ -85,10 +85,9 @@ protected function getExpectedNormalizedEntity() { 'uuid' => [ ['value' => $author->uuid()] ], - 'lang' => 'en', ], ], - $this->baseUrl . '/rest/relation/node/camelids/revision_uid' => [ + $this->baseUrl . '/rest/relation/node/camelids/uid' => [ [ '_links' => [ 'self' => [ @@ -101,6 +100,7 @@ protected function getExpectedNormalizedEntity() { 'uuid' => [ ['value' => $author->uuid()] ], + 'lang' => 'en', ], ], ], diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index c11a18889a3953ea6fb25387aab7b0e204edb9c5..b66628c2f34e13a079f688e3a20f0e5ed7add241 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -2,9 +2,7 @@ namespace Drupal\node\Entity; -use Drupal\Core\Entity\ContentEntityBase; -use Drupal\Core\Entity\EntityChangedTrait; -use Drupal\Core\Entity\EntityPublishedTrait; +use Drupal\Core\Entity\EditorialContentEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -79,10 +77,7 @@ * } * ) */ -class Node extends ContentEntityBase implements NodeInterface { - - use EntityChangedTrait; - use EntityPublishedTrait; +class Node extends EditorialContentEntityBase implements NodeInterface { /** * Whether the node is being previewed or not. @@ -283,21 +278,6 @@ public function setOwner(UserInterface $account) { return $this; } - /** - * {@inheritdoc} - */ - public function getRevisionCreationTime() { - return $this->get('revision_timestamp')->value; - } - - /** - * {@inheritdoc} - */ - public function setRevisionCreationTime($timestamp) { - $this->set('revision_timestamp', $timestamp); - return $this; - } - /** * {@inheritdoc} */ @@ -305,13 +285,6 @@ public function getRevisionAuthor() { return $this->getRevisionUser(); } - /** - * {@inheritdoc} - */ - public function getRevisionUser() { - return $this->get('revision_uid')->entity; - } - /** * {@inheritdoc} */ @@ -320,50 +293,11 @@ public function setRevisionAuthorId($uid) { return $this; } - /** - * {@inheritdoc} - */ - public function setRevisionUser(UserInterface $user) { - $this->set('revision_uid', $user); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getRevisionUserId() { - return $this->get('revision_uid')->entity->id(); - } - - /** - * {@inheritdoc} - */ - public function setRevisionUserId($user_id) { - $this->set('revision_uid', $user_id); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getRevisionLogMessage() { - return $this->get('revision_log')->value; - } - - /** - * {@inheritdoc} - */ - public function setRevisionLogMessage($revision_log_message) { - $this->set('revision_log', $revision_log_message); - return $this; - } - /** * {@inheritdoc} */ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields = parent::baseFieldDefinitions($entity_type); - $fields += static::publishedBaseFieldDefinitions($entity_type); $fields['title'] = BaseFieldDefinition::create('string') ->setLabel(t('Title')) @@ -455,30 +389,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ]) ->setDisplayConfigurable('form', TRUE); - $fields['revision_timestamp'] = BaseFieldDefinition::create('created') - ->setLabel(t('Revision timestamp')) - ->setDescription(t('The time that the current revision was created.')) - ->setRevisionable(TRUE); - - $fields['revision_uid'] = BaseFieldDefinition::create('entity_reference') - ->setLabel(t('Revision user ID')) - ->setDescription(t('The user ID of the author of the current revision.')) - ->setSetting('target_type', 'user') - ->setRevisionable(TRUE); - - $fields['revision_log'] = BaseFieldDefinition::create('string_long') - ->setLabel(t('Revision log message')) - ->setDescription(t('Briefly describe the changes you have made.')) - ->setRevisionable(TRUE) - ->setDefaultValue('') - ->setDisplayOptions('form', [ - 'type' => 'string_textarea', - 'weight' => 25, - 'settings' => [ - 'rows' => 4, - ], - ]); - $fields['revision_translation_affected'] = BaseFieldDefinition::create('boolean') ->setLabel(t('Revision translation affected')) ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.')) diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 6cd5b6b6eedd60355ed083d8a0c596e0b779472c..80d8a061004443281d243d1eb806b45ca154a7e4 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -900,19 +900,15 @@ public function testPatch() { // DX: 403 when sending PATCH request with read-only fields. - // First send all fields (the "maximum normalization"). Assert the expected - // error message for the first PATCH-protected field. Remove that field from - // the normalization, send another request, assert the next PATCH-protected - // field error message. And so on. - $max_normalization = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format); - for ($i = 0; $i < count(static::$patchProtectedFieldNames); $i++) { - $max_normalization = $this->removeFieldsFromNormalization($max_normalization, array_slice(static::$patchProtectedFieldNames, 0, $i)); - $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format); + foreach (static::$patchProtectedFieldNames as $field_name) { + $normalization = $this->getNormalizedPatchEntity() + [$field_name => [['value' => $this->randomString()]]]; + $request_options[RequestOptions::BODY] = $this->serializer->serialize($normalization, static::$format); $response = $this->request('PATCH', $url, $request_options); - $this->assertResourceErrorResponse(403, "Access denied on updating field '" . static::$patchProtectedFieldNames[$i] . "'.", $response); + $this->assertResourceErrorResponse(403, "Access denied on updating field '$field_name'.", $response); } // 200 for well-formed request that sends the maximum number of fields. + $max_normalization = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format); $max_normalization = $this->removeFieldsFromNormalization($max_normalization, static::$patchProtectedFieldNames); $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format); $response = $this->request('PATCH', $url, $request_options);