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