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