diff --git a/core/modules/content_moderation/config/install/content_moderation.state.archived.yml b/core/modules/content_moderation/config/install/content_moderation.state.archived.yml deleted file mode 100644 index 0279481..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state.archived.yml +++ /dev/null @@ -1,8 +0,0 @@ -langcode: en -status: true -dependencies: { } -id: archived -label: Archived -published: false -default_revision: true -weight: -8 diff --git a/core/modules/content_moderation/config/install/content_moderation.state.draft.yml b/core/modules/content_moderation/config/install/content_moderation.state.draft.yml deleted file mode 100644 index c7eb64c..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state.draft.yml +++ /dev/null @@ -1,8 +0,0 @@ -langcode: en -status: true -dependencies: { } -id: draft -label: Draft -published: false -default_revision: false -weight: -10 diff --git a/core/modules/content_moderation/config/install/content_moderation.state.published.yml b/core/modules/content_moderation/config/install/content_moderation.state.published.yml deleted file mode 100644 index 8467e86..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state.published.yml +++ /dev/null @@ -1,8 +0,0 @@ -langcode: en -status: true -dependencies: { } -id: published -label: Published -published: true -default_revision: true -weight: -9 diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_draft.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_draft.yml deleted file mode 100644 index 8fbf9c3..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_draft.yml +++ /dev/null @@ -1,11 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - content_moderation.state.archived - - content_moderation.state.draft -id: archived_draft -label: 'Un-archive to Draft' -stateFrom: archived -stateTo: draft -weight: -5 diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_published.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_published.yml deleted file mode 100644 index 4be7600..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_published.yml +++ /dev/null @@ -1,11 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - content_moderation.state.archived - - content_moderation.state.published -id: archived_published -label: 'Un-archive' -stateFrom: archived -stateTo: published -weight: -4 diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_draft.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_draft.yml deleted file mode 100644 index 0ba0f34..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_draft.yml +++ /dev/null @@ -1,10 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - content_moderation.state.draft -id: draft_draft -label: 'Create New Draft' -stateFrom: draft -stateTo: draft -weight: -10 diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_published.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_published.yml deleted file mode 100644 index cf95d3d..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_published.yml +++ /dev/null @@ -1,11 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - content_moderation.state.draft - - content_moderation.state.published -id: draft_published -label: 'Publish' -stateFrom: draft -stateTo: published -weight: -9 diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_archived.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_archived.yml deleted file mode 100644 index f3a866a..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_archived.yml +++ /dev/null @@ -1,11 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - content_moderation.state.archived - - content_moderation.state.published -id: published_archived -label: 'Archive' -stateFrom: published -stateTo: archived -weight: -6 diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_draft.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_draft.yml deleted file mode 100644 index bd25a31..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_draft.yml +++ /dev/null @@ -1,11 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - content_moderation.state.draft - - content_moderation.state.published -id: published_draft -label: 'Create New Draft' -stateFrom: published -stateTo: draft -weight: -8 diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_published.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_published.yml deleted file mode 100644 index 3c09a85..0000000 --- a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_published.yml +++ /dev/null @@ -1,10 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - content_moderation.state.published -id: published_published -label: 'Publish' -stateFrom: published -stateTo: published -weight: -7 diff --git a/core/modules/content_moderation/config/install/content_moderation.workflow.typical.yml b/core/modules/content_moderation/config/install/content_moderation.workflow.typical.yml new file mode 100644 index 0000000..da61c2c --- /dev/null +++ b/core/modules/content_moderation/config/install/content_moderation.workflow.typical.yml @@ -0,0 +1,50 @@ +langcode: en +status: true +dependencies: { } +id: typical +label: 'Typical workflow' +states: + archived: + label: Archived + published: false + default_revision: true + weight: 5 + draft: + label: Draft + published: false + default_revision: false + weight: -5 + published: + label: Published + published: true + default_revision: true + weight: 0 +transitions: + - + label: 'Un-archive to Draft' + from: archived + to: draft + - + label: 'Un-archive' + from: archived + to: published + - + label: 'Create new draft' + from: draft + to: draft + - + label: 'Publish' + from: draft + to: published + - + label: 'Archive' + from: published + to: archived + - + label: 'Create new draft' + from: published + to: draft + - + label: 'Publish' + from: published + to: published diff --git a/core/modules/content_moderation/config/schema/content_moderation.schema.yml b/core/modules/content_moderation/config/schema/content_moderation.schema.yml index 7f9e8fd..4929733 100644 --- a/core/modules/content_moderation/config/schema/content_moderation.schema.yml +++ b/core/modules/content_moderation/config/schema/content_moderation.schema.yml @@ -1,26 +1,14 @@ -content_moderation.state.*: - type: config_entity - label: 'Moderation state config' +views.filter.latest_revision: + type: views_filter + label: 'Latest revision' mapping: - id: + value: type: string - label: 'ID' - label: - type: label - label: 'Label' - published: - type: boolean - label: 'Is published' - default_revision: - type: boolean - label: 'Is default revision' - weight: - type: integer - label: 'Weight' + label: 'Value' -content_moderation.state_transition.*: +content_moderation.workflow.*: type: config_entity - label: 'Moderation state transition config' + label: 'Moderation state config' mapping: id: type: string @@ -28,52 +16,38 @@ content_moderation.state_transition.*: label: type: label label: 'Label' - stateFrom: - type: string - label: 'From state' - stateTo: - type: string - label: 'To state' - weight: - type: integer - label: 'Weight' - -node.type.*.third_party.content_moderation: - type: mapping - label: 'Enable moderation states for this node type' - mapping: - enabled: - type: boolean - label: 'Moderation states enabled' - allowed_moderation_states: + states: type: sequence + label: 'States' sequence: - type: string - label: 'Moderation state' - default_moderation_state: - type: string - label: 'Moderation state for new content' - -block_content.type.*.third_party.content_moderation: - type: mapping - label: 'Enable moderation states for this block content type' - mapping: - enabled: - type: boolean - label: 'Moderation states enabled' - allowed_moderation_states: + type: mapping + label: 'State' + mapping: + label: + type: label + label: 'Label' + published: + type: boolean + label: 'Is published' + default_revision: + type: boolean + label: 'Is default revision' + weight: + type: integer + label: 'Weight' + transitions: type: sequence + label: 'Transitions' sequence: - type: string - label: 'Moderation state' - default_moderation_state: - type: string - label: 'Moderation state for new block content' - -views.filter.latest_revision: - type: views_filter - label: 'Latest revision' - mapping: - value: - type: string - label: 'Value' + type: mapping + label: 'Transition' + mapping: + label: + type: label + label: 'Label' + from: + type: string + label: 'From state' + to: + type: string + label: 'To state' diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module index 582242b..41b8186 100644 --- a/core/modules/content_moderation/content_moderation.module +++ b/core/modules/content_moderation/content_moderation.module @@ -182,15 +182,17 @@ function content_moderation_node_access(NodeInterface $node, $operation, Account $access_result->addCacheableDependency($node); } - elseif ($operation === 'update' && $moderation_info->isModeratedEntity($node) && $node->moderation_state && $node->moderation_state->target_id) { + elseif ($operation === 'update' && $moderation_info->isModeratedEntity($node) && $node->moderation_state) { /** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */ $transition_validation = \Drupal::service('content_moderation.state_transition_validation'); - $valid_transition_targets = $transition_validation->getValidTransitionTargets($node, $account); + $valid_transition_targets = $transition_validation->getValidTransitions($node, $account); $access_result = $valid_transition_targets ? AccessResult::neutral() : AccessResult::forbidden(); $access_result->addCacheableDependency($node); $access_result->addCacheableDependency($account); + $workflow = \Drupal::service('content_moderation.moderation_information')->getWorkflowForEntity($node); + $access_result->addCacheableDependency($workflow); foreach ($valid_transition_targets as $valid_transition_target) { $access_result->addCacheableDependency($valid_transition_target); } @@ -222,3 +224,23 @@ function content_moderation_action_info_alter(&$definitions) { $definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class; } } + +/** + * Implements hook_entity_bundle_info_alter(). + */ +function content_moderation_entity_bundle_info_alter(&$bundles) { + /* @var \Drupal\content_moderation\WorkflowInterface[] $workflows */ + $workflows = \Drupal::entityTypeManager()->getStorage('workflow')->loadMultiple(); + foreach ($workflows as $workflow_id => $workflow) { + foreach ($workflow->getApplies() as $entity_type => $workflow_bundles) { + foreach ($workflow_bundles as $bundle => $default_state) { + if (isset($bundles[$entity_type][$bundle])) { + $bundles[$entity_type][$bundle]['workflow_information'] = [ + 'workflow' => $workflow_id, + 'default_state' => $default_state, + ]; + } + } + } + } +} diff --git a/core/modules/content_moderation/content_moderation.permissions.yml b/core/modules/content_moderation/content_moderation.permissions.yml index 293a77d..fc0ed53 100644 --- a/core/modules/content_moderation/content_moderation.permissions.yml +++ b/core/modules/content_moderation/content_moderation.permissions.yml @@ -16,6 +16,11 @@ view any unpublished content: description: 'Create and edit content moderation state transitions.' 'restrict access': TRUE +'administer workflows': + title: 'Administer workflows' + description: 'Create and edit workflows.' + '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.services.yml b/core/modules/content_moderation/content_moderation.services.yml index 75f0c64..904bc0d 100644 --- a/core/modules/content_moderation/content_moderation.services.yml +++ b/core/modules/content_moderation/content_moderation.services.yml @@ -6,10 +6,10 @@ services: - { name: paramconverter, priority: 5 } content_moderation.state_transition_validation: class: \Drupal\content_moderation\StateTransitionValidation - arguments: ['@entity_type.manager', '@entity.query'] + arguments: ['@content_moderation.moderation_information'] content_moderation.moderation_information: class: Drupal\content_moderation\ModerationInformation - arguments: ['@entity_type.manager'] + arguments: ['@entity_type.manager', '@entity_type.bundle.info'] access_check.latest_revision: class: Drupal\content_moderation\Access\LatestRevisionCheck arguments: ['@content_moderation.moderation_information'] diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php index 978408f..9fc766a 100644 --- a/core/modules/content_moderation/src/Entity/ContentModerationState.php +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php @@ -55,10 +55,9 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setTranslatable(TRUE) ->setRevisionable(TRUE); - $fields['moderation_state'] = BaseFieldDefinition::create('entity_reference') + $fields['moderation_state'] = BaseFieldDefinition::create('string') ->setLabel(t('Moderation state')) ->setDescription(t('The moderation state of the referenced content.')) - ->setSetting('target_type', 'moderation_state') ->setRequired(TRUE) ->setTranslatable(TRUE) ->setRevisionable(TRUE) @@ -155,7 +154,7 @@ public function save() { if ($related_entity instanceof TranslatableInterface) { $related_entity = $related_entity->getTranslation($this->activeLangcode); } - $related_entity->moderation_state->target_id = $this->moderation_state->target_id; + $related_entity->moderation_state = $this->moderation_state; return $related_entity->save(); } diff --git a/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php b/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php index 83de187..247d352 100644 --- a/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php +++ b/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php @@ -2,8 +2,11 @@ namespace Drupal\content_moderation\Entity\Handler; +use Drupal\content_moderation\ModerationInformationInterface; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormStateInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Customizations for node entities. @@ -11,6 +14,32 @@ class NodeModerationHandler extends ModerationHandler { /** + * The moderation information service. + * + * @var \Drupal\content_moderation\ModerationInformationInterface + */ + protected $moderationInfo; + + /** + * NodeModerationHandler constructor. + * + * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info + * The moderation information service. + */ + public function __construct(ModerationInformationInterface $moderation_info) { + $this->moderationInfo = $moderation_info; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $container->get('content_moderation.moderation_information') + ); + } + + /** * {@inheritdoc} */ public function onPresave(ContentEntityInterface $entity, $default_revision, $published_state) { @@ -38,7 +67,7 @@ public function enforceRevisionsBundleFormAlter(array &$form, FormStateInterface /* @var \Drupal\node\Entity\NodeType $entity */ $entity = $form_state->getFormObject()->getEntity(); - if ($entity->getThirdPartySetting('content_moderation', 'enabled', FALSE)) { + if ($this->moderationInfo->getWorkFlowForEntity($entity)) { // Force the revision checkbox on. $form['workflow']['options']['#default_value']['revision'] = 'revision'; $form['workflow']['options']['revision']['#disabled'] = TRUE; diff --git a/core/modules/content_moderation/src/Entity/ModerationState.php b/core/modules/content_moderation/src/Entity/ModerationState.php deleted file mode 100644 index 379ff60..0000000 --- a/core/modules/content_moderation/src/Entity/ModerationState.php +++ /dev/null @@ -1,102 +0,0 @@ -published; - } - - /** - * {@inheritdoc} - */ - public function isDefaultRevisionState() { - return $this->published || $this->default_revision; - } - -} diff --git a/core/modules/content_moderation/src/Entity/ModerationStateTransition.php b/core/modules/content_moderation/src/Entity/ModerationStateTransition.php deleted file mode 100644 index 95a115b..0000000 --- a/core/modules/content_moderation/src/Entity/ModerationStateTransition.php +++ /dev/null @@ -1,114 +0,0 @@ -stateFrom) { - $this->addDependency('config', ModerationState::load($this->stateFrom)->getConfigDependencyName()); - } - if ($this->stateTo) { - $this->addDependency('config', ModerationState::load($this->stateTo)->getConfigDependencyName()); - } - return $this; - } - - /** - * {@inheritdoc} - */ - public function getFromState() { - return $this->stateFrom; - } - - /** - * {@inheritdoc} - */ - public function getToState() { - return $this->stateTo; - } - - /** - * {@inheritdoc} - */ - public function getWeight() { - return $this->weight; - } - -} diff --git a/core/modules/content_moderation/src/Entity/Workflow.php b/core/modules/content_moderation/src/Entity/Workflow.php new file mode 100644 index 0000000..fd76299 --- /dev/null +++ b/core/modules/content_moderation/src/Entity/Workflow.php @@ -0,0 +1,197 @@ +applies[$entity_type_id][$bundle_id]); + } + + public function applyToEntityTypeAndBundle($entity_type_id, $bundle_id) { + $this->applies[$entity_type_id][$bundle_id] = ''; + return $this; + } + + public function removeEntityTypeAndBundle($entity_type_id, $bundle_id) { + unset($this->applies[$entity_type_id][$bundle_id]); + return $this; + } + + public function setDefaultState($entity_type_id, $bundle_id, $state) { + if (!isset($this->states[$state])) { + // @todo convert to something specific + throw new \Exception(); + } + $this->applies[$entity_type_id][$bundle_id] = $state; + return $this; + } + + public function getDefaultState($entity_type_id, $bundle_id) { + if (isset($this->applies[$entity_type_id][$bundle_id])) { + return $this->applies[$entity_type_id][$bundle_id]; + } + return NULL; + } + + public function getStateOptions() { + return array_map(function (array $state) { + return $state['label']; + }, $this->states); + } + + public function canTranstion($from_state, $to_state) { + foreach ($this->transitions as $transition) { + if ($transition['from'] === $from_state && $transition['to'] === $to_state) { + return TRUE; + } + } + return FALSE; + } + + public function getPossibleStates($from_state) { + $to_states = []; + foreach ($this->transitions as $transition) { + if ($transition['from'] === $from_state) { + $to_states[] = $transition['to']; + } + } + return $to_states; + + } + + public function getApplies() { + return $this->applies; + } + + public function getStateLabel($state) { + if (!isset($this->states[$state]['label'])) { + // @todo + throw new \InvalidArgumentException(); + } + return $this->states[$state]['label']; + } + + /** + * {@inheritdoc} + */ + public function getTransitions() { + return $this->transitions; + } + + + public function isDefaultRevisionState($state) { + if (!isset($this->states[$state])) { + // @todo + throw new \InvalidArgumentException(); + } + return $this->states[$state]['default_revision']; + } + + public function isPublishedState($state) { + if (!isset($this->states[$state])) { + // @todo + throw new \InvalidArgumentException(); + } + return $this->states[$state]['published']; + } + +} diff --git a/core/modules/content_moderation/src/EntityOperations.php b/core/modules/content_moderation/src/EntityOperations.php index 51ad7d6..1052a80 100644 --- a/core/modules/content_moderation/src/EntityOperations.php +++ b/core/modules/content_moderation/src/EntityOperations.php @@ -6,6 +6,7 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\TypedData\TranslatableInterface; @@ -46,6 +47,11 @@ class EntityOperations implements ContainerInjectionInterface { protected $tracker; /** + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ + protected $bundleInfo; + + /** * Constructs a new EntityOperations object. * * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info @@ -57,11 +63,12 @@ class EntityOperations implements ContainerInjectionInterface { * @param \Drupal\content_moderation\RevisionTrackerInterface $tracker * The revision tracker. */ - public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, RevisionTrackerInterface $tracker) { + public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, RevisionTrackerInterface $tracker, EntityTypeBundleInfoInterface $bundle_indo) { $this->moderationInfo = $moderation_info; $this->entityTypeManager = $entity_type_manager; $this->formBuilder = $form_builder; $this->tracker = $tracker; + $this->bundleInfo = $bundle_indo; } /** @@ -72,7 +79,8 @@ public static function create(ContainerInterface $container) { $container->get('content_moderation.moderation_information'), $container->get('entity_type.manager'), $container->get('form_builder'), - $container->get('content_moderation.revision_tracker') + $container->get('content_moderation.revision_tracker'), + $container->get('entity_type.bundle.info') ); } @@ -86,17 +94,16 @@ public function entityPresave(EntityInterface $entity) { if (!$this->moderationInfo->isModeratedEntity($entity)) { return; } - if ($entity->moderation_state->target_id) { - $moderation_state = $this->entityTypeManager - ->getStorage('moderation_state') - ->load($entity->moderation_state->target_id); - $published_state = $moderation_state->isPublishedState(); + + if ($entity->moderation_state->value) { + $workflow = $this->moderationInfo->getWorkFlowForEntity($entity); + $published_state = $workflow->isPublishedState($entity->moderation_state->value); // This entity is default if it is new, the default revision, or the // default revision is not published. $update_default_revision = $entity->isNew() - || $moderation_state->isDefaultRevisionState() - || !$this->isDefaultRevisionPublished($entity); + || $workflow->isDefaultRevisionState($entity->moderation_state->value) + || !$this->isDefaultRevisionPublished($entity, $workflow); // Fire per-entity-type logic for handling the save process. $this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->onPresave($entity, $update_default_revision, $published_state); @@ -140,15 +147,13 @@ public function entityUpdate(EntityInterface $entity) { * The entity to update or create a moderation state for. */ protected function updateOrCreateFromEntity(EntityInterface $entity) { - $moderation_state = $entity->moderation_state->target_id; + $moderation_state = $entity->moderation_state->value; /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ if (!$moderation_state) { - $moderation_state = $this->entityTypeManager - ->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle()) - ->getThirdPartySetting('content_moderation', 'default_moderation_state'); + $moderation_state = $this->bundleInfo->getBundleInfo($entity->getEntityType()->id())[$entity->bundle()]['workflow_information']['default_state']; } - // @todo what if $entity->moderation_state->target_id is null at this point? + // @todo what if $entity->moderation_state is null at this point? $entity_type_id = $entity->getEntityTypeId(); $entity_id = $entity->id(); $entity_revision_id = $entity->getRevisionId(); @@ -239,11 +244,13 @@ public function entityView(array &$build, EntityInterface $entity, EntityViewDis * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity being saved. + * @param \Drupal\content_moderation\WorkflowInterface $workflow + * The workflow being applied to the entity. * * @return bool * TRUE if the default revision is published. FALSE otherwise. */ - protected function isDefaultRevisionPublished(EntityInterface $entity) { + protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowInterface $workflow) { $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId()); $default_revision = $storage->load($entity->id()); @@ -258,7 +265,7 @@ protected function isDefaultRevisionPublished(EntityInterface $entity) { $default_revision = $default_revision->getTranslation($entity->language()->getId()); } - return $default_revision && $default_revision->moderation_state->entity->isPublishedState(); + return $default_revision && $workflow->isPublishedState($default_revision->moderation_state->value); } } diff --git a/core/modules/content_moderation/src/EntityTypeInfo.php b/core/modules/content_moderation/src/EntityTypeInfo.php index ab7e918..939ba0a 100644 --- a/core/modules/content_moderation/src/EntityTypeInfo.php +++ b/core/modules/content_moderation/src/EntityTypeInfo.php @@ -291,7 +291,7 @@ public function entityBaseFieldInfo(EntityTypeInterface $entity_type) { } $fields = []; - $fields['moderation_state'] = BaseFieldDefinition::create('entity_reference') + $fields['moderation_state'] = BaseFieldDefinition::create('string') ->setLabel(t('Moderation state')) ->setDescription(t('The moderation state of this piece of content.')) ->setComputed(TRUE) diff --git a/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php b/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php index 58dd334..7276c0a 100644 --- a/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php +++ b/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php @@ -2,12 +2,10 @@ namespace Drupal\content_moderation\Form; -use Drupal\Core\Config\Entity\ThirdPartySettingsInterface; +use Drupal\content_moderation\WorkflowInterface; use Drupal\Core\Entity\EntityForm; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\content_moderation\Entity\ModerationState; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -50,146 +48,117 @@ public function getBaseFormId() { * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - /* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $bundle */ - $bundle = $form_state->getFormObject()->getEntity(); - $form['enable_moderation_state'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Enable moderation states.'), - '#description' => $this->t('Content of this type must transition through moderation states in order to be published.'), - '#default_value' => $bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE), + /* @var \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle */ + $bundle = $this->getEntity(); + $bundle_of_entity_type = $this->entityTypeManager->getDefinition($bundle->getEntityType()->getBundleOf()); + /* @var \Drupal\content_moderation\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(); + })); + + $selected_workflow = array_reduce($workflows, function ($carry, WorkflowInterface $workflow) use ($bundle_of_entity_type, $bundle) { + if ($workflow->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['bundle'] = [ + '#type' => 'hidden', + '#value' => $bundle->id(), + ]; + + $form['entity_type'] = [ + '#type' => 'hidden', + '#value' => $bundle_of_entity_type->id(), ]; // Add a special message when moderation is being disabled. - if ($bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE)) { + if ($selected_workflow) { $form['enable_moderation_state_note'] = [ '#type' => 'item', '#description' => $this->t('After disabling moderation, any existing forward drafts will be accessible via the "Revisions" tab.'), - '#states' => [ - 'visible' => [ - ':input[name=enable_moderation_state]' => ['checked' => FALSE], - ], - ], + // @todo make this work with new select + // '#states' => [ + // 'visible' => [ + // ':input[name=enable_moderation_state]' => ['checked' => FALSE], + // ], + // ], ]; } - $states = $this->entityTypeManager->getStorage('moderation_state')->loadMultiple(); - $label = function(ModerationState $state) { - return $state->label(); - }; - - $options_published = array_map($label, array_filter($states, function(ModerationState $state) { - return $state->isPublishedState(); - })); - - $options_unpublished = array_map($label, array_filter($states, function(ModerationState $state) { - return !$state->isPublishedState(); - })); - - $form['allowed_moderation_states_unpublished'] = [ - '#type' => 'checkboxes', - '#title' => $this->t('Allowed moderation states (Unpublished)'), - '#description' => $this->t('The allowed unpublished moderation states this content-type can be assigned.'), - '#default_value' => $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($options_unpublished)), - '#options' => $options_unpublished, - '#required' => TRUE, - '#states' => [ - 'visible' => [ - ':input[name=enable_moderation_state]' => ['checked' => TRUE], - ], - ], - ]; - - $form['allowed_moderation_states_published'] = [ - '#type' => 'checkboxes', - '#title' => $this->t('Allowed moderation states (Published)'), - '#description' => $this->t('The allowed published moderation states this content-type can be assigned.'), - '#default_value' => $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($options_published)), - '#options' => $options_published, - '#required' => TRUE, - '#states' => [ - 'visible' => [ - ':input[name=enable_moderation_state]' => ['checked' => TRUE], - ], - ], - ]; - - // The key of the array needs to be a user-facing string so we have to fully - // render the translatable string to a real string, or else PHP errors on an - // object used as an array key. - $options = [ - $this->t('Unpublished')->render() => $options_unpublished, - $this->t('Published')->render() => $options_published, - ]; + if ($selected_workflow) { + $options = $workflows[$selected_workflow]->getStateOptions(); + $default_moderation_state = $workflows[$selected_workflow]->getDefaultState($bundle_of_entity_type->id(), $bundle->id()); + } + else { + $options = []; + $default_moderation_state = NULL; + } $form['default_moderation_state'] = [ '#type' => 'select', '#title' => $this->t('Default moderation state'), '#options' => $options, - '#description' => $this->t('Select the moderation state for new content'), - '#default_value' => $bundle->getThirdPartySetting('content_moderation', 'default_moderation_state', 'draft'), - '#states' => [ - 'visible' => [ - ':input[name=enable_moderation_state]' => ['checked' => TRUE], - ], - ], + '#description' => $this->t('Select the moderation state for new %entity_type_label', ['%entity_type_label' => $bundle_of_entity_type->getSingularLabel()]), + '#default_value' => $default_moderation_state, + // @todo something + // '#states' => [ + // 'visible' => [ + // ':input[name=enable_moderation_state]' => ['checked' => TRUE], + // ], + // ], ]; - $form['#entity_builders'][] = [$this, 'formBuilderCallback']; return parent::form($form, $form_state); } /** - * Form builder callback. - * - * @todo This should be folded into the form method. - * - * @param string $entity_type_id - * The entity type identifier. - * @param \Drupal\Core\Entity\EntityInterface $bundle - * The bundle entity updated with the submitted values. - * @param array $form - * The complete form array. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. + * {@inheritdoc} */ - public function formBuilderCallback($entity_type_id, EntityInterface $bundle, &$form, FormStateInterface $form_state) { - // @todo https://www.drupal.org/node/2779933 write a test for this. - if ($bundle instanceof ThirdPartySettingsInterface) { - $bundle->setThirdPartySetting('content_moderation', 'enabled', $form_state->getValue('enable_moderation_state')); - $bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys(array_filter($form_state->getValue('allowed_moderation_states_published') + $form_state->getValue('allowed_moderation_states_unpublished')))); - $bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', $form_state->getValue('default_moderation_state')); - } + 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 validateForm(array &$form, FormStateInterface $form_state) { - if ($form_state->getValue('enable_moderation_state')) { - $allowed = array_keys(array_filter($form_state->getValue('allowed_moderation_states_published') + $form_state->getValue('allowed_moderation_states_unpublished'))); - - if (($default = $form_state->getValue('default_moderation_state')) && !in_array($default, $allowed, TRUE)) { - $form_state->setErrorByName('default_moderation_state', $this->t('The default moderation state must be one of the allowed states.')); + public function save(array $form, FormStateInterface $form_state) { + if ($workflow = $form_state->getValue('workflow')) { + /* @var \Drupal\content_moderation\WorkflowInterface $workflow */ + $workflow = $this->entityTypeManager->getStorage('workflow')->load($workflow); + $workflow->applyToEntityTypeAndBundle($form_state->getValue('entity_type'), $form_state->getValue('bundle')); + if ($form_state->getValue('default_moderation_state')) { + $workflow->setDefaultState($form_state->getValue('entity_type'), $form_state->getValue('bundle'), $form_state->getValue('default_moderation_state')); } + $workflow->save(); } - } - - /** - * {@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. - if ($form_state->getValue('enable_moderation_state')) { - /* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $bundle */ - $bundle = $form_state->getFormObject()->getEntity(); - - $this->entityTypeManager->getHandler($bundle->getEntityType()->getBundleOf(), 'moderation')->onBundleModerationConfigurationFormSubmit($bundle); + else { + /* @var \Drupal\content_moderation\WorkflowInterface[] $workflows */ + $workflows = $this->entityTypeManager->getStorage('workflow')->loadMultiple(); + foreach ($workflows as $workflow) { + if ($workflow->appliesToEntityTypeAndBundle($form_state->getValue('entity_type'), $form_state->getValue('bundle'))) { + $workflow + ->removeEntityTypeAndBundle($form_state->getValue('entity_type'), $form_state->getValue('bundle')) + ->save(); + } + } } - - parent::submitForm($form, $form_state); - - drupal_set_message($this->t('Your settings have been saved.')); + \Drupal::service('entity_type.bundle.info')->clearCachedBundles(); } } diff --git a/core/modules/content_moderation/src/Form/EntityModerationForm.php b/core/modules/content_moderation/src/Form/EntityModerationForm.php index 39baec0..4f79aa9 100644 --- a/core/modules/content_moderation/src/Form/EntityModerationForm.php +++ b/core/modules/content_moderation/src/Form/EntityModerationForm.php @@ -3,10 +3,8 @@ namespace Drupal\content_moderation\Form; use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\content_moderation\Entity\ModerationStateTransition; use Drupal\content_moderation\ModerationInformationInterface; use Drupal\content_moderation\StateTransitionValidation; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -31,26 +29,16 @@ class EntityModerationForm extends FormBase { protected $validation; /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** * EntityModerationForm constructor. * * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info * The moderation information service. * @param \Drupal\content_moderation\StateTransitionValidation $validation * The moderation state transition validation service. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager. */ - public function __construct(ModerationInformationInterface $moderation_info, StateTransitionValidation $validation, EntityTypeManagerInterface $entity_type_manager) { + public function __construct(ModerationInformationInterface $moderation_info, StateTransitionValidation $validation) { $this->moderationInfo = $moderation_info; $this->validation = $validation; - $this->entityTypeManager = $entity_type_manager; } /** @@ -59,8 +47,7 @@ public function __construct(ModerationInformationInterface $moderation_info, Sta public static function create(ContainerInterface $container) { return new static( $container->get('content_moderation.moderation_information'), - $container->get('content_moderation.state_transition_validation'), - $container->get('entity_type.manager') + $container->get('content_moderation.state_transition_validation') ); } @@ -75,20 +62,20 @@ public function getFormId() { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, ContentEntityInterface $entity = NULL) { - /** @var \Drupal\content_moderation\Entity\ModerationState $current_state */ - $current_state = $entity->moderation_state->entity; + $current_state = $entity->moderation_state->value; + $workflow = $this->moderationInfo->getWorkFlowForEntity($entity); $transitions = $this->validation->getValidTransitions($entity, $this->currentUser()); // Exclude self-transitions. - $transitions = array_filter($transitions, function(ModerationStateTransition $transition) use ($current_state) { - return $transition->getToState() != $current_state->id(); + $transitions = array_filter($transitions, function($transition) use ($current_state) { + return $transition != $current_state; }); $target_states = []; - /** @var ModerationStateTransition $transition */ + foreach ($transitions as $transition) { - $target_states[$transition->getToState()] = $transition->label(); + $target_states[$transition] = $workflow->getStateLabel($transition); } if (!count($target_states)) { @@ -99,7 +86,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ContentEn $form['current'] = [ '#type' => 'item', '#title' => $this->t('Status'), - '#markup' => $current_state->label(), + '#markup' => $workflow->getStateLabel($current_state), ]; } @@ -139,21 +126,19 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // @todo should we just just be updating the content moderation state // entity? That would prevent setting the revision log. - $entity->moderation_state->target_id = $new_state; + $entity->set('moderation_state', $new_state); $entity->revision_log = $form_state->getValue('revision_log'); $entity->save(); drupal_set_message($this->t('The moderation state has been updated.')); - /** @var \Drupal\content_moderation\Entity\ModerationState $state */ - $state = $this->entityTypeManager->getStorage('moderation_state')->load($new_state); - + $workflow = $this->moderationInfo->getWorkFlowForEntity($entity); // The page we're on likely won't be visible if we just set the entity to // the default state, as we hide that latest-revision tab if there is no // forward revision. Redirect to the canonical URL instead, since that will // still exist. - if ($state->isDefaultRevisionState()) { + if ($workflow->isDefaultRevisionState($new_state)) { $form_state->setRedirectUrl($entity->toUrl('canonical')); } } diff --git a/core/modules/content_moderation/src/Form/ModerationStateTransitionDeleteForm.php b/core/modules/content_moderation/src/Form/ModerationStateTransitionDeleteForm.php deleted file mode 100644 index f153f1f..0000000 --- a/core/modules/content_moderation/src/Form/ModerationStateTransitionDeleteForm.php +++ /dev/null @@ -1,49 +0,0 @@ -t('Are you sure you want to delete %name?', array('%name' => $this->entity->label())); - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - return new Url('entity.moderation_state_transition.collection'); - } - - /** - * {@inheritdoc} - */ - public function getConfirmText() { - return $this->t('Delete'); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $this->entity->delete(); - - drupal_set_message($this->t( - 'Moderation transition %label deleted.', - ['%label' => $this->entity->label()] - )); - - $form_state->setRedirectUrl($this->getCancelUrl()); - } - -} diff --git a/core/modules/content_moderation/src/Form/ModerationStateDeleteForm.php b/core/modules/content_moderation/src/Form/WorkflowDeleteForm.php similarity index 84% rename from core/modules/content_moderation/src/Form/ModerationStateDeleteForm.php rename to core/modules/content_moderation/src/Form/WorkflowDeleteForm.php index 43e2b36..69b99d2 100644 --- a/core/modules/content_moderation/src/Form/ModerationStateDeleteForm.php +++ b/core/modules/content_moderation/src/Form/WorkflowDeleteForm.php @@ -7,7 +7,7 @@ use Drupal\Core\Url; /** - * Builds the form to delete Moderation state entities. + * Builds the form to delete Workflow entities. */ class ModerationStateDeleteForm extends EntityConfirmFormBase { @@ -22,7 +22,7 @@ public function getQuestion() { * {@inheritdoc} */ public function getCancelUrl() { - return new Url('entity.moderation_state.collection'); + return new Url('entity.workflow.collection'); } /** @@ -39,7 +39,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $this->entity->delete(); drupal_set_message($this->t( - 'Moderation state %label deleted.', + 'Workflow %label deleted.', ['%label' => $this->entity->label()] )); diff --git a/core/modules/content_moderation/src/Form/WorkflowForm.php b/core/modules/content_moderation/src/Form/WorkflowForm.php new file mode 100644 index 0000000..b26b3ee --- /dev/null +++ b/core/modules/content_moderation/src/Form/WorkflowForm.php @@ -0,0 +1,68 @@ +entity; + $form['label'] = array( + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => $workflow->label(), + '#description' => $this->t('Label for the Workflow.'), + '#required' => TRUE, + ); + + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => $workflow->id(), + '#machine_name' => array( + 'exists' => [Workflow::class, 'load'], + ), + '#disabled' => !$workflow->isNew(), + ); + + // @todo add workflow overview. + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /* @var \Drupal\content_moderation\WorkflowInterface $workflow */ + $workflow = $this->entity; + $status = $workflow->save(); + + switch ($status) { + case SAVED_NEW: + drupal_set_message($this->t('Created the %label Worflow.', [ + '%label' => $workflow->label(), + ])); + break; + + default: + drupal_set_message($this->t('Saved the %label Workflow.', [ + '%label' => $workflow->label(), + ])); + } + $form_state->setRedirectUrl($workflow->toUrl('collection')); + } + +} diff --git a/core/modules/content_moderation/src/ModerationInformation.php b/core/modules/content_moderation/src/ModerationInformation.php index ed33902..b466451 100644 --- a/core/modules/content_moderation/src/ModerationInformation.php +++ b/core/modules/content_moderation/src/ModerationInformation.php @@ -4,6 +4,7 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -20,15 +21,23 @@ class ModerationInformation implements ModerationInformationInterface { protected $entityTypeManager; /** + * The bundle information service. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ + protected $bundleInfo; + + /** * Creates a new ModerationInformation instance. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. - * @param \Drupal\Core\Session\AccountInterface $current_user - * The current user. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info + * The bundle information service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info) { $this->entityTypeManager = $entity_type_manager; + $this->bundleInfo = $bundle_info; } /** @@ -54,10 +63,8 @@ public function canModerateEntitiesOfEntityType(EntityTypeInterface $entity_type */ public function shouldModerateEntitiesOfBundle(EntityTypeInterface $entity_type, $bundle) { if ($this->canModerateEntitiesOfEntityType($entity_type)) { - $bundle_entity = $this->entityTypeManager->getStorage($entity_type->getBundleEntityType())->load($bundle); - if ($bundle_entity) { - return $bundle_entity->getThirdPartySetting('content_moderation', 'enabled', FALSE); - } + $bundles = $this->bundleInfo->getBundleInfo($entity_type->id()); + return isset($bundles[$bundle]['workflow_information']); } return FALSE; } @@ -123,10 +130,26 @@ public function hasForwardRevision(ContentEntityInterface $entity) { * {@inheritdoc} */ public function isLiveRevision(ContentEntityInterface $entity) { + $workflow = $this->getWorkFlowForEntity($entity); return $this->isLatestRevision($entity) && $entity->isDefaultRevision() - && $entity->moderation_state->entity - && $entity->moderation_state->entity->isPublishedState(); + && $entity->moderation_state->value + && $workflow->isPublishedState($entity->moderation_state->value); + } + + /** + * @param $entity_type + * @param $bundle + * + * @return \Drupal\content_moderation\WorkflowInterface + * The workflow entity. + */ + public function getWorkFlowForEntity(ContentEntityInterface $entity) { + $bundles = $this->bundleInfo->getBundleInfo($entity->getEntityTypeId()); + if (isset($bundles[$entity->bundle()]['workflow_information']['workflow'])) { + return $this->entityTypeManager->getStorage('workflow')->load($bundles[$entity->bundle()]['workflow_information']['workflow']); + }; + return NULL; } } diff --git a/core/modules/content_moderation/src/ModerationInformationInterface.php b/core/modules/content_moderation/src/ModerationInformationInterface.php index 95a658e..fdf0f17 100644 --- a/core/modules/content_moderation/src/ModerationInformationInterface.php +++ b/core/modules/content_moderation/src/ModerationInformationInterface.php @@ -126,4 +126,13 @@ public function hasForwardRevision(ContentEntityInterface $entity); */ public function isLiveRevision(ContentEntityInterface $entity); + /** + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The content entity to get the workflow for. + * + * @return \Drupal\content_moderation\WorkflowInterface|null + * The workflow entity. + */ + public function getWorkFlowForEntity(ContentEntityInterface $entity); + } diff --git a/core/modules/content_moderation/src/Permissions.php b/core/modules/content_moderation/src/Permissions.php index 027684c..e94906db 100644 --- a/core/modules/content_moderation/src/Permissions.php +++ b/core/modules/content_moderation/src/Permissions.php @@ -2,9 +2,8 @@ namespace Drupal\content_moderation; +use Drupal\content_moderation\Entity\Workflow; use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\content_moderation\Entity\ModerationState; -use Drupal\content_moderation\Entity\ModerationStateTransition; /** * Defines a class for dynamic permissions based on transitions. @@ -22,19 +21,17 @@ class Permissions { public function transitionPermissions() { // @todo https://www.drupal.org/node/2779933 write a test for this. $perms = []; - /* @var \Drupal\content_moderation\ModerationStateInterface[] $states */ - $states = ModerationState::loadMultiple(); - /* @var \Drupal\content_moderation\ModerationStateTransitionInterface $transition */ - foreach (ModerationStateTransition::loadMultiple() as $id => $transition) { - $perms['use ' . $id . ' transition'] = [ - 'title' => $this->t('Use the %transition_name transition', [ - '%transition_name' => $transition->label(), - ]), - 'description' => $this->t('Move content from %from state to %to state.', [ - '%from' => $states[$transition->getFromState()]->label(), - '%to' => $states[$transition->getToState()]->label(), - ]), - ]; + + /** @var \Drupal\content_moderation\WorkflowInterface $workflow */ + foreach (Workflow::loadMultiple() as $id => $workflow) { + foreach ($workflow->getTransitions() as $transition) { + $perms['use ' . $workflow->id() . ' transition from ' . $transition['from'] . ' to ' . $transition['to']] = [ + 'title' => $this->t('Move content from %from state to %to state.', [ + '%from' => $workflow->getStateLabel($transition['from']), + '%to' => $workflow->getStateLabel($transition['to']), + ]), + ]; + } } return $perms; diff --git a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php index d6dc89d..1d00c75 100644 --- a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php +++ b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php @@ -3,9 +3,7 @@ namespace Drupal\content_moderation\Plugin\Field\FieldWidget; use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget; @@ -23,7 +21,7 @@ * id = "moderation_state_default", * label = @Translation("Moderation state"), * field_types = { - * "entity_reference" + * "string" * } * ) */ @@ -37,20 +35,6 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact protected $currentUser; /** - * Moderation state transition entity query. - * - * @var \Drupal\Core\Entity\Query\QueryInterface - */ - protected $moderationStateTransitionEntityQuery; - - /** - * Moderation state storage. - * - * @var \Drupal\Core\Entity\EntityStorageInterface - */ - protected $moderationStateStorage; - - /** * Moderation information service. * * @var \Drupal\content_moderation\ModerationInformation @@ -65,13 +49,6 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact protected $entityTypeManager; /** - * Moderation state transition storage. - * - * @var \Drupal\Core\Entity\EntityStorageInterface - */ - protected $moderationStateTransitionStorage; - - /** * Moderation state transition validation service. * * @var \Drupal\content_moderation\StateTransitionValidation @@ -95,22 +72,13 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact * Current user service. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * Entity type manager. - * @param \Drupal\Core\Entity\EntityStorageInterface $moderation_state_storage - * Moderation state storage. - * @param \Drupal\Core\Entity\EntityStorageInterface $moderation_state_transition_storage - * Moderation state transition storage. - * @param \Drupal\Core\Entity\Query\QueryInterface $entity_query - * Moderation transition entity query service. * @param \Drupal\content_moderation\ModerationInformation $moderation_information * Moderation information service. * @param \Drupal\content_moderation\StateTransitionValidation $validator * Moderation state transition validation service */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, EntityStorageInterface $moderation_state_storage, EntityStorageInterface $moderation_state_transition_storage, QueryInterface $entity_query, ModerationInformation $moderation_information, StateTransitionValidation $validator) { + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidation $validator) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); - $this->moderationStateTransitionEntityQuery = $entity_query; - $this->moderationStateTransitionStorage = $moderation_state_transition_storage; - $this->moderationStateStorage = $moderation_state_storage; $this->entityTypeManager = $entity_type_manager; $this->currentUser = $current_user; $this->moderationInformation = $moderation_information; @@ -129,9 +97,6 @@ public static function create(ContainerInterface $container, array $configuratio $configuration['third_party_settings'], $container->get('current_user'), $container->get('entity_type.manager'), - $container->get('entity_type.manager')->getStorage('moderation_state'), - $container->get('entity_type.manager')->getStorage('moderation_state_transition'), - $container->get('entity.query')->get('moderation_state_transition', 'AND'), $container->get('content_moderation.moderation_information'), $container->get('content_moderation.state_transition_validation') ); @@ -151,19 +116,17 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen return $element + ['#access' => FALSE]; } - $default = $items->get($delta)->value ?: $bundle_entity->getThirdPartySetting('content_moderation', 'default_moderation_state', FALSE); - /** @var \Drupal\content_moderation\ModerationStateInterface $default_state */ - $default_state = $this->entityTypeManager->getStorage('moderation_state')->load($default); - if (!$default || !$default_state) { + $workflow = $this->moderationInformation->getWorkFlowForEntity($entity); + $default = $items->get($delta)->value ?: $workflow->getDefaultState($entity->getEntityTypeId(), $entity->bundle()); + if (!$default) { throw new \UnexpectedValueException(sprintf('The %s bundle has an invalid moderation state configuration, moderation states are enabled but no default is set.', $bundle_entity->label())); } $transitions = $this->validator->getValidTransitions($entity, $this->currentUser); $target_states = []; - /** @var \Drupal\content_moderation\Entity\ModerationStateTransition $transition */ foreach ($transitions as $transition) { - $target_states[$transition->getToState()] = $transition->label(); + $target_states[$transition] = $workflow->getStateLabel($transition); } // @todo https://www.drupal.org/node/2779933 write a test for this. @@ -172,7 +135,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen '#type' => 'select', '#options' => $target_states, '#default_value' => $default, - '#published' => $default ? $default_state->isPublishedState() : FALSE, + '#published' => $default ? $workflow->isPublishedState($default) : FALSE, '#key_column' => $this->column, ]; $element['#element_validate'][] = array(get_class($this), 'validateElement'); @@ -197,7 +160,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen public static function updateStatus($entity_type_id, ContentEntityInterface $entity, array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); if (isset($element['#moderation_state'])) { - $entity->moderation_state->target_id = $element['#moderation_state']; + $entity->moderation_state->value = $element['#moderation_state']; } } diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php index 80820b4..626afbc 100644 --- a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php +++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php @@ -2,8 +2,7 @@ namespace Drupal\content_moderation\Plugin\Field; -use Drupal\content_moderation\Entity\ModerationState; -use Drupal\Core\Field\EntityReferenceFieldItemList; +use Drupal\Core\Field\FieldItemList; /** * A computed field that provides a content entity's moderation state. @@ -11,19 +10,20 @@ * It links content entities to a moderation state configuration entity via a * moderation state content entity. */ -class ModerationStateFieldItemList extends EntityReferenceFieldItemList { +class ModerationStateFieldItemList extends FieldItemList { /** * Gets the moderation state entity linked to a content entity revision. * - * @return \Drupal\content_moderation\ModerationStateInterface|null - * The moderation state configuration entity linked to a content entity - * revision. + * @return string|null + * The moderation state linked to a content entity revision. */ protected function getModerationState() { $entity = $this->getEntity(); - if (!\Drupal::service('content_moderation.moderation_information')->shouldModerateEntitiesOfBundle($entity->getEntityType(), $entity->bundle())) { + /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */ + $moderation_info = \Drupal::service('content_moderation.moderation_information'); + if (!$moderation_info->shouldModerateEntitiesOfBundle($entity->getEntityType(), $entity->bundle())) { return NULL; } @@ -51,17 +51,15 @@ protected function getModerationState() { $content_moderation_state = $content_moderation_state->getTranslation($langcode); } - return $content_moderation_state->get('moderation_state')->entity; + return $content_moderation_state->get('moderation_state')->value; } } // It is possible that the bundle does not exist at this point. For example, // the node type form creates a fake Node entity to get default values. // @see \Drupal\node\NodeTypeForm::form() - $bundle_entity = \Drupal::entityTypeManager() - ->getStorage($entity->getEntityType()->getBundleEntityType()) - ->load($entity->bundle()); - if ($bundle_entity && ($default = $bundle_entity->getThirdPartySetting('content_moderation', 'default_moderation_state'))) { - return ModerationState::load($default); + $workflow = $moderation_info->getWorkFlowForEntity($entity); + if ($workflow) { + return $workflow->getDefaultState($entity->getEntityTypeId(), $entity->bundle()); } } @@ -91,10 +89,11 @@ protected function computeModerationFieldItemList() { // Compute the value of the moderation state. $index = 0; if (!isset($this->list[$index]) || $this->list[$index]->isEmpty()) { + $moderation_state = $this->getModerationState(); // Do not store NULL values in the static cache. if ($moderation_state) { - $this->list[$index] = $this->createItem($index, ['entity' => $moderation_state]); + $this->list[$index] = $this->createItem($index, $moderation_state); } } } diff --git a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php index ca75604..d2c0a26 100644 --- a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php +++ b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php @@ -2,12 +2,12 @@ namespace Drupal\content_moderation\Plugin\Validation\Constraint; -use Drupal\content_moderation\Entity\ModerationState as ModerationStateEntity; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\content_moderation\ModerationInformationInterface; use Drupal\content_moderation\StateTransitionValidation; +use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -39,6 +39,12 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements protected $moderationInformation; /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + /** * Creates a new ModerationStateConstraintValidator instance. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager @@ -47,11 +53,14 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements * The state transition validation. * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_information * The moderation information. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, StateTransitionValidation $validation, ModerationInformationInterface $moderation_information) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, StateTransitionValidation $validation, ModerationInformationInterface $moderation_information, AccountInterface $current_user) { $this->validation = $validation; $this->entityTypeManager = $entity_type_manager; $this->moderationInformation = $moderation_information; + $this->currentUser = $current_user; } /** @@ -61,7 +70,8 @@ public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager'), $container->get('content_moderation.state_transition_validation'), - $container->get('content_moderation.moderation_information') + $container->get('content_moderation.moderation_information'), + $container->get('current_user') ); } @@ -93,21 +103,17 @@ public function validate($value, Constraint $constraint) { $original_entity = $original_entity->getTranslation($entity->language()->getId()); } - if ($entity->moderation_state->target_id) { - $new_state_id = $entity->moderation_state->target_id; + $workflow = $this->moderationInformation->getWorkFlowForEntity($entity); + if ($entity->moderation_state->state) { + $new_state = $entity->moderation_state->state; } else { - $new_state_id = $default = $this->entityTypeManager - ->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle()) - ->getThirdPartySetting('content_moderation', 'default_moderation_state'); - } - if ($new_state_id) { - $new_state = ModerationStateEntity::load($new_state_id); + $new_state = $workflow->getDefaultState($entity->getEntityTypeId(), $entity->bundle()); } // @todo - what if $new_state_id references something that does not exist or // is null. - if (!$this->validation->isTransitionAllowed($original_entity->moderation_state->entity, $new_state)) { - $this->context->addViolation($constraint->message, ['%from' => $original_entity->moderation_state->entity->label(), '%to' => $new_state->label()]); + if (!$this->validation->userMayTransition($original_entity, $new_state, $this->currentUser)) { + $this->context->addViolation($constraint->message, ['%from' => $workflow->getStateLabel($original_entity->moderation_state->state), '%to' => $workflow->getStateLabel($new_state)]); } } @@ -126,9 +132,9 @@ public function validate($value, Constraint $constraint) { protected function isFirstTimeModeration(EntityInterface $entity) { $original_entity = $this->moderationInformation->getLatestRevision($entity->getEntityTypeId(), $entity->id()); - $original_id = $original_entity->moderation_state->target_id; + $original_id = $original_entity->moderation_state; - return !($entity->moderation_state->target_id && $original_entity && $original_id); + return !($entity->moderation_state && $original_entity && $original_id); } } diff --git a/core/modules/content_moderation/src/StateTransitionValidation.php b/core/modules/content_moderation/src/StateTransitionValidation.php index 2e2a4e2..6107360 100644 --- a/core/modules/content_moderation/src/StateTransitionValidation.php +++ b/core/modules/content_moderation/src/StateTransitionValidation.php @@ -3,10 +3,7 @@ namespace Drupal\content_moderation; use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Session\AccountInterface; -use Drupal\content_moderation\Entity\ModerationStateTransition; /** * Validates whether a certain state transition is allowed. @@ -14,18 +11,11 @@ class StateTransitionValidation implements StateTransitionValidationInterface { /** - * Entity type manager. + * The moderation information service. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface + * @var \Drupal\content_moderation\ModerationInformationInterface */ - protected $entityTypeManager; - - /** - * Entity query factory. - * - * @var \Drupal\Core\Entity\Query\QueryFactory - */ - protected $queryFactory; + protected $moderationInfo; /** * Stores the possible state transitions. @@ -37,211 +27,44 @@ class StateTransitionValidation implements StateTransitionValidationInterface { /** * Constructs a new StateTransitionValidation. * - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager service. - * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory - * The entity query factory. + * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info + * The moderation information service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, QueryFactory $query_factory) { - $this->entityTypeManager = $entity_type_manager; - $this->queryFactory = $query_factory; - } - - /** - * Computes a mapping of possible transitions. - * - * This method is uncached and will recalculate the list on every request. - * In most cases you want to use getPossibleTransitions() instead. - * - * @see static::getPossibleTransitions() - * - * @return array[] - * An array containing all possible transitions. Each entry is keyed by the - * "from" state, and the value is an array of all legal "to" states based - * on the currently defined transition objects. - */ - protected function calculatePossibleTransitions() { - $transitions = $this->transitionStorage()->loadMultiple(); - - $possible_transitions = []; - /** @var \Drupal\content_moderation\ModerationStateTransitionInterface $transition */ - foreach ($transitions as $transition) { - $possible_transitions[$transition->getFromState()][] = $transition->getToState(); - } - return $possible_transitions; - } - - /** - * Returns a mapping of possible transitions. - * - * @return array[] - * An array containing all possible transitions. Each entry is keyed by the - * "from" state, and the value is an array of all legal "to" states based - * on the currently defined transition objects. - */ - protected function getPossibleTransitions() { - if (empty($this->possibleTransitions)) { - $this->possibleTransitions = $this->calculatePossibleTransitions(); - } - return $this->possibleTransitions; - } - - /** - * {@inheritdoc} - */ - public function getValidTransitionTargets(ContentEntityInterface $entity, AccountInterface $user) { - $bundle = $this->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle()); - - $states_for_bundle = $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', []); - - /** @var \Drupal\content_moderation\Entity\ModerationState $current_state */ - $current_state = $entity->moderation_state->entity; - - $all_transitions = $this->getPossibleTransitions(); - $destination_ids = $all_transitions[$current_state->id()]; - - $destination_ids = array_intersect($states_for_bundle, $destination_ids); - $destinations = $this->entityTypeManager->getStorage('moderation_state')->loadMultiple($destination_ids); - - return array_filter($destinations, function(ModerationStateInterface $destination_state) use ($current_state, $user) { - return $this->userMayTransition($current_state, $destination_state, $user); - }); + public function __construct(ModerationInformationInterface $moderation_info) { + $this->moderationInfo = $moderation_info; } /** * {@inheritdoc} */ public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user) { - $bundle = $this->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle()); + /** @var \Drupal\content_moderation\WorkflowInterface $workflow */ + $workflow = $this->moderationInfo->getWorkFlowForEntity($entity); - /** @var \Drupal\content_moderation\Entity\ModerationState $current_state */ - $current_state = $entity->moderation_state->entity; - $current_state_id = $current_state ? $current_state->id() : $bundle->getThirdPartySetting('content_moderation', 'default_moderation_state'); + $current_state = $entity->moderation_state->value; + $current_state = $current_state ?: $workflow->getDefaultState($entity->getEntityTypeId(), $entity->bundle()); - // Determine the states that are legal on this bundle. - $legal_bundle_states = $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', []); + $destinations = $workflow->getPossibleStates($current_state); - // Legal transitions include those that are possible from the current state, - // filtered by those whose target is legal on this bundle and that the - // user has access to execute. - $transitions = array_filter($this->getTransitionsFrom($current_state_id), function(ModerationStateTransition $transition) use ($legal_bundle_states, $user) { - return in_array($transition->getToState(), $legal_bundle_states, TRUE) - && $user->hasPermission('use ' . $transition->id() . ' transition'); + return array_filter($destinations, function($destination_state) use ($entity, $current_state, $user) { + return $this->userMayTransition($entity, $destination_state, $user); }); - - return $transitions; - } - - /** - * Returns a list of possible transitions from a given state. - * - * This list is based only on those transitions that exist, not what - * transitions are legal in a given context. - * - * @param string $state_name - * The machine name of the state from which we are transitioning. - * - * @return ModerationStateTransition[] - * A list of possible transitions from a given state. - */ - protected function getTransitionsFrom($state_name) { - $result = $this->transitionStateQuery() - ->condition('stateFrom', $state_name) - ->sort('weight') - ->execute(); - - return $this->transitionStorage()->loadMultiple($result); } /** * {@inheritdoc} */ - public function userMayTransition(ModerationStateInterface $from, ModerationStateInterface $to, AccountInterface $user) { - if ($transition = $this->getTransitionFromStates($from, $to)) { - return $user->hasPermission('use ' . $transition->id() . ' transition'); - } - return FALSE; - } - - /** - * Returns the transition object that transitions from one state to another. - * - * @param \Drupal\content_moderation\ModerationStateInterface $from - * The origin state. - * @param \Drupal\content_moderation\ModerationStateInterface $to - * The destination state. - * - * @return ModerationStateTransition|null - * A transition object, or NULL if there is no such transition. - */ - protected function getTransitionFromStates(ModerationStateInterface $from, ModerationStateInterface $to) { - $from = $this->transitionStateQuery() - ->condition('stateFrom', $from->id()) - ->condition('stateTo', $to->id()) - ->execute(); + public function userMayTransition(ContentEntityInterface $entity, $to, AccountInterface $user) { + /** @var \Drupal\content_moderation\WorkflowInterface $workflow */ + $workflow = $this->moderationInfo->getWorkFlowForEntity($entity); - $transitions = $this->transitionStorage()->loadMultiple($from); + $current_state = $entity->moderation_state->value; + $current_state = $current_state ?: $workflow->getDefaultState($entity->getEntityTypeId(), $entity->bundle()); - if ($transitions) { - return current($transitions); - } - return NULL; - } - - /** - * {@inheritdoc} - */ - public function isTransitionAllowed(ModerationStateInterface $from, ModerationStateInterface $to) { - $allowed_transitions = $this->calculatePossibleTransitions(); - if (isset($allowed_transitions[$from->id()])) { - return in_array($to->id(), $allowed_transitions[$from->id()], TRUE); + if ($workflow->canTranstion($current_state, $to)) { + return $user->hasPermission('use ' . $workflow->id() . ' transition from ' . $current_state . ' to ' . $to); } return FALSE; } - /** - * Returns a transition state entity query. - * - * @return \Drupal\Core\Entity\Query\QueryInterface - * A transition state entity query. - */ - protected function transitionStateQuery() { - return $this->queryFactory->get('moderation_state_transition', 'AND'); - } - - /** - * Returns the transition entity storage service. - * - * @return \Drupal\Core\Entity\EntityStorageInterface - * The transition state entity storage. - */ - protected function transitionStorage() { - return $this->entityTypeManager->getStorage('moderation_state_transition'); - } - - /** - * Returns the state entity storage service. - * - * @return \Drupal\Core\Entity\EntityStorageInterface - * The moderation state entity storage. - */ - protected function stateStorage() { - return $this->entityTypeManager->getStorage('moderation_state'); - } - - /** - * Loads a specific bundle entity. - * - * @param string $bundle_entity_type_id - * The bundle entity type ID. - * @param string $bundle_id - * The bundle ID. - * - * @return \Drupal\Core\Config\Entity\ConfigEntityInterface|null - * The specific bundle entity. - */ - protected function loadBundleEntity($bundle_entity_type_id, $bundle_id) { - return $this->entityTypeManager->getStorage($bundle_entity_type_id)->load($bundle_id); - } - } diff --git a/core/modules/content_moderation/src/StateTransitionValidationInterface.php b/core/modules/content_moderation/src/StateTransitionValidationInterface.php index 5ef0dd1..e09e994 100644 --- a/core/modules/content_moderation/src/StateTransitionValidationInterface.php +++ b/core/modules/content_moderation/src/StateTransitionValidationInterface.php @@ -11,20 +11,6 @@ interface StateTransitionValidationInterface { /** - * Gets a list of states a user may transition an entity to. - * - * @param \Drupal\Core\Entity\ContentEntityInterface $entity - * The entity to be transitioned. - * @param \Drupal\Core\Session\AccountInterface $user - * The account that wants to perform a transition. - * - * @return \Drupal\content_moderation\Entity\ModerationState[] - * Returns an array of States to which the specified user may transition the - * entity. - */ - public function getValidTransitionTargets(ContentEntityInterface $entity, AccountInterface $user); - - /** * Gets a list of transitions that are legal for this user on this entity. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity @@ -43,9 +29,9 @@ public function getValidTransitions(ContentEntityInterface $entity, AccountInter * This method will also return FALSE if there is no transition between the * specified states at all. * - * @param \Drupal\content_moderation\ModerationStateInterface $from - * The origin state. - * @param \Drupal\content_moderation\ModerationStateInterface $to + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity to check. + * @param string $to * The destination state. * @param \Drupal\Core\Session\AccountInterface $user * The user to validate. @@ -53,19 +39,6 @@ public function getValidTransitions(ContentEntityInterface $entity, AccountInter * @return bool * TRUE if the given user may transition between those two states. */ - public function userMayTransition(ModerationStateInterface $from, ModerationStateInterface $to, AccountInterface $user); - - /** - * Determines a transition allowed. - * - * @param \Drupal\content_moderation\ModerationStateInterface $from - * The origin state. - * @param \Drupal\content_moderation\ModerationStateInterface $to - * The destination state. - * - * @return bool - * Is the transition allowed. - */ - public function isTransitionAllowed(ModerationStateInterface $from, ModerationStateInterface $to); + public function userMayTransition(ContentEntityInterface $entity, $to, AccountInterface $user); } diff --git a/core/modules/content_moderation/src/WorkflowAccessControlHandler.php b/core/modules/content_moderation/src/WorkflowAccessControlHandler.php new file mode 100644 index 0000000..96630e7 --- /dev/null +++ b/core/modules/content_moderation/src/WorkflowAccessControlHandler.php @@ -0,0 +1,31 @@ +orIf($admin_access); + } + + return $admin_access; + } + +} diff --git a/core/modules/content_moderation/src/WorkflowInterface.php b/core/modules/content_moderation/src/WorkflowInterface.php new file mode 100644 index 0000000..371091a --- /dev/null +++ b/core/modules/content_moderation/src/WorkflowInterface.php @@ -0,0 +1,59 @@ +setupStateStorage(); - $state_transition_validation = new StateTransitionValidation($this->setupEntityTypeManager($storage), $this->setupQueryFactory()); - $this->assertTrue($state_transition_validation->isTransitionAllowed($storage->load($from_id), $storage->load($to_id))); - } - - /** - * Data provider for self::testIsTransitionAllowedWithValidTransition(). - */ - public function providerIsTransitionAllowedWithValidTransition() { - return [ - ['draft', 'draft'], - ['draft', 'needs_review'], - ['needs_review', 'needs_review'], - ['needs_review', 'staging'], - ['staging', 'published'], - ['needs_review', 'draft'], - ]; - } - - /** - * @covers ::isTransitionAllowed - * @covers ::calculatePossibleTransitions - * - * @dataProvider providerIsTransitionAllowedWithInValidTransition - */ - public function testIsTransitionAllowedWithInValidTransition($from_id, $to_id) { - $storage = $this->setupStateStorage(); - $state_transition_validation = new StateTransitionValidation($this->setupEntityTypeManager($storage), $this->setupQueryFactory()); - $this->assertFalse($state_transition_validation->isTransitionAllowed($storage->load($from_id), $storage->load($to_id))); - } - - /** - * Data provider for self::testIsTransitionAllowedWithInValidTransition(). - */ - public function providerIsTransitionAllowedWithInValidTransition() { - return [ - ['published', 'needs_review'], - ['published', 'staging'], - ['staging', 'needs_review'], - ['staging', 'staging'], - ['needs_review', 'published'], - ['published', 'archived'], - ['archived', 'published'], - ]; - } - - /** * Verifies user-aware transition validation. * * @param string $from_id