3 namespace Drupal\content_moderation;
5 use Drupal\content_moderation\Entity\ContentModerationState as ContentModerationStateEntity;
6 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
7 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
8 use Drupal\Core\Entity\EntityInterface;
9 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
10 use Drupal\Core\Entity\EntityTypeManagerInterface;
11 use Drupal\Core\Form\FormBuilderInterface;
12 use Drupal\Core\TypedData\TranslatableInterface;
13 use Drupal\content_moderation\Form\EntityModerationForm;
14 use Drupal\workflows\WorkflowInterface;
15 use Symfony\Component\DependencyInjection\ContainerInterface;
18 * Defines a class for reacting to entity events.
20 class EntityOperations implements ContainerInjectionInterface {
23 * The Moderation Information service.
25 * @var \Drupal\content_moderation\ModerationInformationInterface
27 protected $moderationInfo;
30 * The Entity Type Manager service.
32 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
34 protected $entityTypeManager;
37 * The Form Builder service.
39 * @var \Drupal\Core\Form\FormBuilderInterface
41 protected $formBuilder;
44 * The Revision Tracker service.
46 * @var \Drupal\content_moderation\RevisionTrackerInterface
51 * The entity bundle information service.
53 * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
55 protected $bundleInfo;
58 * Constructs a new EntityOperations object.
60 * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
61 * Moderation information service.
62 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
63 * Entity type manager service.
64 * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
66 * @param \Drupal\content_moderation\RevisionTrackerInterface $tracker
67 * The revision tracker.
68 * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
69 * The entity bundle information service.
71 public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, RevisionTrackerInterface $tracker, EntityTypeBundleInfoInterface $bundle_info) {
72 $this->moderationInfo = $moderation_info;
73 $this->entityTypeManager = $entity_type_manager;
74 $this->formBuilder = $form_builder;
75 $this->tracker = $tracker;
76 $this->bundleInfo = $bundle_info;
82 public static function create(ContainerInterface $container) {
84 $container->get('content_moderation.moderation_information'),
85 $container->get('entity_type.manager'),
86 $container->get('form_builder'),
87 $container->get('content_moderation.revision_tracker'),
88 $container->get('entity_type.bundle.info')
93 * Acts on an entity and set published status based on the moderation state.
95 * @param \Drupal\Core\Entity\EntityInterface $entity
96 * The entity being saved.
98 public function entityPresave(EntityInterface $entity) {
99 if (!$this->moderationInfo->isModeratedEntity($entity)) {
103 if ($entity->moderation_state->value) {
104 $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
105 /** @var \Drupal\content_moderation\ContentModerationState $current_state */
106 $current_state = $workflow->getState($entity->moderation_state->value);
108 // This entity is default if it is new, a new translation, the default
109 // revision, or the default revision is not published.
110 $update_default_revision = $entity->isNew()
111 || $entity->isNewTranslation()
112 || $current_state->isDefaultRevisionState()
113 || !$this->isDefaultRevisionPublished($entity, $workflow);
115 // Fire per-entity-type logic for handling the save process.
116 $this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->onPresave($entity, $update_default_revision, $current_state->isPublishedState());
123 * @param \Drupal\Core\Entity\EntityInterface $entity
124 * The entity that was just saved.
126 * @see hook_entity_insert()
128 public function entityInsert(EntityInterface $entity) {
129 if ($this->moderationInfo->isModeratedEntity($entity)) {
130 $this->updateOrCreateFromEntity($entity);
131 $this->setLatestRevision($entity);
138 * @param \Drupal\Core\Entity\EntityInterface $entity
139 * The entity that was just saved.
141 * @see hook_entity_update()
143 public function entityUpdate(EntityInterface $entity) {
144 if ($this->moderationInfo->isModeratedEntity($entity)) {
145 $this->updateOrCreateFromEntity($entity);
146 $this->setLatestRevision($entity);
151 * Creates or updates the moderation state of an entity.
153 * @param \Drupal\Core\Entity\EntityInterface $entity
154 * The entity to update or create a moderation state for.
156 protected function updateOrCreateFromEntity(EntityInterface $entity) {
157 $moderation_state = $entity->moderation_state->value;
158 $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
159 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
160 if (!$moderation_state) {
161 $moderation_state = $workflow->getTypePlugin()->getInitialState($workflow, $entity)->id();
164 // @todo what if $entity->moderation_state is null at this point?
165 $entity_type_id = $entity->getEntityTypeId();
166 $entity_id = $entity->id();
167 $entity_revision_id = $entity->getRevisionId();
169 $storage = $this->entityTypeManager->getStorage('content_moderation_state');
170 $entities = $storage->loadByProperties([
171 'content_entity_type_id' => $entity_type_id,
172 'content_entity_id' => $entity_id,
173 'workflow' => $workflow->id(),
176 /** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */
177 $content_moderation_state = reset($entities);
178 if (!($content_moderation_state instanceof ContentModerationStateInterface)) {
179 $content_moderation_state = $storage->create([
180 'content_entity_type_id' => $entity_type_id,
181 'content_entity_id' => $entity_id,
183 $content_moderation_state->workflow->target_id = $workflow->id();
185 elseif ($content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
186 // If a new revision of the content has been created, add a new content
187 // moderation state revision.
188 $content_moderation_state->setNewRevision(TRUE);
191 // Sync translations.
192 if ($entity->getEntityType()->hasKey('langcode')) {
193 $entity_langcode = $entity->language()->getId();
194 if (!$content_moderation_state->hasTranslation($entity_langcode)) {
195 $content_moderation_state->addTranslation($entity_langcode);
197 if ($content_moderation_state->language()->getId() !== $entity_langcode) {
198 $content_moderation_state = $content_moderation_state->getTranslation($entity_langcode);
202 // Create the ContentModerationState entity for the inserted entity.
203 $content_moderation_state->set('content_entity_revision_id', $entity_revision_id);
204 $content_moderation_state->set('moderation_state', $moderation_state);
205 ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
209 * Set the latest revision.
211 * @param \Drupal\Core\Entity\EntityInterface $entity
212 * The content entity to create content_moderation_state entity for.
214 protected function setLatestRevision(EntityInterface $entity) {
215 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
216 $this->tracker->setLatestRevision(
217 $entity->getEntityTypeId(),
219 $entity->language()->getId(),
220 $entity->getRevisionId()
225 * Act on entities being assembled before rendering.
227 * This is a hook bridge.
229 * @see hook_entity_view()
230 * @see EntityFieldManagerInterface::getExtraFields()
232 public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
233 if (!$this->moderationInfo->isModeratedEntity($entity)) {
236 if (!$this->moderationInfo->isLatestRevision($entity)) {
239 if ($this->moderationInfo->isLiveRevision($entity)) {
243 $component = $display->getComponent('content_moderation_control');
245 $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
246 $build['content_moderation_control']['#weight'] = $component['weight'];
251 * Check if the default revision for the given entity is published.
253 * The default revision is the same as the entity retrieved by "default" from
254 * the storage handler. If the entity is translated, check if any of the
255 * translations are published.
257 * @param \Drupal\Core\Entity\EntityInterface $entity
258 * The entity being saved.
259 * @param \Drupal\workflows\WorkflowInterface $workflow
260 * The workflow being applied to the entity.
263 * TRUE if the default revision is published. FALSE otherwise.
265 protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowInterface $workflow) {
266 $default_revision = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->load($entity->id());
268 // Ensure we are checking all translations of the default revision.
269 if ($default_revision instanceof TranslatableInterface && $default_revision->isTranslatable()) {
270 // Loop through each language that has a translation.
271 foreach ($default_revision->getTranslationLanguages() as $language) {
272 // Load the translated revision.
273 $language_revision = $default_revision->getTranslation($language->getId());
274 // Return TRUE if a translation with a published state is found.
275 if ($workflow->getState($language_revision->moderation_state->value)->isPublishedState()) {
281 return $workflow->getState($default_revision->moderation_state->value)->isPublishedState();