diff --git a/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php b/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php index 67743d41efc532c7b9fa6dada94d9ab9d961ca82..31f1c18c682bbb414bf78aee70f90e7138defa9d 100644 --- a/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php +++ b/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php @@ -3,13 +3,16 @@ namespace Drupal\content_moderation\Plugin\WorkflowType; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\content_moderation\ContentModerationState; use Drupal\workflows\Plugin\WorkflowTypeBase; use Drupal\workflows\StateInterface; use Drupal\workflows\WorkflowInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Attaches workflows to content entity types and their bundles. @@ -23,10 +26,37 @@ * }, * ) */ -class ContentModeration extends WorkflowTypeBase { +class ContentModeration extends WorkflowTypeBase implements ContainerFactoryPluginInterface { use StringTranslationTrait; + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Creates an instance of the ContentModeration WorkflowType plugin. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager') + ); + } + /** * {@inheritdoc} */ @@ -193,11 +223,59 @@ public function defaultConfiguration() { } /** - * @inheritDoc + * {@inheritdoc} */ public function calculateDependencies() { - // @todo : Implement calculateDependencies() method. - return []; + $dependencies = parent::calculateDependencies(); + foreach ($this->getEntityTypes() as $entity_type_id) { + $entity_definition = $this->entityTypeManager->getDefinition($entity_type_id); + foreach ($this->getBundlesForEntityType($entity_type_id) as $bundle) { + $dependency = $entity_definition->getBundleConfigDependency($bundle); + $dependencies[$dependency['type']][] = $dependency['name']; + } + } + return $dependencies; + } + + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + $changed = parent::onDependencyRemoval($dependencies); + + // When bundle config entities are removed, ensure they are cleaned up from + // the workflow. + foreach ($dependencies['config'] as $removed_config) { + if ($entity_type_id = $removed_config->getEntityType()->getBundleOf()) { + $bundle_id = $removed_config->id(); + $this->removeEntityTypeAndBundle($entity_type_id, $bundle_id); + $changed = TRUE; + } + } + + // When modules that provide entity types are removed, ensure they are also + // removed from the workflow. + if (!empty($dependencies['module'])) { + // Gather all entity definitions provided by the dependent modules which + // are being removed. + $module_entity_definitions = []; + foreach ($this->entityTypeManager->getDefinitions() as $entity_definition) { + if (in_array($entity_definition->getProvider(), $dependencies['module'])) { + $module_entity_definitions[] = $entity_definition; + } + } + + // For all entity types provided by the uninstalled modules, remove any + // configuration for those types. + foreach ($module_entity_definitions as $module_entity_definition) { + foreach ($this->getBundlesForEntityType($module_entity_definition->id()) as $bundle) { + $this->removeEntityTypeAndBundle($module_entity_definition->id(), $bundle); + $changed = TRUE; + } + } + } + + return $changed; } /** diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php index a1c7542ef13d1b106437e8d664ae9aec6a12e199..51511556555558fc96dd971f26960feb76b5a681 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php @@ -387,6 +387,48 @@ protected function setEntityTestWithBundleKeys($keys, $remove_keys = []) { \Drupal::entityDefinitionUpdateManager()->applyUpdates(); } + /** + * Tests the dependencies of the workflow when using content moderation. + */ + public function testWorkflowDependencies() { + $node_type = NodeType::create([ + 'type' => 'example', + ]); + $node_type->save(); + + $workflow = Workflow::load('editorial'); + // Test both a config and non-config based bundle and entity type. + $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example'); + $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev'); + $workflow->save(); + + $this->assertEquals([ + 'module' => [ + 'content_moderation', + 'entity_test', + ], + 'config' => [ + 'node.type.example', + ], + ], $workflow->getDependencies()); + + $entity_types = $workflow->getTypePlugin()->getEntityTypes(); + $this->assertTrue(in_array('node', $entity_types)); + $this->assertTrue(in_array('entity_test_rev', $entity_types)); + + // Delete the node type and ensure it is removed from the workflow. + $node_type->delete(); + $workflow = Workflow::load('editorial'); + $entity_types = $workflow->getTypePlugin()->getEntityTypes(); + $this->assertFalse(in_array('node', $entity_types)); + + // Uninstall entity test and ensure it's removed from the workflow. + $this->container->get('config.manager')->uninstall('module', 'entity_test'); + $workflow = Workflow::load('editorial'); + $entity_types = $workflow->getTypePlugin()->getEntityTypes(); + $this->assertFalse(in_array('entity_test_rev', $entity_types)); + } + /** * Reloads the entity after clearing the static cache. * diff --git a/core/modules/workflows/src/Entity/Workflow.php b/core/modules/workflows/src/Entity/Workflow.php index 330c3922e59f26066763ab616eaa43560fb4a208..c6b1ce6a43581085397e0a541f34900f5bf90ca5 100644 --- a/core/modules/workflows/src/Entity/Workflow.php +++ b/core/modules/workflows/src/Entity/Workflow.php @@ -520,4 +520,14 @@ public function status() { return !empty($this->status) && !empty($this->states); } + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + $changed = $this->getTypePlugin()->onDependencyRemoval($dependencies); + // Ensure the parent method is called in order to process dependencies that + // affect third party settings. + return parent::onDependencyRemoval($dependencies) || $changed; + } + } diff --git a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php index 15ced3e25a67152e1fe1f48c997f2da01a6bb317..59c6b961371b68549ebc7f343fe4c1d375e2c73e 100644 --- a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php +++ b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php @@ -138,4 +138,11 @@ public function calculateDependencies() { return []; } + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + return FALSE; + } + } diff --git a/core/modules/workflows/src/WorkflowTypeInterface.php b/core/modules/workflows/src/WorkflowTypeInterface.php index 511cb9d211d413ced4e5b8cef237f2934ccfc668..0fe668547ce89595ed5dac3b38339abc27f9e84e 100644 --- a/core/modules/workflows/src/WorkflowTypeInterface.php +++ b/core/modules/workflows/src/WorkflowTypeInterface.php @@ -25,7 +25,7 @@ interface WorkflowTypeInterface extends PluginInspectionInterface, DerivativeIns * @param \Drupal\workflows\WorkflowInterface $workflow * The workflow to initialize. * - * @return \Drupal\workflows\WorkflowInterface $workflow + * @return \Drupal\workflows\WorkflowInterface * The initialized workflow. * * @see \Drupal\workflows\Form\WorkflowAddForm::save() @@ -62,7 +62,7 @@ public function checkWorkflowAccess(WorkflowInterface $entity, $operation, Accou * @param \Drupal\workflows\StateInterface $state * The state object to decorate. * - * @return \Drupal\workflows\StateInterface $state + * @return \Drupal\workflows\StateInterface * The decorated state object. */ public function decorateState(StateInterface $state); @@ -80,7 +80,7 @@ public function deleteState($state_id); * @param \Drupal\workflows\TransitionInterface $transition * The transition object to decorate. * - * @return \Drupal\workflows\TransitionInterface $transition + * @return \Drupal\workflows\TransitionInterface * The decorated transition object. */ public function decorateTransition(TransitionInterface $transition); @@ -144,4 +144,19 @@ public function buildTransitionConfigurationForm(FormStateInterface $form_state, */ public function getRequiredStates(); + /** + * Informs the plugin that a dependency of the workflow will be deleted. + * + * @param array $dependencies + * An array of dependencies that will be deleted keyed by dependency type. + * + * @return bool + * TRUE if the workflow settings have been changed, FALSE if not. + * + * @see \Drupal\Core\Config\ConfigEntityInterface::onDependencyRemoval() + * + * @todo https://www.drupal.org/node/2579743 make part of a generic interface. + */ + public function onDependencyRemoval(array $dependencies); + } diff --git a/core/modules/workflows/tests/modules/workflow_third_party_settings_test/config/schema/workflow_third_party_settings_test.schema.yml b/core/modules/workflows/tests/modules/workflow_third_party_settings_test/config/schema/workflow_third_party_settings_test.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..d1a2b4eea78921d51637b3b351d3200eb2ac6cc4 --- /dev/null +++ b/core/modules/workflows/tests/modules/workflow_third_party_settings_test/config/schema/workflow_third_party_settings_test.schema.yml @@ -0,0 +1,2 @@ +workflows.workflow.*.third_party.workflow_third_party_settings_test: + type: ignore diff --git a/core/modules/workflows/tests/modules/workflow_third_party_settings_test/workflow_third_party_settings_test.info.yml b/core/modules/workflows/tests/modules/workflow_third_party_settings_test/workflow_third_party_settings_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..5b152f7be7cbf324519a5d3765a6eddd827c8610 --- /dev/null +++ b/core/modules/workflows/tests/modules/workflow_third_party_settings_test/workflow_third_party_settings_test.info.yml @@ -0,0 +1,8 @@ +name: 'Workflow Third Party Settings Test' +type: module +description: 'Allows third party settings on workflows to be tested.' +package: Testing +version: VERSION +core: 8.x +dependencies: + - workflows diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php index 8440c1c18cde285f20adac351238c119cf7a1eb4..a254a4f31fe8776694ffd52b4bf07bd3db906782 100644 --- a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php +++ b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php @@ -79,4 +79,13 @@ public function buildTransitionConfigurationForm(FormStateInterface $form_state, return $form; } + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + // Always return TRUE to allow the logic in + // \Drupal\workflows\Entity\Workflow::onDependencyRemoval() to be tested. + return TRUE; + } + } diff --git a/core/modules/workflows/tests/src/Kernel/WorkflowDependenciesTest.php b/core/modules/workflows/tests/src/Kernel/WorkflowDependenciesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e9ce62c0b77ace8f11aa89c207f15e9e4508ec5b --- /dev/null +++ b/core/modules/workflows/tests/src/Kernel/WorkflowDependenciesTest.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\Tests\workflows\Kernel; + +use Drupal\KernelTests\KernelTestBase; +use Drupal\workflows\Entity\Workflow; + +/** + * Tests configuration dependencies in workflows. + * + * @coversDefaultClass \Drupal\workflows\Entity\Workflow + * + * @group workflows + */ +class WorkflowDependenciesTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['system', 'workflows', 'workflow_type_test', 'workflow_third_party_settings_test']; + + /** + * Tests \Drupal\workflows\Entity\Workflow::onDependencyRemoval(). + */ + public function testOnDependencyRemoval() { + // Create a workflow that has a dependency on a third party setting. + $workflow = Workflow::create(['id' => 'test3', 'type' => 'workflow_type_complex_test']); + $workflow->setThirdPartySetting('workflow_third_party_settings_test', 'key', 'value'); + $workflow->save(); + $this->assertSame(['workflow_third_party_settings_test', 'workflow_type_test'], $workflow->getDependencies()['module']); + + // Uninstall workflow_third_party_settings_test to ensure + // \Drupal\workflows\Entity\Workflow::onDependencyRemoval() works as + // expected. + \Drupal::service('module_installer')->uninstall(['node', 'workflow_third_party_settings_test']); + $workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($workflow->id()); + $this->assertSame(['workflow_type_test'], $workflow->getDependencies()['module']); + } + +}