diff --git a/core/modules/content_moderation/src/EntityTypeInfo.php b/core/modules/content_moderation/src/EntityTypeInfo.php index fe8e6eb45c..cf5bd715f8 100644 --- a/core/modules/content_moderation/src/EntityTypeInfo.php +++ b/core/modules/content_moderation/src/EntityTypeInfo.php @@ -144,7 +144,6 @@ public function entityTypeAlter(array &$entity_types) { $entity_type_to_exclude = [ 'path_alias', 'workspace', - 'taxonomy_term', ]; if ($entity_type->isRevisionable() && !$entity_type->isInternal() && !in_array($entity_type_id, $entity_type_to_exclude)) { $entity_types[$entity_type_id] = $this->addModerationToEntityType($entity_type); diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php index 534a689b45..2f9bdcf82a 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php @@ -94,6 +94,7 @@ protected function setUp(): void { $this->installEntitySchema('block_content'); $this->installEntitySchema('media'); $this->installEntitySchema('file'); + $this->installEntitySchema('taxonomy_term'); $this->installEntitySchema('content_moderation_state'); $this->installConfig('content_moderation'); $this->installSchema('file', 'file_usage'); @@ -173,6 +174,9 @@ public function basicModerationTestCases() { 'Nodes' => [ 'node', ], + 'Taxonomy term' => [ + 'taxonomy_term', + ], 'Block content' => [ 'block_content', ], @@ -294,7 +298,7 @@ public function testContentModerationStateTranslationDataRemoval($entity_type_id /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $this->createEntity($entity_type_id, 'published'); $langcode = 'fr'; - $translation = $entity->addTranslation($langcode, ['title' => 'French title test']); + $translation = $entity->addTranslation($langcode, [$entity->getEntityType()->getKey('label') => 'French title test']); // Make sure we add values for all of the required fields. if ($entity_type_id == 'block_content') { $translation->info = $this->randomString(); @@ -806,14 +810,4 @@ protected function assertDefaultRevision(EntityInterface $entity, int $revision_ } } - /** - * Tests that the 'taxonomy_term' entity type cannot be moderated. - */ - public function testTaxonomyTermEntityTypeModeration() { - /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */ - $moderation_info = \Drupal::service('content_moderation.moderation_information'); - $entity_type = \Drupal::entityTypeManager()->getDefinition('taxonomy_term'); - $this->assertFalse($moderation_info->canModerateEntitiesOfEntityType($entity_type)); - } - } diff --git a/core/modules/taxonomy/src/TermAccessControlHandler.php b/core/modules/taxonomy/src/TermAccessControlHandler.php index b25dca4627..31ff834c93 100644 --- a/core/modules/taxonomy/src/TermAccessControlHandler.php +++ b/core/modules/taxonomy/src/TermAccessControlHandler.php @@ -46,6 +46,13 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter return AccessResult::neutral()->setReason("The following permissions are required: 'delete terms in {$entity->bundle()}' OR 'administer taxonomy'."); + case 'view all revisions': + if ($account->hasPermission('view all revisions')) { + return AccessResult::allowed()->cachePerPermissions(); + } + + return AccessResult::neutral()->setReason("The following permissions are required: 'view all revisions' OR 'administer taxonomy'."); + default: // No opinion. return AccessResult::neutral()->cachePerPermissions(); diff --git a/core/modules/taxonomy/tests/src/Functional/TaxonomyTermContentModerationTest.php b/core/modules/taxonomy/tests/src/Functional/TaxonomyTermContentModerationTest.php new file mode 100644 index 0000000000..a9683f6a15 --- /dev/null +++ b/core/modules/taxonomy/tests/src/Functional/TaxonomyTermContentModerationTest.php @@ -0,0 +1,215 @@ +createEditorialWorkflow(); + + $this->drupalLogin($this->drupalCreateUser([ + 'administer taxonomy', + 'use editorial transition create_new_draft', + 'use editorial transition publish', + 'view any unpublished content', + 'view latest version', + ])); + + $this->vocabulary = $this->createVocabulary(); + + // Set the vocabulary as moderated. + $workflow = Workflow::load('editorial'); + $workflow->getTypePlugin()->addEntityTypeAndBundle('taxonomy_term', $this->vocabulary->id()); + $workflow->save(); + } + + /** + * Tests taxonomy term parents on a moderated vocabulary. + */ + public function testTaxonomyTermParents() { + $assert_session = $this->assertSession(); + // Create a simple hierarchy in the vocabulary, a root term and three parent + // terms. + $root = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'moderation_state' => 'published']); + $parent_1 = $this->createTerm($this->vocabulary, [ + 'langcode' => 'en', + 'moderation_state' => 'published', + 'parent' => $root->id(), + ]); + $parent_2 = $this->createTerm($this->vocabulary, [ + 'langcode' => 'en', + 'moderation_state' => 'published', + 'parent' => $root->id(), + ]); + $parent_3 = $this->createTerm($this->vocabulary, [ + 'langcode' => 'en', + 'moderation_state' => 'published', + 'parent' => $root->id(), + ]); + + // Create a child term and assign one of the parents above. + $child = $this->createTerm($this->vocabulary, [ + 'langcode' => 'en', + 'moderation_state' => 'published', + 'parent' => $parent_1->id(), + ]); + + /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ + $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $validation_message = 'You can only change the hierarchy for the published version of this term.'; + + // Add a pending revision without changing the term parent. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->submitForm(['moderation_state[0][state]' => 'draft'], 'Save'); + + $assert_session->pageTextNotContains($validation_message); + + // Add a pending revision and change the parent. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->submitForm(['parent[]' => [$parent_2->id()], 'moderation_state[0][state]' => 'draft'], 'Save'); + + // Check that parents were not changed. + $assert_session->pageTextContains($validation_message); + $taxonomy_storage->resetCache(); + $this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id()))); + + // Add a pending revision and add a new parent. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->submitForm(['parent[]' => [$parent_1->id(), $parent_3->id()], 'moderation_state[0][state]' => 'draft'], 'Save'); + + // Check that parents were not changed. + $assert_session->pageTextContains($validation_message); + $taxonomy_storage->resetCache(); + $this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id()))); + + // Add a pending revision and use the root term as a parent. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->submitForm(['parent[]' => [$root->id()], 'moderation_state[0][state]' => 'draft'], 'Save'); + + // Check that parents were not changed. + $assert_session->pageTextContains($validation_message); + $taxonomy_storage->resetCache(); + $this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id()))); + + // Add a pending revision and remove the parent. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->submitForm(['parent[]' => [], 'moderation_state[0][state]' => 'draft'], 'Save'); + + // Check that parents were not changed. + $assert_session->pageTextContains($validation_message); + $taxonomy_storage->resetCache(); + $this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id()))); + + // Add a published revision. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->submitForm(['moderation_state[0][state]' => 'published'], 'Save'); + + // Change the parents. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->submitForm(['parent[]' => [$parent_2->id()]], 'Save'); + + // Check that parents were changed. + $assert_session->pageTextNotContains($validation_message); + $taxonomy_storage->resetCache(); + $this->assertNotEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id()))); + + // Add a pending revision and change the weight. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->submitForm(['weight' => 10, 'moderation_state[0][state]' => 'draft'], 'Save'); + + // Check that weight was not changed. + $assert_session->pageTextContains($validation_message); + + // Add a new term without any parent and publish it. + $edit = [ + 'name[0][value]' => $this->randomMachineName(), + 'moderation_state[0][state]' => 'published', + ]; + $this->drupalGet("admin/structure/taxonomy/manage/{$this->vocabulary->id()}/add"); + $this->submitForm($edit, 'Save'); + // Add a pending revision without any changes. + $terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(["name" => $edit['name[0][value]']]); + $term = reset($terms); + $this->drupalGet('taxonomy/term/' . $term->id() . '/edit'); + $this->submitForm(['moderation_state[0][state]' => 'draft'], 'Save'); + $assert_session->pageTextNotContains($validation_message); + } + + /** + * Tests changing field values in pending revisions of taxonomy terms. + */ + public function testTaxonomyTermPendingRevisions() { + $assert_session = $this->assertSession(); + $default_term_name = 'term - default revision'; + $default_term_description = 'The default revision of a term.'; + $term = $this->createTerm($this->vocabulary, [ + 'name' => $default_term_name, + 'description' => $default_term_description, + 'langcode' => 'en', + 'moderation_state' => 'published', + ]); + + // Add a pending revision without changing the term parent. + $this->drupalGet('taxonomy/term/' . $term->id() . '/edit'); + $assert_session->pageTextContains($default_term_name); + $assert_session->pageTextContains($default_term_description); + + // Check the revision log message field does not appear on the term edit + // page. + $this->drupalGet('taxonomy/term/' . $term->id() . '/edit'); + $assert_session->fieldNotExists('revision_log_message[0][value]'); + + $pending_term_name = 'term - pending revision'; + $pending_term_description = 'The pending revision of a term.'; + $edit = [ + 'name[0][value]' => $pending_term_name, + 'description[0][value]' => $pending_term_description, + 'moderation_state[0][state]' => 'draft', + ]; + $this->drupalGet('taxonomy/term/' . $term->id() . '/edit'); + $this->submitForm($edit, 'Save'); + + $assert_session->pageTextContains($pending_term_name); + $assert_session->pageTextContains($pending_term_description); + $assert_session->pageTextNotContains($default_term_description); + + // Check that the default revision of the term contains the correct values. + $this->drupalGet('taxonomy/term/' . $term->id()); + $assert_session->pageTextContains($default_term_name); + $assert_session->pageTextContains($default_term_description); + } + +}