Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / content_moderation / src / EntityTypeInfo.php
index f11e4716859f11f838d504e3e8bdff4cb194b8e2..666ee2647ff59518f6b6dd1aaa44ecb0d744cd46 100644 (file)
@@ -3,12 +3,10 @@
 namespace Drupal\content_moderation;
 
 use Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList;
-use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
 use Drupal\Core\Entity\BundleEntityFormBase;
 use Drupal\Core\Entity\ContentEntityFormInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -17,13 +15,10 @@ use Drupal\Core\Form\FormStateInterface;
 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;
 
 /**
@@ -31,6 +26,8 @@ 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 {
 
@@ -64,6 +61,13 @@ 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.
    *
@@ -90,12 +94,13 @@ class EntityTypeInfo implements ContainerInjectionInterface {
    * @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;
   }
 
   /**
@@ -107,7 +112,8 @@ class EntityTypeInfo implements ContainerInjectionInterface {
       $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')
     );
   }
 
@@ -115,9 +121,7 @@ class EntityTypeInfo implements ContainerInjectionInterface {
   /**
    * 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()
@@ -127,11 +131,6 @@ class EntityTypeInfo implements ContainerInjectionInterface {
       // 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()]);
-        }
       }
     }
   }
@@ -158,8 +157,6 @@ class EntityTypeInfo implements ContainerInjectionInterface {
       $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;
@@ -169,74 +166,9 @@ class EntityTypeInfo implements ContainerInjectionInterface {
     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
@@ -255,6 +187,8 @@ class EntityTypeInfo implements ContainerInjectionInterface {
    *   - 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 = [];
@@ -301,6 +235,8 @@ class EntityTypeInfo implements ContainerInjectionInterface {
    *
    * @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)) {
@@ -313,7 +249,6 @@ class EntityTypeInfo implements ContainerInjectionInterface {
       ->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',
@@ -321,11 +256,11 @@ class EntityTypeInfo implements ContainerInjectionInterface {
       ])
       ->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);
@@ -348,25 +283,85 @@ class EntityTypeInfo implements ContainerInjectionInterface {
   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.
@@ -381,7 +376,7 @@ class EntityTypeInfo implements ContainerInjectionInterface {
     $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()]);
     }