Skip to content
content_moderation.module 7.97 KiB
Newer Older
<?php

/**
 * @file
 * Contains content_moderation.module.
 */

use Drupal\content_moderation\EntityOperations;
use Drupal\content_moderation\EntityTypeInfo;
use Drupal\content_moderation\ContentPreprocess;
use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublishNode;
use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
use Drupal\content_moderation\Plugin\Menu\EditTab;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Drupal\node\Plugin\Action\PublishNode;
use Drupal\node\Plugin\Action\UnpublishNode;

/**
 * Implements hook_help().
 */
function content_moderation_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the content_moderation module.
    case 'help.page.content_moderation':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Content Moderation module provides basic moderation for 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>';
      return $output;
  }
}

/**
 * Creates an EntityTypeInfo object to respond to entity hooks.
 *
 * @return \Drupal\content_moderation\EntityTypeInfo
 */
function _content_moderation_create_entity_type_info() {
  return new EntityTypeInfo(
    \Drupal::service('string_translation'),
    \Drupal::service('content_moderation.moderation_information'),
    \Drupal::service('entity_type.manager')
  );
}

/**
 * Implements hook_entity_base_field_info().
 */
function content_moderation_entity_base_field_info(EntityTypeInterface $entity_type) {
  return _content_moderation_create_entity_type_info()->entityBaseFieldInfo($entity_type);
}

/**
 * Implements hook_entity_type_alter().
 */
function content_moderation_entity_type_alter(array &$entity_types) {
  _content_moderation_create_entity_type_info()->entityTypeAlter($entity_types);
}

/**
 * Implements hook_entity_operation().
 */
function content_moderation_entity_operation(EntityInterface $entity) {
  _content_moderation_create_entity_type_info()->entityOperation($entity);
}

/**
 * Sets required flag based on enabled state.
 */
function content_moderation_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
  _content_moderation_create_entity_type_info()->entityBundleFieldInfoAlter($fields, $entity_type, $bundle);
}

/**
 * Creates an EntityOperations object to respond to entity operation hooks.
 *
 * @return \Drupal\content_moderation\EntityOperations
 */
function _content_moderation_create_entity_operations() {
  return new EntityOperations(
    \Drupal::service('content_moderation.moderation_information'),
    \Drupal::service('entity_type.manager'),
    \Drupal::service('form_builder'),
    \Drupal::service('content_moderation.revision_tracker')
  );
}

/**
 * Implements hook_entity_presave().
 */
function content_moderation_entity_presave(EntityInterface $entity) {
  return _content_moderation_create_entity_operations()->entityPresave($entity);
}

/**
 * Implements hook_entity_insert().
 */
function content_moderation_entity_insert(EntityInterface $entity) {
  return _content_moderation_create_entity_operations()->entityInsert($entity);
}

/**
 * Implements hook_entity_update().
 */
function content_moderation_entity_update(EntityInterface $entity) {
  return _content_moderation_create_entity_operations()->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) {
  _content_moderation_create_entity_type_info()->bundleFormAlter($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) {
  $content_process = new ContentPreprocess(\Drupal::routeMatch());
  $content_process->preprocessNode($variables);
}

/**
 * Implements hook_entity_extra_field_info().
 */
function content_moderation_entity_extra_field_info() {
  return _content_moderation_create_entity_type_info()->entityExtraFieldInfo();
}

/**
 * Implements hook_entity_view().
 */
function content_moderation_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  _content_moderation_create_entity_operations()->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->isModeratableEntity($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;
  }
}