Exception for Entities(Bundles) that are not under Moderation when calling Drupal::service('content_moderation.moderation_information')->getOriginalState($entity)

Created on 18 August 2021, about 3 years ago
Updated 15 June 2023, about 1 year ago

Problem/Motivation

On Drupal 9.x, a call to `::getOriginalState($entity)` ( Drupal::service('content_moderation.moderation_information')->getOriginalState($entity)) on an `Entity` that belongs to a Bundle not under moderation (so has no workflow) produces

The website encountered an unexpected error. Please try again later.
Error: Call to a member function getTypePlugin() on null in Drupal\content_moderation\ModerationInformation->getOriginalState() (line 216 of core/modules/content_moderation/src/ModerationInformation.php).

The reason for that is chained method accessors where one of the intermediate results may be NULL.

https://git.drupalcode.org/project/drupal/-/blob/9.3.x/core/modules/cont...

Steps to reproduce

- Create a New Node Content Type/Bundle and leave it out of Moderation/no workflow.
- Create a test case like this (snippet not complete code) and give `$bundle` the machine name of your new bundle.

   ....
   $entity = $this->entityTypeManager->getStorage('node')->create(array(
      'type'  => $bundle,
    ));
    $bundle_label = $entity->type->entity->label() ?? $bundle;
    if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
      /** @var \Drupal\content_moderation\ModerationInformation $moderationInformation */
      $moderationInformation = \Drupal::service('content_moderation.moderation_information');
      if ($moderationInformation->canModerateEntitiesOfEntityType($entity->getEntityType())  {
        $default = $moderationInformation->getOriginalState($entity);
     }

It will fail on the last line with the error described above

Proposed resolution

Instead of

/**
   * {@inheritdoc}
   */
  public function getOriginalState(ContentEntityInterface $entity) {
    $state = NULL;
    $workflow_type = $this->getWorkflowForEntity($entity)->getTypePlugin();
    if (!$entity->isNew() && !$this->isFirstTimeModeration($entity)) {
      /** @var \Drupal\Core\Entity\ContentEntityInterface $original_entity */
      $original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadRevision($entity->getLoadedRevisionId());
      if (!$entity->isDefaultTranslation() && $original_entity->hasTranslation($entity->language()->getId())) {
        $original_entity = $original_entity->getTranslation($entity->language()->getId());
      }
      if ($workflow_type->hasState($original_entity->moderation_state->value)) {
        $state = $workflow_type->getState($original_entity->moderation_state->value);
      }
    }
    return $state ?: $workflow_type->getInitialState($entity);
  }

This may be done (but needs an update to the Interface to allow this method to return `null` too)

/**
   * {@inheritdoc}
   */
  public function getOriginalState(ContentEntityInterface $entity) {
    $state = NULL;
    /** @var \Drupal\workflows\WorkflowInterface|null $workflow  */
    $workflow = $this->getWorkflowForEntity($entity);
    if (!$entity->isNew() && !$this->isFirstTimeModeration($entity) && $workflow) {
      $workflow_type = $workflow->getTypePlugin();
      /** @var \Drupal\Core\Entity\ContentEntityInterface $original_entity */
      $original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadRevision($entity->getLoadedRevisionId());
      if (!$entity->isDefaultTranslation() && $original_entity->hasTranslation($entity->language()->getId())) {
        $original_entity = $original_entity->getTranslation($entity->language()->getId());
      }
      if ($workflow_type->hasState($original_entity->moderation_state->value)) {
        $state = $workflow_type->getState($original_entity->moderation_state->value);
      }
      return $state ?: $workflow_type->getInitialState($entity);
    }
    return $state;
  }

Remaining tasks

Get some feedback. Make request. Write tests?

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

πŸ› Bug report
Status

Active

Version

9.5

Component
Content moderationΒ  β†’

Last updated 1 day ago

  • Maintained by
  • πŸ‡¦πŸ‡ΊAustralia @Sam152
Created by

πŸ‡ΊπŸ‡ΈUnited States DiegoPino

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡ΊπŸ‡ΈUnited States dww

    This came up for random daily triage from #bugsmash.

    Sounds like there's general agreement that throwing a more specific Exception in this case would be better. Quoting @acbramley:

    There's a isModeratedEntity function that can wrap that call so I agree we could just throw a more relevant exception

    That said, #4 makes some good points about the API changes and possible disruption of throwing an exception here. I'm still wrapping my own head around this issue, so I don't have an opinion myself. But at least tagging this to be smashed, and pasting some of the points from Slack in here...

  • πŸ‡¦πŸ‡ΊAustralia acbramley

    Thanks for copying that in @dww

    That said, #4 makes some good points about the API changes and possible disruption of throwing an exception here

    The behaviour at the moment is a more obscure exception as per the IS, so maybe this isn't as disruptive as other API changes? Instead of an obscure fatal we provide a nice Exception where we could mention wrapping the call in isModeratedEntity?

Production build 0.71.5 2024