' . t('About') . '';
$output .= '
' . t('The Content Moderation module provides basic moderation for content. This lets site admins define states for content, and then define transitions between those states. 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('Moderation states') . '
';
$output .= '- ' . t('Moderation states provide the Draft and Archived states as additions to the basic Published option. You can click the blue Add Moderation state button and create new states.') . '
';
$output .= '- ' . t('Moderation state transitions') . '
';
$output .= '- ' . t('Using the "Moderation state transitions" screen, you can create the actual workflow. You decide the direction in which content moves from state to state, and which user roles are allowed to make that move.') . '
';
$output .= '- ' . t('Configure Content Moderation permissions') . '
';
$output .= '- ' . t('Each state 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);
}
/**
* Sets required flag based on enabled state.
*/
function content_moderation_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
\Drupal::service('class_resolver')
->getInstanceFromDefinition(EntityTypeInfo::class)
->entityBundleFieldInfoAlter($fields, $entity_type, $bundle);
}
/**
* 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 && $node->moderation_state->target_id) {
/** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */
$transition_validation = \Drupal::service('content_moderation.state_transition_validation');
$valid_transition_targets = $transition_validation->getValidTransitionTargets($node, $account);
$access_result = $valid_transition_targets ? AccessResult::neutral() : AccessResult::forbidden();
$access_result->addCacheableDependency($node);
$access_result->addCacheableDependency($account);
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;
}
}