Pull merge.
[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\content_moderation\Entity\ContentModerationStateInterface;
7 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
8 use Drupal\Core\Entity\ContentEntityInterface;
9 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
10 use Drupal\Core\Entity\EntityInterface;
11 use Drupal\Core\Entity\EntityPublishedInterface;
12 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
13 use Drupal\Core\Entity\EntityTypeManagerInterface;
14 use Drupal\Core\Form\FormBuilderInterface;
15 use Drupal\content_moderation\Form\EntityModerationForm;
16 use Drupal\Core\Routing\RouteBuilderInterface;
17 use Drupal\workflows\Entity\Workflow;
18 use Symfony\Component\DependencyInjection\ContainerInterface;
19
20 /**
21  * Defines a class for reacting to entity events.
22  *
23  * @internal
24  */
25 class EntityOperations implements ContainerInjectionInterface {
26
27   /**
28    * The Moderation Information service.
29    *
30    * @var \Drupal\content_moderation\ModerationInformationInterface
31    */
32   protected $moderationInfo;
33
34   /**
35    * The Entity Type Manager service.
36    *
37    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
38    */
39   protected $entityTypeManager;
40
41   /**
42    * The Form Builder service.
43    *
44    * @var \Drupal\Core\Form\FormBuilderInterface
45    */
46   protected $formBuilder;
47
48   /**
49    * The entity bundle information service.
50    *
51    * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
52    */
53   protected $bundleInfo;
54
55   /**
56    * The router builder service.
57    *
58    * @var \Drupal\Core\Routing\RouteBuilderInterface
59    */
60   protected $routerBuilder;
61
62   /**
63    * Constructs a new EntityOperations object.
64    *
65    * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
66    *   Moderation information service.
67    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
68    *   Entity type manager service.
69    * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
70    *   The form builder.
71    * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
72    *   The entity bundle information service.
73    * @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder
74    *   The router builder service.
75    */
76   public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, EntityTypeBundleInfoInterface $bundle_info, RouteBuilderInterface $router_builder) {
77     $this->moderationInfo = $moderation_info;
78     $this->entityTypeManager = $entity_type_manager;
79     $this->formBuilder = $form_builder;
80     $this->bundleInfo = $bundle_info;
81     $this->routerBuilder = $router_builder;
82   }
83
84   /**
85    * {@inheritdoc}
86    */
87   public static function create(ContainerInterface $container) {
88     return new static(
89       $container->get('content_moderation.moderation_information'),
90       $container->get('entity_type.manager'),
91       $container->get('form_builder'),
92       $container->get('entity_type.bundle.info'),
93       $container->get('router.builder')
94     );
95   }
96
97   /**
98    * Acts on an entity and set published status based on the moderation state.
99    *
100    * @param \Drupal\Core\Entity\EntityInterface $entity
101    *   The entity being saved.
102    *
103    * @see hook_entity_presave()
104    */
105   public function entityPresave(EntityInterface $entity) {
106     if (!$this->moderationInfo->isModeratedEntity($entity)) {
107       return;
108     }
109
110     if ($entity->moderation_state->value) {
111       $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
112       /** @var \Drupal\content_moderation\ContentModerationState $current_state */
113       $current_state = $workflow->getTypePlugin()
114         ->getState($entity->moderation_state->value);
115
116       // This entity is default if it is new, the default revision, or the
117       // default revision is not published.
118       $update_default_revision = $entity->isNew()
119         || $current_state->isDefaultRevisionState()
120         || !$this->moderationInfo->isDefaultRevisionPublished($entity);
121
122       // Fire per-entity-type logic for handling the save process.
123       $this->entityTypeManager
124         ->getHandler($entity->getEntityTypeId(), 'moderation')
125         ->onPresave($entity, $update_default_revision, $current_state->isPublishedState());
126     }
127   }
128
129   /**
130    * @param \Drupal\Core\Entity\EntityInterface $entity
131    *   The entity that was just saved.
132    *
133    * @see hook_entity_insert()
134    */
135   public function entityInsert(EntityInterface $entity) {
136     if ($this->moderationInfo->isModeratedEntity($entity)) {
137       $this->updateOrCreateFromEntity($entity);
138     }
139   }
140
141   /**
142    * @param \Drupal\Core\Entity\EntityInterface $entity
143    *   The entity that was just saved.
144    *
145    * @see hook_entity_update()
146    */
147   public function entityUpdate(EntityInterface $entity) {
148     if ($this->moderationInfo->isModeratedEntity($entity)) {
149       $this->updateOrCreateFromEntity($entity);
150     }
151     // When updating workflow settings for Content Moderation, we need to
152     // rebuild routes as we may be enabling new entity types and the related
153     // entity forms.
154     elseif ($entity instanceof Workflow && $entity->getTypePlugin()->getPluginId() == 'content_moderation') {
155       $this->routerBuilder->setRebuildNeeded();
156     }
157   }
158
159   /**
160    * Creates or updates the moderation state of an entity.
161    *
162    * @param \Drupal\Core\Entity\EntityInterface $entity
163    *   The entity to update or create a moderation state for.
164    */
165   protected function updateOrCreateFromEntity(EntityInterface $entity) {
166     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
167     $entity_revision_id = $entity->getRevisionId();
168     $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
169     $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
170     /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
171     $storage = $this->entityTypeManager->getStorage('content_moderation_state');
172
173     if (!($content_moderation_state instanceof ContentModerationStateInterface)) {
174       $content_moderation_state = $storage->create([
175         'content_entity_type_id' => $entity->getEntityTypeId(),
176         'content_entity_id' => $entity->id(),
177         // Make sure that the moderation state entity has the same language code
178         // as the moderated entity.
179         'langcode' => $entity->language()->getId(),
180       ]);
181       $content_moderation_state->workflow->target_id = $workflow->id();
182     }
183
184     // Sync translations.
185     if ($entity->getEntityType()->hasKey('langcode')) {
186       $entity_langcode = $entity->language()->getId();
187       if ($entity->isDefaultTranslation()) {
188         $content_moderation_state->langcode = $entity_langcode;
189       }
190       else {
191         if (!$content_moderation_state->hasTranslation($entity_langcode)) {
192           $content_moderation_state->addTranslation($entity_langcode);
193         }
194         if ($content_moderation_state->language()->getId() !== $entity_langcode) {
195           $content_moderation_state = $content_moderation_state->getTranslation($entity_langcode);
196         }
197       }
198     }
199
200     // If a new revision of the content has been created, add a new content
201     // moderation state revision.
202     if (!$content_moderation_state->isNew() && $content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
203       $content_moderation_state = $storage->createRevision($content_moderation_state, $entity->isDefaultRevision());
204     }
205
206     // Create the ContentModerationState entity for the inserted entity.
207     $moderation_state = $entity->moderation_state->value;
208     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
209     if (!$moderation_state) {
210       $moderation_state = $workflow->getTypePlugin()->getInitialState($entity)->id();
211     }
212
213     $content_moderation_state->set('content_entity_revision_id', $entity_revision_id);
214     $content_moderation_state->set('moderation_state', $moderation_state);
215     ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
216   }
217
218   /**
219    * @param \Drupal\Core\Entity\EntityInterface $entity
220    *   The entity being deleted.
221    *
222    * @see hook_entity_delete()
223    */
224   public function entityDelete(EntityInterface $entity) {
225     $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
226     if ($content_moderation_state) {
227       $content_moderation_state->delete();
228     }
229   }
230
231   /**
232    * @param \Drupal\Core\Entity\EntityInterface $entity
233    *   The entity revision being deleted.
234    *
235    * @see hook_entity_revision_delete()
236    */
237   public function entityRevisionDelete(EntityInterface $entity) {
238     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
239     if (!$entity->isDefaultRevision()) {
240       $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
241       if ($content_moderation_state) {
242         $this->entityTypeManager
243           ->getStorage('content_moderation_state')
244           ->deleteRevision($content_moderation_state->getRevisionId());
245       }
246     }
247   }
248
249   /**
250    * @param \Drupal\Core\Entity\EntityInterface $translation
251    *   The entity translation being deleted.
252    *
253    * @see hook_entity_translation_delete()
254    */
255   public function entityTranslationDelete(EntityInterface $translation) {
256     /** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
257     if (!$translation->isDefaultTranslation()) {
258       $langcode = $translation->language()->getId();
259       $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($translation);
260       if ($content_moderation_state && $content_moderation_state->hasTranslation($langcode)) {
261         $content_moderation_state->removeTranslation($langcode);
262         ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
263       }
264     }
265   }
266
267   /**
268    * Act on entities being assembled before rendering.
269    *
270    * @see hook_entity_view()
271    * @see EntityFieldManagerInterface::getExtraFields()
272    */
273   public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
274     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
275     if (!$this->moderationInfo->isModeratedEntity($entity)) {
276       return;
277     }
278     if (isset($entity->in_preview) && $entity->in_preview) {
279       return;
280     }
281     // If the component is not defined for this display, we have nothing to do.
282     if (!$display->getComponent('content_moderation_control')) {
283       return;
284     }
285     // The moderation form should be displayed only when viewing the latest
286     // (translation-affecting) revision, unless it was created as published
287     // default revision.
288     if (($entity->isDefaultRevision() || $entity->wasDefaultRevision()) && $this->isPublished($entity)) {
289       return;
290     }
291     if (!$entity->isLatestRevision() && !$entity->isLatestTranslationAffectedRevision()) {
292       return;
293     }
294
295     $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
296   }
297
298   /**
299    * Checks if the entity is published.
300    *
301    * This method is optimized to not have to unnecessarily load the moderation
302    * state and workflow if it is not required.
303    *
304    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
305    *   The entity to check.
306    *
307    * @return bool
308    *   TRUE if the entity is published, FALSE otherwise.
309    */
310   protected function isPublished(ContentEntityInterface $entity) {
311     // If the entity implements EntityPublishedInterface directly, check that
312     // first, otherwise fall back to check through the workflow state.
313     if ($entity instanceof EntityPublishedInterface) {
314       return $entity->isPublished();
315     }
316     if ($moderation_state = $entity->get('moderation_state')->value) {
317       $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
318       return $workflow->getTypePlugin()->getState($moderation_state)->isPublishedState();
319     }
320     return FALSE;
321   }
322
323 }