content_moderation.module 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <?php
  2. /**
  3. * @file
  4. * Contains content_moderation.module.
  5. */
  6. use Drupal\content_moderation\EntityOperations;
  7. use Drupal\content_moderation\EntityTypeInfo;
  8. use Drupal\content_moderation\ContentPreprocess;
  9. use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublishNode;
  10. use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
  11. use Drupal\Core\Access\AccessResult;
  12. use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
  13. use Drupal\Core\Entity\EntityInterface;
  14. use Drupal\Core\Entity\EntityTypeInterface;
  15. use Drupal\Core\Form\FormStateInterface;
  16. use Drupal\Core\Routing\RouteMatchInterface;
  17. use Drupal\Core\Session\AccountInterface;
  18. use Drupal\workflows\WorkflowInterface;
  19. use Drupal\node\NodeInterface;
  20. use Drupal\node\Plugin\Action\PublishNode;
  21. use Drupal\node\Plugin\Action\UnpublishNode;
  22. use Drupal\workflows\Entity\Workflow;
  23. /**
  24. * Implements hook_help().
  25. */
  26. function content_moderation_help($route_name, RouteMatchInterface $route_match) {
  27. switch ($route_name) {
  28. // Main module help for the content_moderation module.
  29. case 'help.page.content_moderation':
  30. $output = '';
  31. $output .= '<h3>' . t('About') . '</h3>';
  32. $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>';
  33. $output .= '<h3>' . t('Uses') . '</h3>';
  34. $output .= '<dl>';
  35. $output .= '<dt>' . t('Configuring workflows') . '</dt>';
  36. $output .= '<dd>' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '</p>';
  37. $output .= '<dt>' . t('Configure Content Moderation permissions') . '</dt>';
  38. $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>';
  39. $output .= '</dl>';
  40. return $output;
  41. }
  42. }
  43. /**
  44. * Implements hook_entity_base_field_info().
  45. */
  46. function content_moderation_entity_base_field_info(EntityTypeInterface $entity_type) {
  47. return \Drupal::service('class_resolver')
  48. ->getInstanceFromDefinition(EntityTypeInfo::class)
  49. ->entityBaseFieldInfo($entity_type);
  50. }
  51. /**
  52. * Implements hook_entity_type_alter().
  53. */
  54. function content_moderation_entity_type_alter(array &$entity_types) {
  55. \Drupal::service('class_resolver')
  56. ->getInstanceFromDefinition(EntityTypeInfo::class)
  57. ->entityTypeAlter($entity_types);
  58. }
  59. /**
  60. * Implements hook_entity_operation().
  61. */
  62. function content_moderation_entity_operation(EntityInterface $entity) {
  63. return \Drupal::service('class_resolver')
  64. ->getInstanceFromDefinition(EntityTypeInfo::class)
  65. ->entityOperation($entity);
  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. * Implements hook_entity_insert().
  77. */
  78. function content_moderation_entity_insert(EntityInterface $entity) {
  79. return \Drupal::service('class_resolver')
  80. ->getInstanceFromDefinition(EntityOperations::class)
  81. ->entityInsert($entity);
  82. }
  83. /**
  84. * Implements hook_entity_update().
  85. */
  86. function content_moderation_entity_update(EntityInterface $entity) {
  87. return \Drupal::service('class_resolver')
  88. ->getInstanceFromDefinition(EntityOperations::class)
  89. ->entityUpdate($entity);
  90. }
  91. /**
  92. * Implements hook_form_alter().
  93. */
  94. function content_moderation_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  95. \Drupal::service('class_resolver')
  96. ->getInstanceFromDefinition(EntityTypeInfo::class)
  97. ->formAlter($form, $form_state, $form_id);
  98. }
  99. /**
  100. * Implements hook_preprocess_HOOK().
  101. *
  102. * Many default node templates rely on $page to determine whether to output the
  103. * node title as part of the node content.
  104. */
  105. function content_moderation_preprocess_node(&$variables) {
  106. \Drupal::service('class_resolver')
  107. ->getInstanceFromDefinition(ContentPreprocess::class)
  108. ->preprocessNode($variables);
  109. }
  110. /**
  111. * Implements hook_entity_extra_field_info().
  112. */
  113. function content_moderation_entity_extra_field_info() {
  114. return \Drupal::service('class_resolver')
  115. ->getInstanceFromDefinition(EntityTypeInfo::class)
  116. ->entityExtraFieldInfo();
  117. }
  118. /**
  119. * Implements hook_entity_view().
  120. */
  121. function content_moderation_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  122. \Drupal::service('class_resolver')
  123. ->getInstanceFromDefinition(EntityOperations::class)
  124. ->entityView($build, $entity, $display, $view_mode);
  125. }
  126. /**
  127. * Implements hook_node_access().
  128. *
  129. * Nodes in particular should be viewable if unpublished and the user has
  130. * the appropriate permission. This permission is therefore effectively
  131. * mandatory for any user that wants to moderate things.
  132. */
  133. function content_moderation_node_access(NodeInterface $node, $operation, AccountInterface $account) {
  134. /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
  135. $moderation_info = Drupal::service('content_moderation.moderation_information');
  136. $access_result = NULL;
  137. if ($operation === 'view') {
  138. $access_result = (!$node->isPublished())
  139. ? AccessResult::allowedIfHasPermission($account, 'view any unpublished content')
  140. : AccessResult::neutral();
  141. $access_result->addCacheableDependency($node);
  142. }
  143. elseif ($operation === 'update' && $moderation_info->isModeratedEntity($node) && $node->moderation_state) {
  144. /** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */
  145. $transition_validation = \Drupal::service('content_moderation.state_transition_validation');
  146. $valid_transition_targets = $transition_validation->getValidTransitions($node, $account);
  147. $access_result = $valid_transition_targets ? AccessResult::neutral() : AccessResult::forbidden();
  148. $access_result->addCacheableDependency($node);
  149. $access_result->addCacheableDependency($account);
  150. $workflow = \Drupal::service('content_moderation.moderation_information')->getWorkflowForEntity($node);
  151. $access_result->addCacheableDependency($workflow);
  152. foreach ($valid_transition_targets as $valid_transition_target) {
  153. $access_result->addCacheableDependency($valid_transition_target);
  154. }
  155. }
  156. return $access_result;
  157. }
  158. /**
  159. * Implements hook_theme().
  160. */
  161. function content_moderation_theme() {
  162. return ['entity_moderation_form' => ['render element' => 'form']];
  163. }
  164. /**
  165. * Implements hook_action_info_alter().
  166. */
  167. function content_moderation_action_info_alter(&$definitions) {
  168. // The publish/unpublish actions are not valid on moderated entities. So swap
  169. // their implementations out for alternates that will become a no-op on a
  170. // moderated node. If another module has already swapped out those classes,
  171. // though, we'll be polite and do nothing.
  172. if (isset($definitions['node_publish_action']['class']) && $definitions['node_publish_action']['class'] == PublishNode::class) {
  173. $definitions['node_publish_action']['class'] = ModerationOptOutPublishNode::class;
  174. }
  175. if (isset($definitions['node_unpublish_action']['class']) && $definitions['node_unpublish_action']['class'] == UnpublishNode::class) {
  176. $definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class;
  177. }
  178. }
  179. /**
  180. * Implements hook_entity_bundle_info_alter().
  181. */
  182. function content_moderation_entity_bundle_info_alter(&$bundles) {
  183. /** @var \Drupal\workflows\WorkflowInterface $workflow */
  184. foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
  185. /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
  186. $plugin = $workflow->getTypePlugin();
  187. foreach ($plugin->getEntityTypes() as $entity_type_id) {
  188. foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
  189. if (isset($bundles[$entity_type_id][$bundle_id])) {
  190. $bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id();
  191. }
  192. }
  193. }
  194. }
  195. }
  196. /**
  197. * Implements hook_ENTITY_TYPE_insert().
  198. */
  199. function content_moderation_workflow_insert(WorkflowInterface $entity) {
  200. // Clear bundle cache so workflow gets added or removed from the bundle
  201. // information.
  202. \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
  203. // Clear field cache so extra field is added or removed.
  204. \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
  205. }
  206. /**
  207. * Implements hook_ENTITY_TYPE_update().
  208. */
  209. function content_moderation_workflow_update(WorkflowInterface $entity) {
  210. content_moderation_workflow_insert($entity);
  211. }