Skip to content
Snippets Groups Projects
Verified Commit 07473ef2 authored by Dave Long's avatar Dave Long
Browse files

Issue #3047110 by pavlosdan, Rajeshreeputra, Manuel Garcia, slydevil,...

Issue #3047110 by pavlosdan, Rajeshreeputra, Manuel Garcia, slydevil, hugronaphor, dhirendra.mishra, vsujeetkumar, amateescu, n4r3n, kreynen, acbramley, quietone: Enable Content Moderation integration for taxonomy terms
parent 41a731d7
No related branches found
No related tags found
32 merge requests!11131[10.4.x-only-DO-NOT-MERGE]: Issue ##2842525 Ajax attached to Views exposed filter form does not trigger callbacks,!9470[10.3.x-only-DO-NOT-MERGE]: #3331771 Fix file_get_contents(): Passing null to parameter,!8540Issue #3457061: Bootstrap Modal dialog Not closing after 10.3.0 Update,!8528Issue #3456871 by Tim Bozeman: Support NULL services,!8373Issue #3427374 by danflanagan8, Vighneshh: taxonomy_tid ViewsArgumentDefault...,!7526Expose roles in response,!7352Draft: Resolve #3203489 "Set filename as",!6880Add @property to the DateTimeItem,!3878Removed unused condition head title for views,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3133core/modules/system/css/components/hidden.module.css,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2794Issue #3100732: Allow specifying `meta` data on JSON:API objects,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493
Pipeline #109508 passed with warnings
Pipeline: drupal

#109518

    <?php
    namespace Drupal\content_moderation\Entity\Handler;
    use Drupal\Core\Form\FormStateInterface;
    /**
    * Customizations for taxonomy term entities.
    *
    * @internal
    */
    class TaxonomyTermModerationHandler extends ModerationHandler {
    /**
    * {@inheritdoc}
    */
    public function enforceRevisionsEntityFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
    $form['revision']['#default_value'] = TRUE;
    $form['revision']['#disabled'] = TRUE;
    $form['revision']['#description'] = $this->t('Revisions must be required when moderation is enabled.');
    }
    /**
    * {@inheritdoc}
    */
    public function enforceRevisionsBundleFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
    $form['revision']['#default_value'] = TRUE;
    $form['revision']['#disabled'] = TRUE;
    $form['revision']['#description'] = $this->t('Revisions must be required when moderation is enabled.');
    }
    }
    ...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
    use Drupal\content_moderation\Entity\Handler\BlockContentModerationHandler; use Drupal\content_moderation\Entity\Handler\BlockContentModerationHandler;
    use Drupal\content_moderation\Entity\Handler\ModerationHandler; use Drupal\content_moderation\Entity\Handler\ModerationHandler;
    use Drupal\content_moderation\Entity\Handler\NodeModerationHandler; use Drupal\content_moderation\Entity\Handler\NodeModerationHandler;
    use Drupal\content_moderation\Entity\Handler\TaxonomyTermModerationHandler;
    use Drupal\content_moderation\Entity\Routing\EntityModerationRouteProvider; use Drupal\content_moderation\Entity\Routing\EntityModerationRouteProvider;
    use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
    ...@@ -80,6 +81,7 @@ class EntityTypeInfo implements ContainerInjectionInterface { ...@@ -80,6 +81,7 @@ class EntityTypeInfo implements ContainerInjectionInterface {
    protected $moderationHandlers = [ protected $moderationHandlers = [
    'node' => NodeModerationHandler::class, 'node' => NodeModerationHandler::class,
    'block_content' => BlockContentModerationHandler::class, 'block_content' => BlockContentModerationHandler::class,
    'taxonomy_term' => TaxonomyTermModerationHandler::class,
    ]; ];
    /** /**
    ...@@ -144,7 +146,6 @@ public function entityTypeAlter(array &$entity_types) { ...@@ -144,7 +146,6 @@ public function entityTypeAlter(array &$entity_types) {
    $entity_type_to_exclude = [ $entity_type_to_exclude = [
    'path_alias', 'path_alias',
    'workspace', 'workspace',
    'taxonomy_term',
    ]; ];
    if ($entity_type->isRevisionable() && !$entity_type->isInternal() && !in_array($entity_type_id, $entity_type_to_exclude)) { 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); $entity_types[$entity_type_id] = $this->addModerationToEntityType($entity_type);
    ......
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\content_moderation\Functional;
    use Drupal\Core\Language\LanguageInterface;
    use Drupal\taxonomy\Entity\Term;
    use Drupal\taxonomy\Entity\Vocabulary;
    /**
    * Tests the taxonomy term moderation handler.
    *
    * @group content_moderation
    */
    class ModerationStateTaxonomyTermTest extends ModerationStateTestBase {
    /**
    * {@inheritdoc}
    */
    protected $defaultTheme = 'stark';
    /**
    * {@inheritdoc}
    */
    protected function setUp(): void {
    parent::setUp();
    // Create a "Tags" vocabulary.
    $bundle = Vocabulary::create([
    'vid' => 'tags',
    'name' => 'Tags',
    'new_revision' => FALSE,
    ])->save();
    }
    /**
    * Tests the taxonomy term moderation handler alters the forms as intended.
    *
    * @covers \Drupal\content_moderation\Entity\Handler\TaxonomyTermModerationHandler::enforceRevisionsEntityFormAlter
    * @covers \Drupal\content_moderation\Entity\Handler\TaxonomyTermModerationHandler::enforceRevisionsBundleFormAlter
    */
    public function testEnforceRevisionsEntityFormAlter(): void {
    $this->drupalLogin($this->adminUser);
    // Enable moderation for the tags vocabulary.
    $edit['bundles[tags]'] = TRUE;
    $this->drupalGet('/admin/config/workflow/workflows/manage/editorial/type/taxonomy_term');
    $this->submitForm($edit, 'Save');
    // Check that revision is checked by default when content moderation is
    // enabled for the vocabulary.
    $this->drupalGet('/admin/structure/taxonomy/manage/tags');
    $this->assertSession()->checkboxChecked('revision');
    $this->assertSession()->pageTextContains('Revisions must be required when moderation is enabled.');
    $this->assertSession()->fieldDisabled('revision');
    // Create a taxonomy term and save it as draft.
    $term = Term::create([
    'name' => 'Test tag',
    'vid' => 'tags',
    'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
    ]);
    $term->save();
    // Check that revision is checked by default when editing a term and
    // content moderation is enabled for the term's vocabulary.
    $this->drupalGet($term->toUrl('edit-form'));
    $this->assertSession()->checkboxChecked('revision');
    $this->assertSession()->pageTextContains('Revisions must be required when moderation is enabled.');
    $this->assertSession()->fieldDisabled('revision');
    }
    }
    ...@@ -49,6 +49,7 @@ abstract class ModerationStateTestBase extends BrowserTestBase { ...@@ -49,6 +49,7 @@ abstract class ModerationStateTestBase extends BrowserTestBase {
    'use editorial transition archive', 'use editorial transition archive',
    'use editorial transition archived_draft', 'use editorial transition archived_draft',
    'use editorial transition archived_published', 'use editorial transition archived_published',
    'administer taxonomy',
    ]; ];
    /** /**
    ...@@ -69,6 +70,7 @@ abstract class ModerationStateTestBase extends BrowserTestBase { ...@@ -69,6 +70,7 @@ abstract class ModerationStateTestBase extends BrowserTestBase {
    'block_content', 'block_content',
    'node', 'node',
    'entity_test', 'entity_test',
    'taxonomy',
    ]; ];
    /** /**
    ......
    ...@@ -94,6 +94,7 @@ protected function setUp(): void { ...@@ -94,6 +94,7 @@ protected function setUp(): void {
    $this->installEntitySchema('block_content'); $this->installEntitySchema('block_content');
    $this->installEntitySchema('media'); $this->installEntitySchema('media');
    $this->installEntitySchema('file'); $this->installEntitySchema('file');
    $this->installEntitySchema('taxonomy_term');
    $this->installEntitySchema('content_moderation_state'); $this->installEntitySchema('content_moderation_state');
    $this->installConfig('content_moderation'); $this->installConfig('content_moderation');
    $this->installSchema('file', 'file_usage'); $this->installSchema('file', 'file_usage');
    ...@@ -174,6 +175,9 @@ public static function basicModerationTestCases() { ...@@ -174,6 +175,9 @@ public static function basicModerationTestCases() {
    'Nodes' => [ 'Nodes' => [
    'node', 'node',
    ], ],
    'Taxonomy term' => [
    'taxonomy_term',
    ],
    'Block content' => [ 'Block content' => [
    'block_content', 'block_content',
    ], ],
    ...@@ -298,7 +302,7 @@ public function testContentModerationStateTranslationDataRemoval($entity_type_id ...@@ -298,7 +302,7 @@ public function testContentModerationStateTranslationDataRemoval($entity_type_id
    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = $this->createEntity($entity_type_id, 'published'); $entity = $this->createEntity($entity_type_id, 'published');
    $langcode = 'fr'; $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. // Make sure we add values for all of the required fields.
    if ($entity_type_id == 'block_content') { if ($entity_type_id == 'block_content') {
    $translation->info = $this->randomString(); $translation->info = $this->randomString();
    ...@@ -813,14 +817,4 @@ protected function assertDefaultRevision(EntityInterface $entity, int $revision_ ...@@ -813,14 +817,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));
    }
    } }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\taxonomy\Functional;
    use Drupal\Core\Url;
    use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
    use Drupal\workflows\Entity\Workflow;
    /**
    * Tests taxonomy terms with Content Moderation.
    *
    * @group content_moderation
    * @group taxonomy
    */
    class TaxonomyTermContentModerationTest extends TaxonomyTestBase {
    use ContentModerationTestTrait;
    /**
    * {@inheritdoc}
    */
    protected $defaultTheme = 'stark';
    /**
    * The vocabulary used for creating terms.
    *
    * @var \Drupal\taxonomy\VocabularyInterface
    */
    protected $vocabulary;
    /**
    * {@inheritdoc}
    */
    protected static $modules = ['content_moderation'];
    /**
    * {@inheritdoc}
    */
    protected function setUp(): void {
    parent::setUp();
    $this->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(): void {
    $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($child->toUrl('edit-form'));
    $this->submitForm(['moderation_state[0][state]' => 'draft'], 'Save');
    $assert_session->pageTextNotContains($validation_message);
    // Add a pending revision and change the parent.
    $this->drupalGet($child->toUrl('edit-form'));
    $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($child->toUrl('edit-form'));
    $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($child->toUrl('edit-form'));
    $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($child->toUrl('edit-form'));
    $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($child->toUrl('edit-form'));
    $this->submitForm(['moderation_state[0][state]' => 'published'], 'Save');
    // Change the parents.
    $this->drupalGet($child->toUrl('edit-form'));
    $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($child->toUrl('edit-form'));
    $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(Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $this->vocabulary->id()]));
    $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($term->toUrl('edit-form'));
    $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(): void {
    $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($term->toUrl('edit-form'));
    $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($term->toUrl('edit-form'));
    $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($term->toUrl('edit-form'));
    $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);
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment