namespace Drupal\content_moderation;
use Drupal\content_moderation\Entity\ContentModerationState as ContentModerationStateEntity;
+use Drupal\content_moderation\Entity\ContentModerationStateInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBuilderInterface;
-use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\content_moderation\Form\EntityModerationForm;
-use Drupal\workflows\WorkflowInterface;
+use Drupal\Core\Routing\RouteBuilderInterface;
+use Drupal\workflows\Entity\Workflow;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a class for reacting to entity events.
+ *
+ * @internal
*/
class EntityOperations implements ContainerInjectionInterface {
protected $formBuilder;
/**
- * The Revision Tracker service.
+ * The entity bundle information service.
*
- * @var \Drupal\content_moderation\RevisionTrackerInterface
+ * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
- protected $tracker;
+ protected $bundleInfo;
/**
- * The entity bundle information service.
+ * The router builder service.
*
- * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+ * @var \Drupal\Core\Routing\RouteBuilderInterface
*/
- protected $bundleInfo;
+ protected $routerBuilder;
/**
* Constructs a new EntityOperations object.
* Entity type manager service.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
- * @param \Drupal\content_moderation\RevisionTrackerInterface $tracker
- * The revision tracker.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
* The entity bundle information service.
+ * @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder
+ * The router builder service.
*/
- public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, RevisionTrackerInterface $tracker, EntityTypeBundleInfoInterface $bundle_info) {
+ public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, EntityTypeBundleInfoInterface $bundle_info, RouteBuilderInterface $router_builder) {
$this->moderationInfo = $moderation_info;
$this->entityTypeManager = $entity_type_manager;
$this->formBuilder = $form_builder;
- $this->tracker = $tracker;
$this->bundleInfo = $bundle_info;
+ $this->routerBuilder = $router_builder;
}
/**
$container->get('content_moderation.moderation_information'),
$container->get('entity_type.manager'),
$container->get('form_builder'),
- $container->get('content_moderation.revision_tracker'),
- $container->get('entity_type.bundle.info')
+ $container->get('entity_type.bundle.info'),
+ $container->get('router.builder')
);
}
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being saved.
+ *
+ * @see hook_entity_presave()
*/
public function entityPresave(EntityInterface $entity) {
if (!$this->moderationInfo->isModeratedEntity($entity)) {
if ($entity->moderation_state->value) {
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
/** @var \Drupal\content_moderation\ContentModerationState $current_state */
- $current_state = $workflow->getState($entity->moderation_state->value);
+ $current_state = $workflow->getTypePlugin()
+ ->getState($entity->moderation_state->value);
- // This entity is default if it is new, a new translation, the default
- // revision, or the default revision is not published.
+ // This entity is default if it is new, the default revision, or the
+ // default revision is not published.
$update_default_revision = $entity->isNew()
- || $entity->isNewTranslation()
|| $current_state->isDefaultRevisionState()
- || !$this->isDefaultRevisionPublished($entity, $workflow);
+ || !$this->moderationInfo->isDefaultRevisionPublished($entity);
// Fire per-entity-type logic for handling the save process.
- $this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->onPresave($entity, $update_default_revision, $current_state->isPublishedState());
+ $this->entityTypeManager
+ ->getHandler($entity->getEntityTypeId(), 'moderation')
+ ->onPresave($entity, $update_default_revision, $current_state->isPublishedState());
}
}
/**
- * Hook bridge.
- *
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that was just saved.
*
public function entityInsert(EntityInterface $entity) {
if ($this->moderationInfo->isModeratedEntity($entity)) {
$this->updateOrCreateFromEntity($entity);
- $this->setLatestRevision($entity);
}
}
/**
- * Hook bridge.
- *
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that was just saved.
*
public function entityUpdate(EntityInterface $entity) {
if ($this->moderationInfo->isModeratedEntity($entity)) {
$this->updateOrCreateFromEntity($entity);
- $this->setLatestRevision($entity);
+ }
+ // When updating workflow settings for Content Moderation, we need to
+ // rebuild routes as we may be enabling new entity types and the related
+ // entity forms.
+ elseif ($entity instanceof Workflow && $entity->getTypePlugin()->getPluginId() == 'content_moderation') {
+ $this->routerBuilder->setRebuildNeeded();
}
}
* The entity to update or create a moderation state for.
*/
protected function updateOrCreateFromEntity(EntityInterface $entity) {
- $moderation_state = $entity->moderation_state->value;
- $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
- if (!$moderation_state) {
- $moderation_state = $workflow->getTypePlugin()->getInitialState($workflow, $entity)->id();
- }
-
- // @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();
-
+ $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
+ $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
+ /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = $this->entityTypeManager->getStorage('content_moderation_state');
- $entities = $storage->loadByProperties([
- 'content_entity_type_id' => $entity_type_id,
- 'content_entity_id' => $entity_id,
- 'workflow' => $workflow->id(),
- ]);
- /** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */
- $content_moderation_state = reset($entities);
if (!($content_moderation_state instanceof ContentModerationStateInterface)) {
$content_moderation_state = $storage->create([
- 'content_entity_type_id' => $entity_type_id,
- 'content_entity_id' => $entity_id,
+ 'content_entity_type_id' => $entity->getEntityTypeId(),
+ 'content_entity_id' => $entity->id(),
+ // Make sure that the moderation state entity has the same language code
+ // as the moderated entity.
+ 'langcode' => $entity->language()->getId(),
]);
$content_moderation_state->workflow->target_id = $workflow->id();
}
- elseif ($content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
- // If a new revision of the content has been created, add a new content
- // moderation state revision.
- $content_moderation_state->setNewRevision(TRUE);
- }
// Sync translations.
if ($entity->getEntityType()->hasKey('langcode')) {
$entity_langcode = $entity->language()->getId();
- if (!$content_moderation_state->hasTranslation($entity_langcode)) {
- $content_moderation_state->addTranslation($entity_langcode);
+ if ($entity->isDefaultTranslation()) {
+ $content_moderation_state->langcode = $entity_langcode;
}
- if ($content_moderation_state->language()->getId() !== $entity_langcode) {
- $content_moderation_state = $content_moderation_state->getTranslation($entity_langcode);
+ else {
+ if (!$content_moderation_state->hasTranslation($entity_langcode)) {
+ $content_moderation_state->addTranslation($entity_langcode);
+ }
+ if ($content_moderation_state->language()->getId() !== $entity_langcode) {
+ $content_moderation_state = $content_moderation_state->getTranslation($entity_langcode);
+ }
}
}
+ // If a new revision of the content has been created, add a new content
+ // moderation state revision.
+ if (!$content_moderation_state->isNew() && $content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
+ $content_moderation_state = $storage->createRevision($content_moderation_state, $entity->isDefaultRevision());
+ }
+
// Create the ContentModerationState entity for the inserted entity.
+ $moderation_state = $entity->moderation_state->value;
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+ if (!$moderation_state) {
+ $moderation_state = $workflow->getTypePlugin()->getInitialState($entity)->id();
+ }
+
$content_moderation_state->set('content_entity_revision_id', $entity_revision_id);
$content_moderation_state->set('moderation_state', $moderation_state);
ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
}
/**
- * Set the latest revision.
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * The entity being deleted.
*
+ * @see hook_entity_delete()
+ */
+ public function entityDelete(EntityInterface $entity) {
+ $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
+ if ($content_moderation_state) {
+ $content_moderation_state->delete();
+ }
+ }
+
+ /**
* @param \Drupal\Core\Entity\EntityInterface $entity
- * The content entity to create content_moderation_state entity for.
+ * The entity revision being deleted.
+ *
+ * @see hook_entity_revision_delete()
*/
- protected function setLatestRevision(EntityInterface $entity) {
+ public function entityRevisionDelete(EntityInterface $entity) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
- $this->tracker->setLatestRevision(
- $entity->getEntityTypeId(),
- $entity->id(),
- $entity->language()->getId(),
- $entity->getRevisionId()
- );
+ if (!$entity->isDefaultRevision()) {
+ $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
+ if ($content_moderation_state) {
+ $this->entityTypeManager
+ ->getStorage('content_moderation_state')
+ ->deleteRevision($content_moderation_state->getRevisionId());
+ }
+ }
}
/**
- * Act on entities being assembled before rendering.
+ * @param \Drupal\Core\Entity\EntityInterface $translation
+ * The entity translation being deleted.
*
- * This is a hook bridge.
+ * @see hook_entity_translation_delete()
+ */
+ public function entityTranslationDelete(EntityInterface $translation) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
+ if (!$translation->isDefaultTranslation()) {
+ $langcode = $translation->language()->getId();
+ $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($translation);
+ if ($content_moderation_state && $content_moderation_state->hasTranslation($langcode)) {
+ $content_moderation_state->removeTranslation($langcode);
+ ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
+ }
+ }
+ }
+
+ /**
+ * Act on entities being assembled before rendering.
*
* @see hook_entity_view()
* @see EntityFieldManagerInterface::getExtraFields()
*/
public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
if (!$this->moderationInfo->isModeratedEntity($entity)) {
return;
}
- if (!$this->moderationInfo->isLatestRevision($entity)) {
+ if (isset($entity->in_preview) && $entity->in_preview) {
return;
}
- if ($this->moderationInfo->isLiveRevision($entity)) {
+ // If the component is not defined for this display, we have nothing to do.
+ if (!$display->getComponent('content_moderation_control')) {
return;
}
-
- $component = $display->getComponent('content_moderation_control');
- if ($component) {
- $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
- $build['content_moderation_control']['#weight'] = $component['weight'];
+ // The moderation form should be displayed only when viewing the latest
+ // (translation-affecting) revision, unless it was created as published
+ // default revision.
+ if (($entity->isDefaultRevision() || $entity->wasDefaultRevision()) && $this->isPublished($entity)) {
+ return;
}
+ if (!$entity->isLatestRevision() && !$entity->isLatestTranslationAffectedRevision()) {
+ return;
+ }
+
+ $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
}
/**
- * Check if the default revision for the given entity is published.
+ * Checks if the entity is published.
*
- * The default revision is the same as the entity retrieved by "default" from
- * the storage handler. If the entity is translated, check if any of the
- * translations are published.
+ * This method is optimized to not have to unnecessarily load the moderation
+ * state and workflow if it is not required.
*
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * The entity being saved.
- * @param \Drupal\workflows\WorkflowInterface $workflow
- * The workflow being applied to the entity.
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity to check.
*
* @return bool
- * TRUE if the default revision is published. FALSE otherwise.
+ * TRUE if the entity is published, FALSE otherwise.
*/
- protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowInterface $workflow) {
- $default_revision = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->load($entity->id());
-
- // Ensure we are checking all translations of the default revision.
- if ($default_revision instanceof TranslatableInterface && $default_revision->isTranslatable()) {
- // Loop through each language that has a translation.
- foreach ($default_revision->getTranslationLanguages() as $language) {
- // Load the translated revision.
- $language_revision = $default_revision->getTranslation($language->getId());
- // Return TRUE if a translation with a published state is found.
- if ($workflow->getState($language_revision->moderation_state->value)->isPublishedState()) {
- return TRUE;
- }
- }
+ protected function isPublished(ContentEntityInterface $entity) {
+ // If the entity implements EntityPublishedInterface directly, check that
+ // first, otherwise fall back to check through the workflow state.
+ if ($entity instanceof EntityPublishedInterface) {
+ return $entity->isPublished();
}
-
- return $workflow->getState($default_revision->moderation_state->value)->isPublishedState();
+ if ($moderation_state = $entity->get('moderation_state')->value) {
+ $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
+ return $workflow->getTypePlugin()->getState($moderation_state)->isPublishedState();
+ }
+ return FALSE;
}
}