5a7af0e2c9e2c2640dae1590bb8e58a23dc3fdd0
[yaffs-website] / web / core / modules / content_moderation / content_moderation.module
1 <?php
2
3 /**
4  * @file
5  * Contains content_moderation.module.
6  */
7
8 use Drupal\content_moderation\EntityOperations;
9 use Drupal\content_moderation\EntityTypeInfo;
10 use Drupal\content_moderation\ContentPreprocess;
11 use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublishNode;
12 use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
13 use Drupal\Core\Access\AccessResult;
14 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
15 use Drupal\Core\Entity\EntityInterface;
16 use Drupal\Core\Entity\EntityPublishedInterface;
17 use Drupal\Core\Entity\EntityTypeInterface;
18 use Drupal\Core\Field\FieldDefinitionInterface;
19 use Drupal\Core\Field\FieldItemListInterface;
20 use Drupal\Core\Form\FormStateInterface;
21 use Drupal\Core\Routing\RouteMatchInterface;
22 use Drupal\Core\Session\AccountInterface;
23 use Drupal\workflows\WorkflowInterface;
24 use Drupal\node\Plugin\Action\PublishNode;
25 use Drupal\node\Plugin\Action\UnpublishNode;
26 use Drupal\workflows\Entity\Workflow;
27
28 /**
29  * Implements hook_help().
30  */
31 function content_moderation_help($route_name, RouteMatchInterface $route_match) {
32   switch ($route_name) {
33     // Main module help for the content_moderation module.
34     case 'help.page.content_moderation':
35       $output = '';
36       $output .= '<h3>' . t('About') . '</h3>';
37       $output .= '<p>' . t('The Content Moderation module provides moderation for content by applying workflows to content. For more information, see the <a href=":content_moderation">online documentation for the Content Moderation module</a>.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation']) . '</p>';
38       $output .= '<h3>' . t('Uses') . '</h3>';
39       $output .= '<dl>';
40       $output .= '<dt>' . t('Configuring workflows') . '</dt>';
41       $output .= '<dd>' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '</p>';
42       $output .= '<dt>' . t('Configure Content Moderation permissions') . '</dt>';
43       $output .= '<dd>' . t('Each transition is exposed as a permission. If a user has the permission for a transition, then they can move that node from the start state to the end state') . '</p>';
44       $output .= '</dl>';
45       return $output;
46   }
47 }
48
49 /**
50  * Implements hook_entity_base_field_info().
51  */
52 function content_moderation_entity_base_field_info(EntityTypeInterface $entity_type) {
53   return \Drupal::service('class_resolver')
54     ->getInstanceFromDefinition(EntityTypeInfo::class)
55     ->entityBaseFieldInfo($entity_type);
56 }
57
58 /**
59  * Implements hook_entity_type_alter().
60  */
61 function content_moderation_entity_type_alter(array &$entity_types) {
62   \Drupal::service('class_resolver')
63     ->getInstanceFromDefinition(EntityTypeInfo::class)
64     ->entityTypeAlter($entity_types);
65 }
66
67 /**
68  * Implements hook_entity_presave().
69  */
70 function content_moderation_entity_presave(EntityInterface $entity) {
71   return \Drupal::service('class_resolver')
72     ->getInstanceFromDefinition(EntityOperations::class)
73     ->entityPresave($entity);
74 }
75
76 /**
77  * Implements hook_entity_insert().
78  */
79 function content_moderation_entity_insert(EntityInterface $entity) {
80   return \Drupal::service('class_resolver')
81     ->getInstanceFromDefinition(EntityOperations::class)
82     ->entityInsert($entity);
83 }
84
85 /**
86  * Implements hook_entity_update().
87  */
88 function content_moderation_entity_update(EntityInterface $entity) {
89   return \Drupal::service('class_resolver')
90     ->getInstanceFromDefinition(EntityOperations::class)
91     ->entityUpdate($entity);
92 }
93
94 /**
95  * Implements hook_entity_delete().
96  */
97 function content_moderation_entity_delete(EntityInterface $entity) {
98   return \Drupal::service('class_resolver')
99     ->getInstanceFromDefinition(EntityOperations::class)
100     ->entityDelete($entity);
101 }
102
103 /**
104  * Implements hook_entity_revision_delete().
105  */
106 function content_moderation_entity_revision_delete(EntityInterface $entity) {
107   return \Drupal::service('class_resolver')
108     ->getInstanceFromDefinition(EntityOperations::class)
109     ->entityRevisionDelete($entity);
110 }
111
112 /**
113  * Implements hook_entity_translation_delete().
114  */
115 function content_moderation_entity_translation_delete(EntityInterface $translation) {
116   return \Drupal::service('class_resolver')
117     ->getInstanceFromDefinition(EntityOperations::class)
118     ->entityTranslationDelete($translation);
119 }
120
121 /**
122  * Implements hook_form_alter().
123  */
124 function content_moderation_form_alter(&$form, FormStateInterface $form_state, $form_id) {
125   \Drupal::service('class_resolver')
126     ->getInstanceFromDefinition(EntityTypeInfo::class)
127     ->formAlter($form, $form_state, $form_id);
128 }
129
130 /**
131  * Implements hook_preprocess_HOOK().
132  */
133 function content_moderation_preprocess_node(&$variables) {
134   \Drupal::service('class_resolver')
135     ->getInstanceFromDefinition(ContentPreprocess::class)
136     ->preprocessNode($variables);
137 }
138
139 /**
140  * Implements hook_entity_extra_field_info().
141  */
142 function content_moderation_entity_extra_field_info() {
143   return \Drupal::service('class_resolver')
144     ->getInstanceFromDefinition(EntityTypeInfo::class)
145     ->entityExtraFieldInfo();
146 }
147
148 /**
149  * Implements hook_entity_view().
150  */
151 function content_moderation_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
152   \Drupal::service('class_resolver')
153     ->getInstanceFromDefinition(EntityOperations::class)
154     ->entityView($build, $entity, $display, $view_mode);
155 }
156
157 /**
158  * Implements hook_entity_access().
159  *
160  * Entities should be viewable if unpublished and the user has the appropriate
161  * permission. This permission is therefore effectively mandatory for any user
162  * that wants to moderate things.
163  */
164 function content_moderation_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
165   /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
166   $moderation_info = Drupal::service('content_moderation.moderation_information');
167
168   $access_result = NULL;
169   if ($operation === 'view') {
170     $access_result = (($entity instanceof EntityPublishedInterface) && !$entity->isPublished())
171       ? AccessResult::allowedIfHasPermission($account, 'view any unpublished content')
172       : AccessResult::neutral();
173
174     $access_result->addCacheableDependency($entity);
175   }
176   elseif ($operation === 'update' && $moderation_info->isModeratedEntity($entity) && $entity->moderation_state) {
177     /** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */
178     $transition_validation = \Drupal::service('content_moderation.state_transition_validation');
179
180     $valid_transition_targets = $transition_validation->getValidTransitions($entity, $account);
181     $access_result = $valid_transition_targets ? AccessResult::neutral() : AccessResult::forbidden();
182
183     $access_result->addCacheableDependency($entity);
184     $access_result->addCacheableDependency($account);
185     $workflow = $moderation_info->getWorkflowForEntity($entity);
186     $access_result->addCacheableDependency($workflow);
187     foreach ($valid_transition_targets as $valid_transition_target) {
188       $access_result->addCacheableDependency($valid_transition_target);
189     }
190   }
191
192   return $access_result;
193 }
194
195 /**
196  * Implements hook_entity_field_access().
197  */
198 function content_moderation_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
199   if ($items && $operation === 'edit') {
200     /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
201     $moderation_info = Drupal::service('content_moderation.moderation_information');
202
203     $entity_type = \Drupal::entityTypeManager()->getDefinition($field_definition->getTargetEntityTypeId());
204
205     $entity = $items->getEntity();
206
207     // Deny edit access to the published field if the entity is being moderated.
208     if ($entity_type->hasKey('published') && $moderation_info->isModeratedEntity($entity) && $entity->moderation_state && $field_definition->getName() == $entity_type->getKey('published')) {
209       return AccessResult::forbidden();
210     }
211   }
212
213   return AccessResult::neutral();
214 }
215
216 /**
217  * Implements hook_theme().
218  */
219 function content_moderation_theme() {
220   return ['entity_moderation_form' => ['render element' => 'form']];
221 }
222
223 /**
224  * Implements hook_action_info_alter().
225  */
226 function content_moderation_action_info_alter(&$definitions) {
227
228   // The publish/unpublish actions are not valid on moderated entities. So swap
229   // their implementations out for alternates that will become a no-op on a
230   // moderated node. If another module has already swapped out those classes,
231   // though, we'll be polite and do nothing.
232   if (isset($definitions['node_publish_action']['class']) && $definitions['node_publish_action']['class'] == PublishNode::class) {
233     $definitions['node_publish_action']['class'] = ModerationOptOutPublishNode::class;
234   }
235   if (isset($definitions['node_unpublish_action']['class']) && $definitions['node_unpublish_action']['class'] == UnpublishNode::class) {
236     $definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class;
237   }
238 }
239
240 /**
241  * Implements hook_entity_bundle_info_alter().
242  */
243 function content_moderation_entity_bundle_info_alter(&$bundles) {
244   /** @var \Drupal\workflows\WorkflowInterface $workflow */
245   foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
246     /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
247     $plugin = $workflow->getTypePlugin();
248     foreach ($plugin->getEntityTypes() as $entity_type_id) {
249       foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
250         if (isset($bundles[$entity_type_id][$bundle_id])) {
251           $bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id();
252         }
253       }
254     }
255   }
256 }
257
258 /**
259  * Implements hook_entity_bundle_delete().
260  */
261 function content_moderation_entity_bundle_delete($entity_type_id, $bundle_id) {
262   // Remove non-configuration based bundles from content moderation based
263   // workflows when they are removed.
264   foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
265     if ($workflow->getTypePlugin()->appliesToEntityTypeAndBundle($entity_type_id, $bundle_id)) {
266       $workflow->getTypePlugin()->removeEntityTypeAndBundle($entity_type_id, $bundle_id);
267       $workflow->save();
268     }
269   }
270 }
271
272 /**
273  * Implements hook_ENTITY_TYPE_insert().
274  */
275 function content_moderation_workflow_insert(WorkflowInterface $entity) {
276   // Clear bundle cache so workflow gets added or removed from the bundle
277   // information.
278   \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
279   // Clear field cache so extra field is added or removed.
280   \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
281 }
282
283 /**
284  * Implements hook_ENTITY_TYPE_update().
285  */
286 function content_moderation_workflow_update(WorkflowInterface $entity) {
287   // Clear bundle cache so workflow gets added or removed from the bundle
288   // information.
289   \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
290   // Clear field cache so extra field is added or removed.
291   \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
292 }
293
294 /**
295  * Implements hook_rest_resource_alter().
296  */
297 function content_moderation_rest_resource_alter(&$definitions) {
298   // ContentModerationState is an internal entity type. Therefore it should not
299   // be exposed via REST.
300   // @see \Drupal\content_moderation\ContentModerationStateAccessControlHandler
301   unset($definitions['entity:content_moderation_state']);
302 }