diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module index 77d822be28..31cc6c9438 100644 --- a/core/modules/content_moderation/content_moderation.module +++ b/core/modules/content_moderation/content_moderation.module @@ -63,15 +63,6 @@ function content_moderation_entity_type_alter(array &$entity_types) { } /** - * Implements hook_entity_operation(). - */ -function content_moderation_entity_operation(EntityInterface $entity) { - return \Drupal::service('class_resolver') - ->getInstanceFromDefinition(EntityTypeInfo::class) - ->entityOperation($entity); -} - -/** * Implements hook_entity_presave(). */ function content_moderation_entity_presave(EntityInterface $entity) { diff --git a/core/modules/content_moderation/content_moderation.permissions.yml b/core/modules/content_moderation/content_moderation.permissions.yml index af28bbf1ad..0bc555d7de 100644 --- a/core/modules/content_moderation/content_moderation.permissions.yml +++ b/core/modules/content_moderation/content_moderation.permissions.yml @@ -6,11 +6,6 @@ view any unpublished content: title: 'View content moderation' description: 'View content moderation.' -'administer content moderation': - title: 'Administer content moderation' - description: 'Administer workflows on content entities.' - 'restrict access': TRUE - view latest version: title: 'View the latest version' description: 'View the latest version of an entity. (Also requires "View any unpublished content" permission)' diff --git a/core/modules/content_moderation/content_moderation.routing.yml b/core/modules/content_moderation/content_moderation.routing.yml new file mode 100644 index 0000000000..a7167ca96b --- /dev/null +++ b/core/modules/content_moderation/content_moderation.routing.yml @@ -0,0 +1,7 @@ +content_moderation.workflow_type_edit_form: + path: '/admin/config/workflow/workflows/manage/{workflow}/type/{entity_type_id}' + defaults: + _form: '\Drupal\content_moderation\Form\WorkflowTypeEditForm' + _title_callback: '\Drupal\content_moderation\Form\WorkflowTypeEditForm::getTitle' + requirements: + _permission: 'administer workflows' diff --git a/core/modules/content_moderation/src/EntityTypeInfo.php b/core/modules/content_moderation/src/EntityTypeInfo.php index f11e471685..8f6ac4d79e 100644 --- a/core/modules/content_moderation/src/EntityTypeInfo.php +++ b/core/modules/content_moderation/src/EntityTypeInfo.php @@ -3,12 +3,10 @@ namespace Drupal\content_moderation; use Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList; -use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; use Drupal\Core\Entity\BundleEntityFormBase; use Drupal\Core\Entity\ContentEntityFormInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\ContentEntityTypeInterface; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -17,13 +15,10 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; -use Drupal\Core\Url; use Drupal\content_moderation\Entity\Handler\BlockContentModerationHandler; use Drupal\content_moderation\Entity\Handler\ModerationHandler; use Drupal\content_moderation\Entity\Handler\NodeModerationHandler; -use Drupal\content_moderation\Form\BundleModerationConfigurationForm; use Drupal\content_moderation\Routing\EntityModerationRouteProvider; -use Drupal\content_moderation\Routing\EntityTypeModerationRouteProvider; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -127,11 +122,6 @@ public function entityTypeAlter(array &$entity_types) { // The ContentModerationState entity type should never be moderated. if ($entity_type->isRevisionable() && $entity_type_id != 'content_moderation_state') { $entity_types[$entity_type_id] = $this->addModerationToEntityType($entity_type); - // Add additional moderation support to entity types whose bundles are - // managed by a config entity type. - if ($entity_type->getBundleEntityType()) { - $entity_types[$entity_type->getBundleEntityType()] = $this->addModerationToBundleEntityType($entity_types[$entity_type->getBundleEntityType()]); - } } } } @@ -170,67 +160,6 @@ protected function addModerationToEntityType(ContentEntityTypeInterface $type) { } /** - * Configures moderation configuration support on a entity type definition. - * - * That "configuration support" includes a configuration form, a hypermedia - * link, and a route provider to tie it all together. There's also a - * moderation handler for per-entity-type variation. - * - * @param \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $type - * The config entity definition to modify. - * - * @return \Drupal\Core\Config\Entity\ConfigEntityTypeInterface - * The modified config entity definition. - */ - protected function addModerationToBundleEntityType(ConfigEntityTypeInterface $type) { - if ($type->hasLinkTemplate('edit-form') && !$type->hasLinkTemplate('moderation-form')) { - $type->setLinkTemplate('moderation-form', $type->getLinkTemplate('edit-form') . '/moderation'); - } - - if (!$type->getFormClass('moderation')) { - $type->setFormClass('moderation', BundleModerationConfigurationForm::class); - } - - // @todo Core forgot to add a direct way to manipulate route_provider, so - // we have to do it the sloppy way for now. - $providers = $type->getRouteProviderClasses() ?: []; - if (empty($providers['moderation'])) { - $providers['moderation'] = EntityTypeModerationRouteProvider::class; - $type->setHandlerClass('route_provider', $providers); - } - - return $type; - } - - /** - * Adds an operation on bundles that should have a Moderation form. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity on which to define an operation. - * - * @return array - * An array of operation definitions. - * - * @see hook_entity_operation() - */ - public function entityOperation(EntityInterface $entity) { - $operations = []; - $type = $entity->getEntityType(); - $bundle_of = $type->getBundleOf(); - if ($this->currentUser->hasPermission('administer content moderation') && $bundle_of && - $this->moderationInfo->canModerateEntitiesOfEntityType($this->entityTypeManager->getDefinition($bundle_of)) - ) { - $operations['manage-moderation'] = [ - 'title' => t('Manage moderation'), - 'weight' => 27, - 'url' => Url::fromRoute("entity.{$type->id()}.moderation", [$entity->getEntityTypeId() => $entity->id()]), - ]; - } - - return $operations; - } - - /** * Gets the "extra fields" for a bundle. * * This is a hook bridge. diff --git a/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php b/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php deleted file mode 100644 index 917ec5deda..0000000000 --- a/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php +++ /dev/null @@ -1,155 +0,0 @@ -entityTypeManager = $entity_type_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static($container->get('entity_type.manager')); - } - - /** - * {@inheritdoc} - * - * Blank out the base form ID so that form alters that use the base form ID to - * target both add and edit forms don't pick up this form. - */ - public function getBaseFormId() { - return NULL; - } - - /** - * {@inheritdoc} - */ - public function form(array $form, FormStateInterface $form_state) { - /* @var \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle */ - $bundle = $this->getEntity(); - $bundle_of_entity_type = $this->entityTypeManager->getDefinition($bundle->getEntityType()->getBundleOf()); - /* @var \Drupal\workflows\WorkflowInterface[] $workflows */ - $workflows = $this->entityTypeManager->getStorage('workflow')->loadMultiple(); - - $options = array_map(function (WorkflowInterface $workflow) { - return $workflow->label(); - }, array_filter($workflows, function (WorkflowInterface $workflow) { - return $workflow->status() && $workflow->getTypePlugin() instanceof ContentModeration; - })); - - $selected_workflow = array_reduce($workflows, function ($carry, WorkflowInterface $workflow) use ($bundle_of_entity_type, $bundle) { - $plugin = $workflow->getTypePlugin(); - if ($plugin instanceof ContentModeration && $plugin->appliesToEntityTypeAndBundle($bundle_of_entity_type->id(), $bundle->id())) { - return $workflow->id(); - } - return $carry; - }); - $form['workflow'] = [ - '#type' => 'select', - '#title' => $this->t('Select the workflow to apply'), - '#default_value' => $selected_workflow, - '#options' => $options, - '#required' => FALSE, - '#empty_value' => '', - ]; - - $form['original_workflow'] = [ - '#type' => 'value', - '#value' => $selected_workflow, - ]; - - $form['bundle'] = [ - '#type' => 'value', - '#value' => $bundle->id(), - ]; - - $form['entity_type'] = [ - '#type' => 'value', - '#value' => $bundle_of_entity_type->id(), - ]; - - // Add a special message when moderation is being disabled. - if ($selected_workflow) { - $form['enable_workflow_note'] = [ - '#type' => 'item', - '#description' => $this->t('After disabling moderation, any existing forward drafts will be accessible via the "Revisions" tab.'), - '#access' => !empty($selected_workflow) - ]; - } - - return parent::form($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - // If moderation is enabled, revisions MUST be enabled as well. Otherwise we - // can't have forward revisions. - drupal_set_message($this->t('Your settings have been saved.')); - } - - /** - * {@inheritdoc} - */ - public function save(array $form, FormStateInterface $form_state) { - $entity_type_id = $form_state->getValue('entity_type'); - $bundle_id = $form_state->getValue('bundle'); - $new_workflow_id = $form_state->getValue('workflow'); - $original_workflow_id = $form_state->getValue('original_workflow'); - if ($new_workflow_id === $original_workflow_id) { - // Nothing to do. - return; - } - if ($original_workflow_id) { - /* @var \Drupal\workflows\WorkflowInterface $workflow */ - $workflow = $this->entityTypeManager->getStorage('workflow')->load($original_workflow_id); - $workflow->getTypePlugin()->removeEntityTypeAndBundle($entity_type_id, $bundle_id); - $workflow->save(); - } - if ($new_workflow_id) { - /* @var \Drupal\workflows\WorkflowInterface $workflow */ - $workflow = $this->entityTypeManager->getStorage('workflow')->load($new_workflow_id); - $workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id); - $workflow->save(); - } - } - - /** - * {@inheritdoc} - */ - protected function actions(array $form, FormStateInterface $form_state) { - $actions['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Save'), - '#submit' => ['::submitForm', '::save'], - ]; - - return $actions; - } - -} diff --git a/core/modules/content_moderation/src/Form/WorkflowTypeEditForm.php b/core/modules/content_moderation/src/Form/WorkflowTypeEditForm.php new file mode 100644 index 0000000000..2c6a8206dd --- /dev/null +++ b/core/modules/content_moderation/src/Form/WorkflowTypeEditForm.php @@ -0,0 +1,201 @@ +get('entity_type.manager'), + $container->get('entity_type.bundle.info'), + $container->get('content_moderation.moderation_information') + ); + } + + /** + * {@inheritdoc} + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info , ModerationInformationInterface $moderation_information) { + $this->entityTypeManager = $entity_type_manager; + $this->bundleInfo = $bundle_info; + $this->moderationInformation = $moderation_information; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'workflow_type_edit_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, WorkflowInterface $workflow = NULL, $entity_type_id = NULL) { + $this->workflow = $workflow; + try { + $this->entityType = $this->entityTypeManager->getDefinition($entity_type_id); + } + catch (PluginNotFoundException $e) { + throw new NotFoundHttpException(); + } + + $options = $defaults = []; + foreach ($this->bundleInfo->getBundleInfo($this->entityType->id()) as $bundle_id => $bundle) { + // Check if moderation is enabled for this bundle on any workflow. + $moderation_enabled = $this->moderationInformation->shouldModerateEntitiesOfBundle($this->entityType, $bundle_id); + // Check if moderation is enabled for this bundle on this workflow. + $workflow_moderation_enabled = $this->workflow->getTypePlugin()->appliesToEntityTypeAndBundle($this->entityType->id(), $bundle_id); + // Only show bundles that are not enabled anywhere, or enabled on this + // workflow. + if (!$moderation_enabled || $workflow_moderation_enabled) { + // Add the bundle to the options if it's not enabled on a workflow, + // unless the workflow it's enabled on is this one. + $options[$bundle_id] = [ + 'type' => $bundle['label'], + '#attributes' => ['class' => [$workflow_moderation_enabled ? 'selected' : '']], + ]; + // Add the bundle to the list of default values if it's enabled on this + // workflow. + $defaults[$bundle_id] = $workflow_moderation_enabled; + } + } + + if (!empty($options)) { + $bundles_header = $this->t('All @entity_type types', ['@entity_type' => $this->entityType->getLabel()]); + if ($bundle_entity_type_id = $this->entityType->getBundleEntityType()) { + $bundles_header = $this->t('All @entity_type_plural_label', ['@entity_type_plural_label' => $this->entityTypeManager->getDefinition($bundle_entity_type_id)->getPluralLabel()]); + } + $form['bundles'] = [ + '#type' => 'tableselect', + '#header' => [ + 'type' => $bundles_header, + ], + '#options' => $options, + '#default_value' => $defaults, + ]; + } + + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#button_type' => 'primary', + '#value' => $this->t('Save'), + '#ajax' => [ + 'callback' => [$this, 'ajaxcallback'], + ], + ]; + $form['actions']['cancel'] = [ + '#type' => 'button', + '#value' => $this->t('Cancel'), + '#ajax' => [ + 'callback' => [$this, 'ajaxcallback'], + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + foreach ($form_state->getValue('bundles') as $bundle_id => $checked) { + if ($checked) { + $this->workflow->getTypePlugin()->addEntityTypeAndBundle($this->entityType->id(), $bundle_id); + } + else { + $this->workflow->getTypePlugin()->removeEntityTypeAndBundle($this->entityType->id(), $bundle_id); + } + } + $this->workflow->save(); + } + + /** + * Ajax callback to close the modal and update the selected text. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An ajax response object. + */ + public function ajaxCallback() { + $selected_bundles = []; + foreach ($this->bundleInfo->getBundleInfo($this->entityType->id()) as $bundle_id => $bundle) { + if ($this->workflow->getTypePlugin()->appliesToEntityTypeAndBundle($this->entityType->id(), $bundle_id)) { + $selected_bundles[$bundle_id] = $bundle['label']; + } + } + $response = new AjaxResponse(); + $response->addCommand(new CloseDialogCommand()); + $response->addCommand(new HtmlCommand('#selected-' . $this->entityType->id(), !empty($selected_bundles) ? implode(', ', $selected_bundles) : $this->t('none'))); + return $response; + } + + /** + * Route title callback. + */ + public function getTitle(WorkflowInterface $workflow = NULL, $entity_type_id) { + $this->entityType = $this->entityTypeManager->getDefinition($entity_type_id); + + $title = $this->t('Select the @entity_type types for the @workflow', ['@entity_type' => $this->entityType->getLabel(), '@workflow' => $workflow->label()]); + if ($bundle_entity_type_id = $this->entityType->getBundleEntityType()) { + $title = $this->t('Select the @entity_type_plural_label for the @workflow', ['@entity_type_plural_label' => $this->entityTypeManager->getDefinition($bundle_entity_type_id)->getPluralLabel(), '@workflow' => $workflow->label()]); + } + + return $title; + } + +} diff --git a/core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php b/core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php index 7ff1fa5da5..9f09a9a890 100644 --- a/core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php +++ b/core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php @@ -76,19 +76,6 @@ public static function create(ContainerInterface $container, $base_plugin_id) { public function getDerivativeDefinitions($base_plugin_definition) { $this->derivatives = []; - foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { - if ($this->moderationInfo->canModerateEntitiesOfEntityType($entity_type)) { - $bundle_id = $entity_type->getBundleEntityType(); - $this->derivatives["$bundle_id.moderation_tab"] = [ - 'route_name' => "entity.$bundle_id.moderation", - 'title' => $this->t('Manage moderation'), - // @todo - are we sure they all have an edit_form? - 'base_route' => "entity.$bundle_id.edit_form", - 'weight' => 30, - ] + $base_plugin_definition; - } - } - $latest_version_entities = array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $type) { return $this->moderationInfo->canModerateEntitiesOfEntityType($type) && $type->hasLinkTemplate('latest-version'); }); diff --git a/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php b/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php index 31f1c18c68..c5cf48a649 100644 --- a/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php +++ b/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php @@ -2,14 +2,18 @@ namespace Drupal\content_moderation\Plugin\WorkflowType; +use Drupal\Component\Serialization\Json; +use Drupal\content_moderation\ModerationInformationInterface; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; 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\Core\Url; +use Drupal\workflows\Plugin\WorkflowTypeFormBase; use Drupal\workflows\StateInterface; use Drupal\workflows\WorkflowInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -26,7 +30,7 @@ * }, * ) */ -class ContentModeration extends WorkflowTypeBase implements ContainerFactoryPluginInterface { +class ContentModeration extends WorkflowTypeFormBase implements ContainerFactoryPluginInterface { use StringTranslationTrait; @@ -38,11 +42,38 @@ class ContentModeration extends WorkflowTypeBase implements ContainerFactoryPlug protected $entityTypeManager; /** - * Creates an instance of the ContentModeration WorkflowType plugin. + * The entity type bundle info service. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ + protected $entityTypeBundleInfo; + + /** + * The moderation information service. + * + * @var \Drupal\content_moderation\ModerationInformationInterface */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) { + protected $moderationInfo; + + /** + * Constructs a ContentModeration object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info + * Moderation information service. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, ModerationInformationInterface $moderation_info) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityTypeManager = $entity_type_manager; + $this->entityTypeBundleInfo = $entity_type_bundle_info; + $this->moderationInfo = $moderation_info; } /** @@ -53,7 +84,9 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('entity_type.bundle.info'), + $container->get('content_moderation.moderation_information') ); } @@ -172,6 +205,9 @@ public function appliesToEntityTypeAndBundle($entity_type_id, $bundle_id) { * The bundle ID to remove. */ public function removeEntityTypeAndBundle($entity_type_id, $bundle_id) { + if (!isset($this->configuration['entity_types'][$entity_type_id])) { + return; + } $key = array_search($bundle_id, $this->configuration['entity_types'][$entity_type_id], TRUE); if ($key !== FALSE) { unset($this->configuration['entity_types'][$entity_type_id][$key]); @@ -203,7 +239,7 @@ public function addEntityTypeAndBundle($entity_type_id, $bundle_id) { } /** - * {@inheritDoc} + * {@inheritdoc} */ public function defaultConfiguration() { // This plugin does not store anything per transition. @@ -289,4 +325,68 @@ public function getConfiguration() { return $configuration; } + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state, WorkflowInterface $workflow = NULL) { + $header = [ + 'type' => $this->t('Items'), + 'operations' => $this->t('Operations') + ]; + $form['entity_types_container'] = [ + '#type' => 'details', + '#title' => $this->t('This workflow applies to:'), + '#open' => TRUE, + ]; + $form['entity_types_container']['entity_types'] = [ + '#type' => 'table', + '#header' => $header, + '#empty' => $this->t('There are no entity types.'), + ]; + + $entity_types = $this->entityTypeManager->getDefinitions(); + foreach ($entity_types as $entity_type) { + if (!$this->moderationInfo->canModerateEntitiesOfEntityType($entity_type)) { + continue; + } + + $selected_bundles = []; + foreach ($this->entityTypeBundleInfo->getBundleInfo($entity_type->id()) as $bundle_id => $bundle) { + if ($this->appliesToEntityTypeAndBundle($entity_type->id(), $bundle_id)) { + $selected_bundles[$bundle_id] = $bundle['label']; + } + } + + $form['entity_types_container']['entity_types'][$entity_type->id()] = [ + 'type' => [ + 'label' => ['#markup' => '' . $this->t('@bundle types', ['@bundle' => $entity_type->getLabel()]) . ''], + 'selected' => [ + '#prefix' => '
', + '#markup' => !empty($selected_bundles) ? implode(', ', $selected_bundles) : $this->t('none'), + '#suffix' => '', + ], + ], + 'operations' => [ + '#type' => 'operations', + '#links' => [ + 'Add' => [ + 'title' => $this->t('Select'), + 'url' => Url::fromRoute('content_moderation.workflow_type_edit_form', ['workflow' => $workflow->id(), 'entity_type_id' => $entity_type->id()]), + 'attributes' => [ + 'aria-label' => $this->t('Select'), + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + ]), + ], + ], + ], + ], + ]; + } + + return $form; + } + } diff --git a/core/modules/content_moderation/src/Routing/EntityTypeModerationRouteProvider.php b/core/modules/content_moderation/src/Routing/EntityTypeModerationRouteProvider.php deleted file mode 100644 index d1dcd2b11f..0000000000 --- a/core/modules/content_moderation/src/Routing/EntityTypeModerationRouteProvider.php +++ /dev/null @@ -1,59 +0,0 @@ -getModerationFormRoute($entity_type)) { - $entity_type_id = $entity_type->id(); - $collection->add("entity.{$entity_type_id}.moderation", $moderation_route); - } - - return $collection; - } - - /** - * Gets the moderation-form route. - * - * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type - * The entity type. - * - * @return \Symfony\Component\Routing\Route|null - * The generated route, if available. - */ - protected function getModerationFormRoute(EntityTypeInterface $entity_type) { - if ($entity_type->hasLinkTemplate('moderation-form') && $entity_type->getFormClass('moderation')) { - $entity_type_id = $entity_type->id(); - - $route = new Route($entity_type->getLinkTemplate('moderation-form')); - - // @todo Come up with a new permission. - $route - ->setDefaults([ - '_entity_form' => "{$entity_type_id}.moderation", - '_title' => 'Moderation', - ]) - ->setRequirement('_permission', 'administer content moderation') - ->setOption('parameters', [ - $entity_type_id => ['type' => 'entity:' . $entity_type_id], - ]); - - return $route; - } - } - -} diff --git a/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php b/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php index 8574a99624..fdb532d4c3 100644 --- a/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php +++ b/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php @@ -32,7 +32,6 @@ * @var array */ protected $permissions = [ - 'administer content moderation', 'access administration pages', 'administer content types', 'administer nodes', @@ -41,6 +40,7 @@ 'access content overview', 'use editorial transition create_new_draft', 'use editorial transition publish', + 'administer workflows', ]; /** @@ -115,9 +115,13 @@ protected function createContentTypeFromUi($content_type_name, $content_type_id, * @param string $workflow_id * The workflow to attach to the bundle. */ - protected function enableModerationThroughUi($content_type_id, $workflow_id = 'editorial') { - $edit['workflow'] = $workflow_id; - $this->drupalPostForm('admin/structure/types/manage/' . $content_type_id . '/moderation', $edit, t('Save')); + public function enableModerationThroughUi($content_type_id, $workflow_id = 'editorial') { + $this->drupalGet('/admin/config/workflow/workflows'); + $this->assertLinkByHref('admin/config/workflow/workflows/manage/' . $workflow_id); + $this->drupalGet('/admin/config/workflow/workflows/manage/' . $workflow_id); + $this->assertText('Content'); + $edit['bundles[' . $content_type_id . ']'] = TRUE; + $this->drupalPostForm('admin/config/workflow/workflows/manage/' . $workflow_id . '/type/node', $edit, t('Save')); // Ensure the parent environment is up-to-date. // @see content_moderation_workflow_insert() \Drupal::service('entity_type.bundle.info')->clearCachedBundles(); diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php index 7d2f74666e..868049944f 100644 --- a/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php +++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php @@ -69,7 +69,6 @@ public function testViewShowsCorrectStates() { $permissions = [ 'access content', 'view all revisions', - 'administer content moderation', ]; $admin1 = $this->drupalCreateUser($permissions); $this->drupalLogin($admin1); diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateBlockTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateBlockTest.php index d45fb7b337..1a8f78c504 100644 --- a/core/modules/content_moderation/tests/src/Functional/ModerationStateBlockTest.php +++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateBlockTest.php @@ -51,17 +51,9 @@ protected function setUp() { public function testCustomBlockModeration() { $this->drupalLogin($this->rootUser); - $this->drupalGet('admin/structure/block/block-content/types'); - $this->assertLinkByHref('admin/structure/block/block-content/manage/basic/moderation'); - $this->drupalGet('admin/structure/block/block-content/manage/basic'); - $this->assertLinkByHref('admin/structure/block/block-content/manage/basic/moderation'); - $this->drupalGet('admin/structure/block/block-content/manage/basic/moderation'); - - // Enable moderation for custom blocks at - // admin/structure/block/block-content/manage/basic/moderation. - $edit = ['workflow' => 'editorial']; - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertText(t('Your settings have been saved.')); + // Enable moderation for custom blocks. + $edit['bundles[basic]'] = TRUE; + $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/block_content', $edit, t('Save')); // Create a custom block at block/add and save it as draft. $body = 'Body of moderated block'; diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php index 7579afa4e6..74129a1980 100644 --- a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php +++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php @@ -52,9 +52,8 @@ public function testCreatingContent() { $this->assertText(t('The Moderated content moderated content has been deleted.')); // Disable content moderation. - $this->drupalPostForm('admin/structure/types/manage/moderated_content/moderation', ['workflow' => ''], t('Save')); - $this->drupalGet('admin/structure/types/manage/moderated_content/moderation'); - $this->assertOptionSelected('edit-workflow', ''); + $edit['bundles[moderated_content]'] = FALSE; + $this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', $edit, t('Save'));; // Ensure the parent environment is up-to-date. // @see content_moderation_workflow_insert() \Drupal::service('entity_type.bundle.info')->clearCachedBundles(); diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php index 8391dd0aae..de4a7c29a2 100644 --- a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php +++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php @@ -30,7 +30,7 @@ public function testNotModerated() { */ public function testEnablingOnExistingContent() { $editor_permissions = [ - 'administer content moderation', + 'administer workflows', 'access administration pages', 'administer content types', 'administer nodes', @@ -56,17 +56,8 @@ public function testEnablingOnExistingContent() { ], t('Save and publish')); $this->assertText('Not moderated Test has been created.'); - // Now enable moderation state, ensuring all the expected links and tabs are - // present. - $this->drupalGet('admin/structure/types'); - $this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation'); - $this->drupalGet('admin/structure/types/manage/not_moderated'); - $this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation'); - $this->drupalGet('admin/structure/types/manage/not_moderated/moderation'); - $this->assertOptionSelected('edit-workflow', ''); - $this->assertNoLink('Delete'); - $edit['workflow'] = 'editorial'; - $this->drupalPostForm(NULL, $edit, t('Save')); + // Now enable moderation state. + $this->enableModerationThroughUi('not_moderated'); // And make sure it works. $nodes = \Drupal::entityTypeManager()->getStorage('node') diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateTestBase.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateTestBase.php index 1319ecae1e..9b98a5b718 100644 --- a/core/modules/content_moderation/tests/src/Functional/ModerationStateTestBase.php +++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateTestBase.php @@ -29,7 +29,7 @@ * @var array */ protected $permissions = [ - 'administer content moderation', + 'administer workflows', 'access administration pages', 'administer content types', 'administer nodes', @@ -112,9 +112,11 @@ protected function createContentTypeFromUi($content_type_name, $content_type_id, * @param string $workflow_id * The workflow to attach to the bundle. */ - protected function enableModerationThroughUi($content_type_id, $workflow_id = 'editorial') { - $edit['workflow'] = $workflow_id; - $this->drupalPostForm('admin/structure/types/manage/' . $content_type_id . '/moderation', $edit, t('Save')); + public function enableModerationThroughUi($content_type_id, $workflow_id = 'editorial') { + $this->drupalGet('/admin/config/workflow/workflows'); + $this->assertLinkByHref('admin/config/workflow/workflows/manage/' . $workflow_id); + $edit['bundles[' . $content_type_id . ']'] = TRUE; + $this->drupalPostForm('admin/config/workflow/workflows/manage/' . $workflow_id . '/type/node', $edit, t('Save')); // Ensure the parent environment is up-to-date. // @see content_moderation_workflow_insert() \Drupal::service('entity_type.bundle.info')->clearCachedBundles(); diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationWorkflowTypeApiTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationWorkflowTypeApiTest.php index c2ea8c576d..220ac5d5a6 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationWorkflowTypeApiTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationWorkflowTypeApiTest.php @@ -101,4 +101,26 @@ public function testAddEntityTypeAndBundle() { ); } + /** + * @covers ::addEntityTypeAndBundle + * @covers ::removeEntityTypeAndBundle + */ + public function testRemoveEntityTypeAndBundle() { + /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $workflow_plugin */ + $workflow_plugin = $this->workflow->getTypePlugin(); + + // There should be no bundles for fake_node to start with. + $this->assertEquals([], $workflow_plugin->getBundlesForEntityType('fake_node')); + // Removing a bundle which is not set on the workflow should not throw an + // error and should still result in none being returned. + $workflow_plugin->removeEntityTypeAndBundle('fake_node', 'fake_page'); + $this->assertEquals([], $workflow_plugin->getBundlesForEntityType('fake_node')); + // Adding a bundle for fake_node should result it in being returned, but + // then removing it will return no bundles for fake_node. + $workflow_plugin->addEntityTypeAndBundle('fake_node', 'fake_page'); + $this->assertEquals(['fake_page'], $workflow_plugin->getBundlesForEntityType('fake_node')); + $workflow_plugin->removeEntityTypeAndBundle('fake_node', 'fake_page'); + $this->assertEquals([], $workflow_plugin->getBundlesForEntityType('fake_node')); + } + } diff --git a/core/modules/content_moderation/tests/src/Kernel/EntityTypeInfoTest.php b/core/modules/content_moderation/tests/src/Kernel/EntityTypeInfoTest.php index 1c45721d03..a5791c8840 100644 --- a/core/modules/content_moderation/tests/src/Kernel/EntityTypeInfoTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/EntityTypeInfoTest.php @@ -20,6 +20,7 @@ class EntityTypeInfoTest extends KernelTestBase { */ public static $modules = [ 'content_moderation', + 'workflows', 'entity_test', ]; diff --git a/core/modules/workflows/src/Form/WorkflowEditForm.php b/core/modules/workflows/src/Form/WorkflowEditForm.php index c0ea5a4d39..764c0c9bc5 100644 --- a/core/modules/workflows/src/Form/WorkflowEditForm.php +++ b/core/modules/workflows/src/Form/WorkflowEditForm.php @@ -2,6 +2,8 @@ namespace Drupal\workflows\Form; +use Drupal\Core\Form\SubformState; +use Drupal\workflows\WorkflowTypeFormInterface; use Drupal\workflows\Entity\Workflow; use Drupal\workflows\State; use Drupal\Core\Entity\EntityForm; @@ -180,16 +182,41 @@ public function form(array $form, FormStateInterface $form_state) { '#markup' => $workflow->toLink($this->t('Add a new transition'), 'add-transition-form')->toString(), ]; + if ($workflow->getTypePlugin() instanceof WorkflowTypeFormInterface) { + $form['type_settings'] = [ + '#tree' => TRUE, + ]; + $subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state); + $form['type_settings'] += $workflow->getTypePlugin()->buildConfigurationForm($form['type_settings'], $subform_state, $workflow); + } + return $form; } /** * {@inheritdoc} */ + public function validateForm(array &$form, FormStateInterface $form_state) { + /* @var \Drupal\workflows\WorkflowInterface $workflow */ + $workflow = $this->entity; + if ($workflow->getTypePlugin() instanceof WorkflowTypeFormInterface) { + $subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state); + $workflow->getTypePlugin()->validateConfigurationForm($form['type_settings'], $subform_state); + } + } + + /** + * {@inheritdoc} + */ public function save(array $form, FormStateInterface $form_state) { /* @var \Drupal\workflows\WorkflowInterface $workflow */ $workflow = $this->entity; + if ($workflow->getTypePlugin() instanceof WorkflowTypeFormInterface) { + $subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state); + $workflow->getTypePlugin()->submitConfigurationForm($form['type_settings'], $subform_state); + } $workflow->save(); + drupal_set_message($this->t('Saved the %label Workflow.', ['%label' => $workflow->label()])); } diff --git a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php index 59c6b96137..7e9fed5b0b 100644 --- a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php +++ b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php @@ -56,28 +56,28 @@ public function checkWorkflowAccess(WorkflowInterface $entity, $operation, Accou } /** - * {@inheritDoc} + * {@inheritdoc} */ public function decorateState(StateInterface $state) { return $state; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function deleteState($state_id) { unset($this->configuration['states'][$state_id]); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function decorateTransition(TransitionInterface $transition) { return $transition; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function deleteTransition($transition_id) { unset($this->configuration['transitions'][$transition_id]); @@ -98,14 +98,14 @@ public function buildTransitionConfigurationForm(FormStateInterface $form_state, } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getConfiguration() { return $this->configuration; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function setConfiguration(array $configuration) { $this->configuration = NestedArray::mergeDeep( @@ -122,7 +122,7 @@ public function getRequiredStates() { } /** - * {@inheritDoc} + * {@inheritdoc} */ public function defaultConfiguration() { return [ @@ -132,7 +132,7 @@ public function defaultConfiguration() { } /** - * {@inheritDoc} + * {@inheritdoc} */ public function calculateDependencies() { return []; diff --git a/core/modules/workflows/src/Plugin/WorkflowTypeFormBase.php b/core/modules/workflows/src/Plugin/WorkflowTypeFormBase.php new file mode 100644 index 0000000000..942c2d6ef4 --- /dev/null +++ b/core/modules/workflows/src/Plugin/WorkflowTypeFormBase.php @@ -0,0 +1,30 @@ + '', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['example_setting'] = [ + '#type' => 'textfield', + '#title' => $this->t('Example global workflow setting'), + '#description' => $this->t('Extra information added to the workflow'), + '#default_value' => $this->configuration['example_setting'], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['example_setting'] = $form_state->getValue('example_setting'); + } + } diff --git a/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php b/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php index 3014dd7397..f42edfdfb5 100644 --- a/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php +++ b/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php @@ -251,6 +251,28 @@ public function testWorkflowCreation() { } /** + * Test the workflow configuration form. + */ + public function testWorkflowConfigurationForm() { + $workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_complex_test', 'label' => 'Test']); + $workflow + ->addState('published', 'Published') + ->addTransition('publish', 'Publish', ['published'], 'published') + ->save(); + + $this->drupalLogin($this->createUser(['administer workflows'])); + + // Add additional information to the workflow via the configuration form. + $this->drupalGet('admin/config/workflow/workflows/manage/test'); + $this->assertSession()->pageTextContains('Example global workflow setting'); + $this->submitForm(['type_settings[example_setting]' => 'Extra global settings'], 'Save'); + + $workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertEquals('Extra global settings', $workflow->getTypePlugin()->getConfiguration()['example_setting']); + } + + /** * Tests that workflow types can add form fields to states and transitions. */ public function testWorkflowDecoration() { diff --git a/core/themes/seven/css/components/dropbutton.component.css b/core/themes/seven/css/components/dropbutton.component.css index 229f1bdd02..bc3e87319f 100644 --- a/core/themes/seven/css/components/dropbutton.component.css +++ b/core/themes/seven/css/components/dropbutton.component.css @@ -203,6 +203,9 @@ -webkit-transition: none; transition: none; } +.dropbutton-single .dropbutton-action a.use-ajax{ + float: left; +} /** * The dropdown trigger.