diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php index 655fa05..bf0f2cb 100644 --- a/core/modules/taxonomy/src/Entity/Term.php +++ b/core/modules/taxonomy/src/Entity/Term.php @@ -164,6 +164,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setDescription(t('The parents of this term.')) ->setSetting('target_type', 'taxonomy_term') ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED) + ->addConstraint('TaxonomyTermParent') ->setCustomStorage(TRUE); $fields['changed'] = BaseFieldDefinition::create('changed') diff --git a/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermParentConstraint.php b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermParentConstraint.php new file mode 100644 index 0000000..d607e70 --- /dev/null +++ b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermParentConstraint.php @@ -0,0 +1,19 @@ +published version of this term.'; + +} diff --git a/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermParentConstraintValidator.php b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermParentConstraintValidator.php new file mode 100644 index 0000000..86ddf9c --- /dev/null +++ b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermParentConstraintValidator.php @@ -0,0 +1,57 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) { + $entity = !empty($value->getParent()) ? $value->getEntity() : NULL; + + if ($entity && !$entity->isNew() && !$entity->isDefaultRevision()) { + $new_parents = array_column($value->getValue(), 'target_id'); + $original_parents = array_keys($this->entityTypeManager->getStorage('taxonomy_term')->loadParents($entity->id())); + if ($new_parents != $original_parents) { + $this->context->addViolation($constraint->message); + } + } + } + +} diff --git a/core/modules/taxonomy/src/TermForm.php b/core/modules/taxonomy/src/TermForm.php index 1eae95a..4667dd7 100644 --- a/core/modules/taxonomy/src/TermForm.php +++ b/core/modules/taxonomy/src/TermForm.php @@ -3,6 +3,7 @@ namespace Drupal\taxonomy; use Drupal\Core\Entity\ContentEntityForm; +use Drupal\Core\Entity\EntityConstraintViolationListInterface; use Drupal\Core\Form\FormStateInterface; /** @@ -118,6 +119,25 @@ public function buildEntity(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ + protected function getEditedFieldNames(FormStateInterface $form_state) { + return array_merge(['parent'], parent::getEditedFieldNames($form_state)); + } + + /** + * {@inheritdoc} + */ + protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) { + // Manually flag violations of fields not handled by the form display. + foreach ($violations->getByField('parent') as $violation) { + $form_state->setErrorByName('parent', $violation->getMessage()); + } + + parent::flagViolations($violations, $form, $form_state); + } + + /** + * {@inheritdoc} + */ public function save(array $form, FormStateInterface $form_state) { $term = $this->entity; 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 0000000..2d09f8e --- /dev/null +++ b/core/modules/taxonomy/tests/src/Functional/TaxonomyTermContentModerationTest.php @@ -0,0 +1,79 @@ +drupalLogin($this->drupalCreateUser(['administer taxonomy', 'use editorial transition create_new_draft', 'use editorial transition publish'])); + + // Create a vocabulary. + $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() { + // Create a simple hierarchy in the vocabulary, a root term and three parent + // terms. + $root = $this->createTerm($this->vocabulary, ['langcode' => 'en']); + $parent_1 = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'parent' => $root->id()]); + $parent_2 = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'parent' => $root->id()]); + $parent_3 = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'parent' => $root->id()]); + + // Create a child term and assign one of the parents above. + $child = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'parent' => $parent_1->id()]); + + $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $validation_message = 'You can only change the parent for the published version of this term.'; + + // Add a pending revision without changing the term parent. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->drupalPostForm(NULL, [], 'Save and Create New Draft'); + + $this->assertSession()->pageTextNotContains($validation_message); + + // Try to add a pending revision and change the parent. + $this->drupalGet('taxonomy/term/' . $child->id() . '/edit'); + $this->drupalPostForm(NULL, [ + 'parent[]' => [$parent_2->id()], + ], 'Save and Create New Draft'); + + $this->assertSession()->pageTextContains($validation_message); + } + +}