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;
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 Drupal\content_moderation\Entity\Routing\EntityModerationRouteProvider;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
*
* This class contains primarily bridged hooks for compile-time or
* cache-clear-time hooks. Runtime hooks should be placed in EntityOperations.
+ *
+ * @internal
*/
class EntityTypeInfo implements ContainerInjectionInterface {
*/
protected $currentUser;
+ /**
+ * The state transition validation service.
+ *
+ * @var \Drupal\content_moderation\StateTransitionValidationInterface
+ */
+ protected $validator;
+
/**
* A keyed array of custom moderation handlers for given entity types.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* Current user.
*/
- public function __construct(TranslationInterface $translation, ModerationInformationInterface $moderation_information, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info, AccountInterface $current_user) {
+ public function __construct(TranslationInterface $translation, ModerationInformationInterface $moderation_information, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info, AccountInterface $current_user, StateTransitionValidationInterface $validator) {
$this->stringTranslation = $translation;
$this->moderationInfo = $moderation_information;
$this->entityTypeManager = $entity_type_manager;
$this->bundleInfo = $bundle_info;
$this->currentUser = $current_user;
+ $this->validator = $validator;
}
/**
$container->get('content_moderation.moderation_information'),
$container->get('entity_type.manager'),
$container->get('entity_type.bundle.info'),
- $container->get('current_user')
+ $container->get('current_user'),
+ $container->get('content_moderation.state_transition_validation')
);
}
/**
* Adds Moderation configuration to appropriate entity types.
*
- * This is an alter hook bridge.
- *
- * @param EntityTypeInterface[] $entity_types
+ * @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types
* The master entity type list to alter.
*
* @see hook_entity_type_alter()
// 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()]);
- }
}
}
}
$type->setLinkTemplate('latest-version', $type->getLinkTemplate('canonical') . '/latest');
}
- // @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'] = EntityModerationRouteProvider::class;
return $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.
- *
- * @see hook_entity_extra_field_info()
- *
* @return array
* A nested array of 'pseudo-field' elements. Each list is nested within the
* following keys: entity type, bundle name, context (either 'form' or
* - delete: (optional) String containing markup (normally a link) used as
* the element's 'delete' operation in the administration interface. Only
* for 'form' context.
+ *
+ * @see hook_entity_extra_field_info()
*/
public function entityExtraFieldInfo() {
$return = [];
*
* @return \Drupal\Core\Field\BaseFieldDefinition[]
* New fields added by moderation state.
+ *
+ * @see hook_entity_base_field_info()
*/
public function entityBaseFieldInfo(EntityTypeInterface $entity_type) {
if (!$this->moderationInfo->canModerateEntitiesOfEntityType($entity_type)) {
->setDescription(t('The moderation state of this piece of content.'))
->setComputed(TRUE)
->setClass(ModerationStateFieldItemList::class)
- ->setSetting('target_type', 'moderation_state')
->setDisplayOptions('view', [
'label' => 'hidden',
'region' => 'hidden',
])
->setDisplayOptions('form', [
'type' => 'moderation_state_default',
- 'weight' => 5,
+ 'weight' => 100,
'settings' => [],
])
->addConstraint('ModerationState', [])
- ->setDisplayConfigurable('form', FALSE)
+ ->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', FALSE)
->setReadOnly(FALSE)
->setTranslatable(TRUE);
public function formAlter(array &$form, FormStateInterface $form_state, $form_id) {
$form_object = $form_state->getFormObject();
if ($form_object instanceof BundleEntityFormBase) {
- $type = $form_object->getEntity()->getEntityType();
- if ($this->moderationInfo->canModerateEntitiesOfEntityType($type)) {
- $this->entityTypeManager->getHandler($type->getBundleOf(), 'moderation')->enforceRevisionsBundleFormAlter($form, $form_state, $form_id);
+ $config_entity_type = $form_object->getEntity()->getEntityType();
+ $bundle_of = $config_entity_type->getBundleOf();
+ if ($bundle_of
+ && ($bundle_of_entity_type = $this->entityTypeManager->getDefinition($bundle_of))
+ && $this->moderationInfo->canModerateEntitiesOfEntityType($bundle_of_entity_type)) {
+ $this->entityTypeManager->getHandler($config_entity_type->getBundleOf(), 'moderation')->enforceRevisionsBundleFormAlter($form, $form_state, $form_id);
}
}
- elseif ($form_object instanceof ContentEntityFormInterface) {
+ elseif ($form_object instanceof ContentEntityFormInterface && in_array($form_object->getOperation(), ['edit', 'default'])) {
$entity = $form_object->getEntity();
if ($this->moderationInfo->isModeratedEntity($entity)) {
$this->entityTypeManager
->getHandler($entity->getEntityTypeId(), 'moderation')
->enforceRevisionsEntityFormAlter($form, $form_state, $form_id);
+
+ if (!$this->moderationInfo->isPendingRevisionAllowed($entity)) {
+ $latest_revision = $this->moderationInfo->getLatestRevision($entity->getEntityTypeId(), $entity->id());
+ if ($entity->bundle()) {
+ $bundle_type_id = $entity->getEntityType()->getBundleEntityType();
+ $bundle = $this->entityTypeManager->getStorage($bundle_type_id)->load($entity->bundle());
+ $type_label = $bundle->label();
+ }
+ else {
+ $type_label = $entity->getEntityType()->getLabel();
+ }
+
+ $translation = $this->moderationInfo->getAffectedRevisionTranslation($latest_revision);
+ $args = [
+ '@type_label' => $type_label,
+ '@latest_revision_edit_url' => $translation->toUrl('edit-form', ['language' => $translation->language()])->toString(),
+ '@latest_revision_delete_url' => $translation->toUrl('delete-form', ['language' => $translation->language()])->toString(),
+ ];
+ $label = $this->t('Unable to save this @type_label.', $args);
+ $message = $this->t('<a href="@latest_revision_edit_url">Publish</a> or <a href="@latest_revision_delete_url">delete</a> the latest revision to allow all workflow transitions.', $args);
+ $full_message = $this->t('Unable to save this @type_label. <a href="@latest_revision_edit_url">Publish</a> or <a href="@latest_revision_delete_url">delete</a> the latest revision to allow all workflow transitions.', $args);
+ drupal_set_message($full_message, 'error');
+
+ $form['moderation_state']['#access'] = FALSE;
+ $form['actions']['#access'] = FALSE;
+ $form['invalid_transitions'] = [
+ 'label' => [
+ '#type' => 'item',
+ '#prefix' => '<strong class="label">',
+ '#markup' => $label,
+ '#suffix' => '</strong>',
+ ],
+ 'message' => [
+ '#type' => 'item',
+ '#markup' => $message,
+ ],
+ '#weight' => 999,
+ '#no_valid_transitions' => TRUE,
+ ];
+
+ if ($form['footer']) {
+ $form['invalid_transitions']['#group'] = 'footer';
+ }
+ }
+
// Submit handler to redirect to the latest version, if available.
$form['actions']['submit']['#submit'][] = [EntityTypeInfo::class, 'bundleFormRedirect'];
+
+ // Move the 'moderation_state' field widget to the footer region, if
+ // available.
+ if (isset($form['footer'])) {
+ $form['moderation_state']['#group'] = 'footer';
+ }
+
+ // Duplicate the label of the current moderation state to the meta
+ // region, if available.
+ if (isset($form['meta']['published'])) {
+ $form['meta']['published']['#markup'] = $form['moderation_state']['widget'][0]['current']['#markup'];
+ }
}
}
}
/**
- * Redirect content entity edit forms on save, if there is a forward revision.
+ * Redirect content entity edit forms on save, if there is a pending revision.
*
* When saving their changes, editors should see those changes displayed on
* the next page.
$entity = $form_state->getFormObject()->getEntity();
$moderation_info = \Drupal::getContainer()->get('content_moderation.moderation_information');
- if ($moderation_info->hasForwardRevision($entity) && $entity->hasLinkTemplate('latest-version')) {
+ if ($moderation_info->hasPendingRevision($entity) && $entity->hasLinkTemplate('latest-version')) {
$entity_type_id = $entity->getEntityTypeId();
$form_state->setRedirect("entity.$entity_type_id.latest_version", [$entity_type_id => $entity->id()]);
}