3 namespace Drupal\content_moderation;
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;
21 * Defines a class for reacting to entity events.
25 class EntityOperations implements ContainerInjectionInterface {
28 * The Moderation Information service.
30 * @var \Drupal\content_moderation\ModerationInformationInterface
32 protected $moderationInfo;
35 * The Entity Type Manager service.
37 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
39 protected $entityTypeManager;
42 * The Form Builder service.
44 * @var \Drupal\Core\Form\FormBuilderInterface
46 protected $formBuilder;
49 * The entity bundle information service.
51 * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
53 protected $bundleInfo;
56 * The router builder service.
58 * @var \Drupal\Core\Routing\RouteBuilderInterface
60 protected $routerBuilder;
63 * Constructs a new EntityOperations object.
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
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.
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;
87 public static function create(ContainerInterface $container) {
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')
98 * Acts on an entity and set published status based on the moderation state.
100 * @param \Drupal\Core\Entity\EntityInterface $entity
101 * The entity being saved.
103 * @see hook_entity_presave()
105 public function entityPresave(EntityInterface $entity) {
106 if (!$this->moderationInfo->isModeratedEntity($entity)) {
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);
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);
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());
130 * @param \Drupal\Core\Entity\EntityInterface $entity
131 * The entity that was just saved.
133 * @see hook_entity_insert()
135 public function entityInsert(EntityInterface $entity) {
136 if ($this->moderationInfo->isModeratedEntity($entity)) {
137 $this->updateOrCreateFromEntity($entity);
142 * @param \Drupal\Core\Entity\EntityInterface $entity
143 * The entity that was just saved.
145 * @see hook_entity_update()
147 public function entityUpdate(EntityInterface $entity) {
148 if ($this->moderationInfo->isModeratedEntity($entity)) {
149 $this->updateOrCreateFromEntity($entity);
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
154 elseif ($entity instanceof Workflow && $entity->getTypePlugin()->getPluginId() == 'content_moderation') {
155 $this->routerBuilder->setRebuildNeeded();
160 * Creates or updates the moderation state of an entity.
162 * @param \Drupal\Core\Entity\EntityInterface $entity
163 * The entity to update or create a moderation state for.
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');
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(),
181 $content_moderation_state->workflow->target_id = $workflow->id();
184 // Sync translations.
185 if ($entity->getEntityType()->hasKey('langcode')) {
186 $entity_langcode = $entity->language()->getId();
187 if (!$content_moderation_state->hasTranslation($entity_langcode)) {
188 $content_moderation_state->addTranslation($entity_langcode);
190 if ($content_moderation_state->language()->getId() !== $entity_langcode) {
191 $content_moderation_state = $content_moderation_state->getTranslation($entity_langcode);
195 // If a new revision of the content has been created, add a new content
196 // moderation state revision.
197 if (!$content_moderation_state->isNew() && $content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
198 $content_moderation_state = $storage->createRevision($content_moderation_state, $entity->isDefaultRevision());
201 // Create the ContentModerationState entity for the inserted entity.
202 $moderation_state = $entity->moderation_state->value;
203 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
204 if (!$moderation_state) {
205 $moderation_state = $workflow->getTypePlugin()->getInitialState($entity)->id();
208 $content_moderation_state->set('content_entity_revision_id', $entity_revision_id);
209 $content_moderation_state->set('moderation_state', $moderation_state);
210 ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
214 * @param \Drupal\Core\Entity\EntityInterface $entity
215 * The entity being deleted.
217 * @see hook_entity_delete()
219 public function entityDelete(EntityInterface $entity) {
220 $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
221 if ($content_moderation_state) {
222 $content_moderation_state->delete();
227 * @param \Drupal\Core\Entity\EntityInterface $entity
228 * The entity revision being deleted.
230 * @see hook_entity_revision_delete()
232 public function entityRevisionDelete(EntityInterface $entity) {
233 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
234 if (!$entity->isDefaultRevision()) {
235 $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
236 if ($content_moderation_state) {
237 $this->entityTypeManager
238 ->getStorage('content_moderation_state')
239 ->deleteRevision($content_moderation_state->getRevisionId());
245 * @param \Drupal\Core\Entity\EntityInterface $translation
246 * The entity translation being deleted.
248 * @see hook_entity_translation_delete()
250 public function entityTranslationDelete(EntityInterface $translation) {
251 /** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
252 if (!$translation->isDefaultTranslation()) {
253 $langcode = $translation->language()->getId();
254 $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($translation);
255 if ($content_moderation_state && $content_moderation_state->hasTranslation($langcode)) {
256 $content_moderation_state->removeTranslation($langcode);
257 ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
263 * Act on entities being assembled before rendering.
265 * @see hook_entity_view()
266 * @see EntityFieldManagerInterface::getExtraFields()
268 public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
269 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
270 if (!$this->moderationInfo->isModeratedEntity($entity)) {
273 if (isset($entity->in_preview) && $entity->in_preview) {
276 // If the component is not defined for this display, we have nothing to do.
277 if (!$display->getComponent('content_moderation_control')) {
280 // The moderation form should be displayed only when viewing the latest
281 // (translation-affecting) revision, unless it was created as published
283 if (($entity->isDefaultRevision() || $entity->wasDefaultRevision()) && $this->isPublished($entity)) {
286 if (!$entity->isLatestRevision() && !$entity->isLatestTranslationAffectedRevision()) {
290 $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
294 * Checks if the entity is published.
296 * This method is optimized to not have to unnecessarily load the moderation
297 * state and workflow if it is not required.
299 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
300 * The entity to check.
303 * TRUE if the entity is published, FALSE otherwise.
305 protected function isPublished(ContentEntityInterface $entity) {
306 // If the entity implements EntityPublishedInterface directly, check that
307 // first, otherwise fall back to check through the workflow state.
308 if ($entity instanceof EntityPublishedInterface) {
309 return $entity->isPublished();
311 if ($moderation_state = $entity->get('moderation_state')->value) {
312 $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
313 return $workflow->getTypePlugin()->getState($moderation_state)->isPublishedState();