Chris@0: ' . t('About') . ''; Chris@0: $output .= '

' . t('The Content Moderation module provides moderation for content by applying workflows to content. For more information, see the online documentation for the Content Moderation module.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation']) . '

'; Chris@0: $output .= '

' . t('Uses') . '

'; Chris@0: $output .= '
'; Chris@0: $output .= '
' . t('Configuring workflows') . '
'; Chris@0: $output .= '
' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '

'; Chris@0: $output .= '
' . t('Configure Content Moderation permissions') . '
'; Chris@0: $output .= '
' . 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') . '

'; Chris@0: $output .= '
'; Chris@0: return $output; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_base_field_info(). Chris@0: */ Chris@0: function content_moderation_entity_base_field_info(EntityTypeInterface $entity_type) { Chris@0: return \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityTypeInfo::class) Chris@0: ->entityBaseFieldInfo($entity_type); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_type_alter(). Chris@0: */ Chris@0: function content_moderation_entity_type_alter(array &$entity_types) { Chris@0: \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityTypeInfo::class) Chris@0: ->entityTypeAlter($entity_types); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_presave(). Chris@0: */ Chris@0: function content_moderation_entity_presave(EntityInterface $entity) { Chris@0: return \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityOperations::class) Chris@0: ->entityPresave($entity); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_insert(). Chris@0: */ Chris@0: function content_moderation_entity_insert(EntityInterface $entity) { Chris@0: return \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityOperations::class) Chris@0: ->entityInsert($entity); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_update(). Chris@0: */ Chris@0: function content_moderation_entity_update(EntityInterface $entity) { Chris@0: return \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityOperations::class) Chris@0: ->entityUpdate($entity); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_delete(). Chris@0: */ Chris@0: function content_moderation_entity_delete(EntityInterface $entity) { Chris@0: return \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityOperations::class) Chris@0: ->entityDelete($entity); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_revision_delete(). Chris@0: */ Chris@0: function content_moderation_entity_revision_delete(EntityInterface $entity) { Chris@0: return \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityOperations::class) Chris@0: ->entityRevisionDelete($entity); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_translation_delete(). Chris@0: */ Chris@0: function content_moderation_entity_translation_delete(EntityInterface $translation) { Chris@0: return \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityOperations::class) Chris@0: ->entityTranslationDelete($translation); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_form_alter(). Chris@0: */ Chris@0: function content_moderation_form_alter(&$form, FormStateInterface $form_state, $form_id) { Chris@0: \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityTypeInfo::class) Chris@0: ->formAlter($form, $form_state, $form_id); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_preprocess_HOOK(). Chris@0: */ Chris@0: function content_moderation_preprocess_node(&$variables) { Chris@0: \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(ContentPreprocess::class) Chris@0: ->preprocessNode($variables); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_extra_field_info(). Chris@0: */ Chris@0: function content_moderation_entity_extra_field_info() { Chris@0: return \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityTypeInfo::class) Chris@0: ->entityExtraFieldInfo(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_view(). Chris@0: */ Chris@0: function content_moderation_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { Chris@0: \Drupal::service('class_resolver') Chris@0: ->getInstanceFromDefinition(EntityOperations::class) Chris@0: ->entityView($build, $entity, $display, $view_mode); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_access(). Chris@0: * Chris@0: * Entities should be viewable if unpublished and the user has the appropriate Chris@0: * permission. This permission is therefore effectively mandatory for any user Chris@0: * that wants to moderate things. Chris@0: */ Chris@0: function content_moderation_entity_access(EntityInterface $entity, $operation, AccountInterface $account) { Chris@0: /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */ Chris@0: $moderation_info = Drupal::service('content_moderation.moderation_information'); Chris@0: Chris@0: $access_result = NULL; Chris@0: if ($operation === 'view') { Chris@0: $access_result = (($entity instanceof EntityPublishedInterface) && !$entity->isPublished()) Chris@0: ? AccessResult::allowedIfHasPermission($account, 'view any unpublished content') Chris@0: : AccessResult::neutral(); Chris@0: Chris@0: $access_result->addCacheableDependency($entity); Chris@0: } Chris@0: elseif ($operation === 'update' && $moderation_info->isModeratedEntity($entity) && $entity->moderation_state) { Chris@0: /** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */ Chris@0: $transition_validation = \Drupal::service('content_moderation.state_transition_validation'); Chris@0: Chris@0: $valid_transition_targets = $transition_validation->getValidTransitions($entity, $account); Chris@0: $access_result = $valid_transition_targets ? AccessResult::neutral() : AccessResult::forbidden(); Chris@0: Chris@0: $access_result->addCacheableDependency($entity); Chris@0: $access_result->addCacheableDependency($account); Chris@0: $workflow = $moderation_info->getWorkflowForEntity($entity); Chris@0: $access_result->addCacheableDependency($workflow); Chris@0: foreach ($valid_transition_targets as $valid_transition_target) { Chris@0: $access_result->addCacheableDependency($valid_transition_target); Chris@0: } Chris@0: } Chris@0: Chris@0: return $access_result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_field_access(). Chris@0: */ Chris@0: function content_moderation_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { Chris@0: if ($items && $operation === 'edit') { Chris@0: /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */ Chris@0: $moderation_info = Drupal::service('content_moderation.moderation_information'); Chris@0: Chris@0: $entity_type = \Drupal::entityTypeManager()->getDefinition($field_definition->getTargetEntityTypeId()); Chris@0: Chris@0: $entity = $items->getEntity(); Chris@0: Chris@0: // Deny edit access to the published field if the entity is being moderated. Chris@0: if ($entity_type->hasKey('published') && $moderation_info->isModeratedEntity($entity) && $entity->moderation_state && $field_definition->getName() == $entity_type->getKey('published')) { Chris@0: return AccessResult::forbidden(); Chris@0: } Chris@0: } Chris@0: Chris@0: return AccessResult::neutral(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_theme(). Chris@0: */ Chris@0: function content_moderation_theme() { Chris@0: return ['entity_moderation_form' => ['render element' => 'form']]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_action_info_alter(). Chris@0: */ Chris@0: function content_moderation_action_info_alter(&$definitions) { Chris@0: Chris@0: // The publish/unpublish actions are not valid on moderated entities. So swap Chris@0: // their implementations out for alternates that will become a no-op on a Chris@0: // moderated node. If another module has already swapped out those classes, Chris@0: // though, we'll be polite and do nothing. Chris@0: if (isset($definitions['node_publish_action']['class']) && $definitions['node_publish_action']['class'] == PublishNode::class) { Chris@0: $definitions['node_publish_action']['class'] = ModerationOptOutPublishNode::class; Chris@0: } Chris@0: if (isset($definitions['node_unpublish_action']['class']) && $definitions['node_unpublish_action']['class'] == UnpublishNode::class) { Chris@0: $definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_bundle_info_alter(). Chris@0: */ Chris@0: function content_moderation_entity_bundle_info_alter(&$bundles) { Chris@0: /** @var \Drupal\workflows\WorkflowInterface $workflow */ Chris@0: foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) { Chris@0: /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */ Chris@0: $plugin = $workflow->getTypePlugin(); Chris@0: foreach ($plugin->getEntityTypes() as $entity_type_id) { Chris@0: foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) { Chris@0: if (isset($bundles[$entity_type_id][$bundle_id])) { Chris@0: $bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id(); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_bundle_delete(). Chris@0: */ Chris@0: function content_moderation_entity_bundle_delete($entity_type_id, $bundle_id) { Chris@0: // Remove non-configuration based bundles from content moderation based Chris@0: // workflows when they are removed. Chris@0: foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) { Chris@0: if ($workflow->getTypePlugin()->appliesToEntityTypeAndBundle($entity_type_id, $bundle_id)) { Chris@0: $workflow->getTypePlugin()->removeEntityTypeAndBundle($entity_type_id, $bundle_id); Chris@0: $workflow->save(); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_ENTITY_TYPE_insert(). Chris@0: */ Chris@0: function content_moderation_workflow_insert(WorkflowInterface $entity) { Chris@0: // Clear bundle cache so workflow gets added or removed from the bundle Chris@0: // information. Chris@0: \Drupal::service('entity_type.bundle.info')->clearCachedBundles(); Chris@0: // Clear field cache so extra field is added or removed. Chris@0: \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_ENTITY_TYPE_update(). Chris@0: */ Chris@0: function content_moderation_workflow_update(WorkflowInterface $entity) { Chris@0: // Clear bundle cache so workflow gets added or removed from the bundle Chris@0: // information. Chris@0: \Drupal::service('entity_type.bundle.info')->clearCachedBundles(); Chris@0: // Clear field cache so extra field is added or removed. Chris@0: \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_rest_resource_alter(). Chris@0: */ Chris@0: function content_moderation_rest_resource_alter(&$definitions) { Chris@0: // ContentModerationState is an internal entity type. Therefore it should not Chris@0: // be exposed via REST. Chris@0: // @see \Drupal\content_moderation\ContentModerationStateAccessControlHandler Chris@0: unset($definitions['entity:content_moderation_state']); Chris@0: }