Version 1
[yaffs-website] / web / core / modules / content_moderation / src / EntityOperations.php
1 <?php
2
3 namespace Drupal\content_moderation;
4
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;
16
17 /**
18  * Defines a class for reacting to entity events.
19  */
20 class EntityOperations implements ContainerInjectionInterface {
21
22   /**
23    * The Moderation Information service.
24    *
25    * @var \Drupal\content_moderation\ModerationInformationInterface
26    */
27   protected $moderationInfo;
28
29   /**
30    * The Entity Type Manager service.
31    *
32    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
33    */
34   protected $entityTypeManager;
35
36   /**
37    * The Form Builder service.
38    *
39    * @var \Drupal\Core\Form\FormBuilderInterface
40    */
41   protected $formBuilder;
42
43   /**
44    * The Revision Tracker service.
45    *
46    * @var \Drupal\content_moderation\RevisionTrackerInterface
47    */
48   protected $tracker;
49
50   /**
51    * The entity bundle information service.
52    *
53    * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
54    */
55   protected $bundleInfo;
56
57   /**
58    * Constructs a new EntityOperations object.
59    *
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
65    *   The 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.
70    */
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;
77   }
78
79   /**
80    * {@inheritdoc}
81    */
82   public static function create(ContainerInterface $container) {
83     return new static(
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')
89     );
90   }
91
92   /**
93    * Acts on an entity and set published status based on the moderation state.
94    *
95    * @param \Drupal\Core\Entity\EntityInterface $entity
96    *   The entity being saved.
97    */
98   public function entityPresave(EntityInterface $entity) {
99     if (!$this->moderationInfo->isModeratedEntity($entity)) {
100       return;
101     }
102
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);
107
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);
114
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());
117     }
118   }
119
120   /**
121    * Hook bridge.
122    *
123    * @param \Drupal\Core\Entity\EntityInterface $entity
124    *   The entity that was just saved.
125    *
126    * @see hook_entity_insert()
127    */
128   public function entityInsert(EntityInterface $entity) {
129     if ($this->moderationInfo->isModeratedEntity($entity)) {
130       $this->updateOrCreateFromEntity($entity);
131       $this->setLatestRevision($entity);
132     }
133   }
134
135   /**
136    * Hook bridge.
137    *
138    * @param \Drupal\Core\Entity\EntityInterface $entity
139    *   The entity that was just saved.
140    *
141    * @see hook_entity_update()
142    */
143   public function entityUpdate(EntityInterface $entity) {
144     if ($this->moderationInfo->isModeratedEntity($entity)) {
145       $this->updateOrCreateFromEntity($entity);
146       $this->setLatestRevision($entity);
147     }
148   }
149
150   /**
151    * Creates or updates the moderation state of an entity.
152    *
153    * @param \Drupal\Core\Entity\EntityInterface $entity
154    *   The entity to update or create a moderation state for.
155    */
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();
162     }
163
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();
168
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(),
174     ]);
175
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,
182       ]);
183       $content_moderation_state->workflow->target_id = $workflow->id();
184     }
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);
189     }
190
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);
196       }
197       if ($content_moderation_state->language()->getId() !== $entity_langcode) {
198         $content_moderation_state = $content_moderation_state->getTranslation($entity_langcode);
199       }
200     }
201
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);
206   }
207
208   /**
209    * Set the latest revision.
210    *
211    * @param \Drupal\Core\Entity\EntityInterface $entity
212    *   The content entity to create content_moderation_state entity for.
213    */
214   protected function setLatestRevision(EntityInterface $entity) {
215     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
216     $this->tracker->setLatestRevision(
217       $entity->getEntityTypeId(),
218       $entity->id(),
219       $entity->language()->getId(),
220       $entity->getRevisionId()
221     );
222   }
223
224   /**
225    * Act on entities being assembled before rendering.
226    *
227    * This is a hook bridge.
228    *
229    * @see hook_entity_view()
230    * @see EntityFieldManagerInterface::getExtraFields()
231    */
232   public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
233     if (!$this->moderationInfo->isModeratedEntity($entity)) {
234       return;
235     }
236     if (!$this->moderationInfo->isLatestRevision($entity)) {
237       return;
238     }
239     if ($this->moderationInfo->isLiveRevision($entity)) {
240       return;
241     }
242
243     $component = $display->getComponent('content_moderation_control');
244     if ($component) {
245       $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
246       $build['content_moderation_control']['#weight'] = $component['weight'];
247     }
248   }
249
250   /**
251    * Check if the default revision for the given entity is published.
252    *
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.
256    *
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.
261    *
262    * @return bool
263    *   TRUE if the default revision is published. FALSE otherwise.
264    */
265   protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowInterface $workflow) {
266     $default_revision = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->load($entity->id());
267
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()) {
276           return TRUE;
277         }
278       }
279     }
280
281     return $workflow->getState($default_revision->moderation_state->value)->isPublishedState();
282   }
283
284 }