' . t('About') . '';
$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']) . '
';
$output .= '' . t('Uses') . '
';
$output .= '';
$output .= '- ' . t('Configuring workflows') . '
';
$output .= '- ' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '';
$output .= '
- ' . t('Configure Content Moderation permissions') . '
';
$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') . '';
$output .= '
';
return $output;
}
}
/**
* Implements hook_entity_base_field_info().
*/
function content_moderation_entity_base_field_info(EntityTypeInterface $entity_type) {
return \Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityTypeInfo::class)
->entityBaseFieldInfo($entity_type);
}
/**
* Implements hook_entity_type_alter().
*/
function content_moderation_entity_type_alter(array &$entity_types) {
\Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityTypeInfo::class)
->entityTypeAlter($entity_types);
}
/**
* Implements hook_entity_operation().
*/
function content_moderation_entity_operation(EntityInterface $entity) {
return \Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityTypeInfo::class)
->entityOperation($entity);
}
/**
* Implements hook_entity_presave().
*/
function content_moderation_entity_presave(EntityInterface $entity) {
return \Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityOperations::class)
->entityPresave($entity);
}
/**
* Implements hook_entity_insert().
*/
function content_moderation_entity_insert(EntityInterface $entity) {
return \Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityOperations::class)
->entityInsert($entity);
}
/**
* Implements hook_entity_update().
*/
function content_moderation_entity_update(EntityInterface $entity) {
return \Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityOperations::class)
->entityUpdate($entity);
}
/**
* Implements hook_local_tasks_alter().
*/
function content_moderation_local_tasks_alter(&$local_tasks) {
$content_entity_type_ids = array_keys(array_filter(\Drupal::entityTypeManager()->getDefinitions(), function (EntityTypeInterface $entity_type) {
return $entity_type->isRevisionable();
}));
foreach ($content_entity_type_ids as $content_entity_type_id) {
if (isset($local_tasks["entity.$content_entity_type_id.edit_form"])) {
$local_tasks["entity.$content_entity_type_id.edit_form"]['class'] = EditTab::class;
$local_tasks["entity.$content_entity_type_id.edit_form"]['entity_type_id'] = $content_entity_type_id;
}
}
}
/**
* Implements hook_form_alter().
*/
function content_moderation_form_alter(&$form, FormStateInterface $form_state, $form_id) {
\Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityTypeInfo::class)
->formAlter($form, $form_state, $form_id);
}
/**
* Implements hook_preprocess_HOOK().
*
* Many default node templates rely on $page to determine whether to output the
* node title as part of the node content.
*/
function content_moderation_preprocess_node(&$variables) {
\Drupal::service('class_resolver')
->getInstanceFromDefinition(ContentPreprocess::class)
->preprocessNode($variables);
}
/**
* Implements hook_entity_extra_field_info().
*/
function content_moderation_entity_extra_field_info() {
return \Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityTypeInfo::class)
->entityExtraFieldInfo();
}
/**
* Implements hook_entity_view().
*/
function content_moderation_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
\Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityOperations::class)
->entityView($build, $entity, $display, $view_mode);
}
/**
* Implements hook_node_access().
*
* Nodes in particular should be viewable if unpublished and the user has
* the appropriate permission. This permission is therefore effectively
* mandatory for any user that wants to moderate things.
*/
function content_moderation_node_access(NodeInterface $node, $operation, AccountInterface $account) {
/** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
$moderation_info = Drupal::service('content_moderation.moderation_information');
$access_result = NULL;
if ($operation === 'view') {
$access_result = (!$node->isPublished())
? AccessResult::allowedIfHasPermission($account, 'view any unpublished content')
: AccessResult::neutral();
$access_result->addCacheableDependency($node);
}
elseif ($operation === 'update' && $moderation_info->isModeratedEntity($node) && $node->moderation_state) {
/** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */
$transition_validation = \Drupal::service('content_moderation.state_transition_validation');
$valid_transition_targets = $transition_validation->getValidTransitions($node, $account);
$access_result = $valid_transition_targets ? AccessResult::neutral() : AccessResult::forbidden();
$access_result->addCacheableDependency($node);
$access_result->addCacheableDependency($account);
$workflow = \Drupal::service('content_moderation.moderation_information')->getWorkflowForEntity($node);
$access_result->addCacheableDependency($workflow);
foreach ($valid_transition_targets as $valid_transition_target) {
$access_result->addCacheableDependency($valid_transition_target);
}
}
return $access_result;
}
/**
* Implements hook_theme().
*/
function content_moderation_theme() {
return ['entity_moderation_form' => ['render element' => 'form']];
}
/**
* Implements hook_action_info_alter().
*/
function content_moderation_action_info_alter(&$definitions) {
// The publish/unpublish actions are not valid on moderated entities. So swap
// their implementations out for alternates that will become a no-op on a
// moderated node. If another module has already swapped out those classes,
// though, we'll be polite and do nothing.
if (isset($definitions['node_publish_action']['class']) && $definitions['node_publish_action']['class'] == PublishNode::class) {
$definitions['node_publish_action']['class'] = ModerationOptOutPublishNode::class;
}
if (isset($definitions['node_unpublish_action']['class']) && $definitions['node_unpublish_action']['class'] == UnpublishNode::class) {
$definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class;
}
}
/**
* Implements hook_entity_bundle_info_alter().
*/
function content_moderation_entity_bundle_info_alter(&$bundles) {
/** @var \Drupal\workflows\WorkflowInterface $workflow */
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
$plugin = $workflow->getTypePlugin();
foreach ($plugin->getEntityTypes() as $entity_type_id) {
foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
if (isset($bundles[$entity_type_id][$bundle_id])) {
$bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id();
}
}
}
}
}
/**
* Implements hook_ENTITY_TYPE_insert().
*/
function content_moderation_workflow_insert(WorkflowInterface $entity) {
// Clear bundle cache so workflow gets added or removed from the bundle
// information.
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
// Clear field cache so extra field is added or removed.
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
}
/**
* Implements hook_ENTITY_TYPE_update().
*/
function content_moderation_workflow_update(WorkflowInterface $entity) {
content_moderation_workflow_insert($entity);
}