scheduler_content_moderation_integration.module in Scheduler content moderation integration 8
Scheduler Content Moderation Integration.
This sub-module provides extended options widget populated with default revision states to allow publishing and un-publishing of nodes which are moderated.
File
scheduler_content_moderation_integration.moduleView source
<?php
/**
* @file
* Scheduler Content Moderation Integration.
*
* This sub-module provides extended options widget populated with default
* revision states to allow publishing and un-publishing of nodes which are
* moderated.
*
* @see https://www.drupal.org/project/scheduler/issues/2798689
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\workflows\Transition;
/**
* Implements hook_entity_access().
*
* Deny access to users not having access to scheduled transitions, similar to
* what is done in content_moderation_entity_access() by checking for valid
* transitions.
*/
function scheduler_content_moderation_integration_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
$moderation_info = Drupal::service('content_moderation.moderation_information');
if ($operation === 'update' && $moderation_info
->isModeratedEntity($entity) && $entity->moderation_state && ($entity->publish_on->value || $entity->unpublish_on->value)) {
/** @var \Drupal\workflows\WorkflowInterface $workflow */
$workflow = $moderation_info
->getWorkflowForEntity($entity);
$current_state = $workflow
->getTypePlugin()
->getState($entity->moderation_state->value);
/** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */
$transition_validation = \Drupal::service('content_moderation.state_transition_validation');
foreach ([
$entity->publish_state,
$entity->unpublish_state,
] as $state) {
try {
$scheduled_state = $workflow
->getTypePlugin()
->getState($state->value);
if (!$transition_validation
->isTransitionValid($workflow, $current_state, $scheduled_state, $account, $entity)) {
return AccessResult::forbidden('Scheduled transition is not valid for the given account.');
}
} catch (\InvalidArgumentException $exception) {
// Just move on when there is no valid state.
}
}
}
return AccessResult::neutral();
}
/**
* Implements hook_entity_base_field_info().
*/
function scheduler_content_moderation_integration_entity_base_field_info(EntityTypeInterface $entity_type) {
$fields = [];
if ($entity_type
->id() === 'node') {
$fields['publish_state'] = BaseFieldDefinition::create('list_string')
->setSetting('allowed_values_function', '_scheduler_content_moderation_integration_states_values')
->setLabel(t('Publish state'))
->setDisplayOptions('view', [
'label' => 'hidden',
'region' => 'hidden',
'weight' => -5,
])
->setDisplayOptions('form', [
'type' => 'scheduler_moderation',
'weight' => 30,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', FALSE)
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->addConstraint('SchedulerPublishState')
->addConstraint('SchedulerModerationTransitionAccess');
$fields['unpublish_state'] = BaseFieldDefinition::create('list_string')
->setSetting('allowed_values_function', '_scheduler_content_moderation_integration_states_values')
->setLabel(t('Unpublish state'))
->setDisplayOptions('view', [
'label' => 'hidden',
'region' => 'hidden',
'weight' => -5,
])
->setDisplayOptions('form', [
'type' => 'scheduler_moderation',
'weight' => 30,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', FALSE)
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->addConstraint('SchedulerUnPublishState')
->addConstraint('SchedulerModerationTransitionAccess');
}
return $fields;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function scheduler_content_moderation_integration_form_node_form_alter(&$form, FormStateInterface $form_state) {
// Attach the publish/un-publish state form elements to the scheduler
// settings group.
$form['publish_state']['#group'] = 'scheduler_settings';
$form['unpublish_state']['#group'] = 'scheduler_settings';
$config = \Drupal::config('scheduler.settings');
/** @var \Drupal\node\NodeTypeInterface $type */
$type = $form_state
->getFormObject()
->getEntity()->type->entity;
// If scheduling for publish/unpublish is not enabled, then hide the state
// selection field.
$form['publish_state']['#access'] = $type
->getThirdPartySetting('scheduler', 'publish_enable', $config
->get('default_publish_enable'));
$form['unpublish_state']['#access'] = $type
->getThirdPartySetting('scheduler', 'unpublish_enable', $config
->get('default_unpublish_enable'));
}
/**
* Helper function for the scheduler moderation widget.
*
* Helps on generating the options dynamically for the scheduler
* moderation widget.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
* The field storage definition.
* @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity
* (optional) The entity context if known, or NULL if the allowed values are
* being collected without the context of a specific entity.
* @param bool &$cacheable
* (optional) If an $entity is provided, the $cacheable parameter should be
* modified by reference and set to FALSE if the set of allowed values
* returned was specifically adjusted for that entity and cannot not be reused
* for other entities. Defaults to TRUE.
*
* @return array
* The array of allowed values.
*/
function _scheduler_content_moderation_integration_states_values(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL, &$cacheable = FALSE) {
$options = [];
// Fetch all possible states if no entity is given.
if (!$entity) {
$workflow_storage = \Drupal::entityTypeManager()
->getStorage('workflow');
foreach ($workflow_storage
->loadByProperties([
'type' => 'content_moderation',
]) as $workflow) {
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModerationInterface $workflow_type */
$workflow_type = $workflow
->getTypePlugin();
foreach ($workflow_type
->getStates() as $state_id => $state) {
$options[$workflow
->id() . '_' . $state_id] = $state
->label();
}
}
return $options;
}
// @todo should call $widget->getEmptyLabel().
$options['_none'] = '';
/** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_information */
$moderation_information = \Drupal::service('content_moderation.moderation_information');
// Only add options for moderated entities.
if (!$moderation_information
->isModeratedEntity($entity)) {
return $options;
}
$workflow = $moderation_information
->getWorkflowForEntity($entity);
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModerationInterface $type_plugin */
$type_plugin = $workflow
->getTypePlugin();
$user = \Drupal::currentUser();
$user_transitions = array_filter($type_plugin
->getTransitions(), function (Transition $transition) use ($workflow, $user) {
return $user
->hasPermission('use ' . $workflow
->id() . ' transition ' . $transition
->id());
});
foreach ($user_transitions as $transition) {
/** @var \Drupal\content_moderation\ContentModerationState $state */
$state = $transition
->to();
if ($state
->isDefaultRevisionState() && ($definition
->getName() === 'publish_state' && $state
->isPublishedState() || $definition
->getName() === 'unpublish_state' && !$state
->isPublishedState())) {
$options[$state
->id()] = $state
->label();
}
}
return $options;
}
/**
* Implements hook_scheduler_hide_publish_on_field().
*
* This hook is called from scheduler_form_node_form_alter() and returns TRUE if
* the scheduler publish_on field should be hidden.
*/
function scheduler_content_moderation_integration_scheduler_hide_publish_on_field($form, $form_state, $node) {
/** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_information */
$moderation_information = \Drupal::service('content_moderation.moderation_information');
$return = FALSE;
if ($moderation_information
->isModeratedEntity($node)) {
// If no moderation transitions are available for publish_state then hide
// the publish_on scheduler field.
$options_without_none = array_diff_key($form['publish_state']['widget'][0]['#options'], [
'_none' => '',
]);
$return = count($options_without_none) == 0;
}
return $return;
}
/**
* Implements hook_scheduler_hide_unpublish_on_field().
*
* This hook is called from scheduler_form_node_form_alter() and returns TRUE if
* the scheduler unpublish_on field should be hidden.
*/
function scheduler_content_moderation_integration_scheduler_hide_unpublish_on_field($form, $form_state, $node) {
/** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_information */
$moderation_information = \Drupal::service('content_moderation.moderation_information');
$return = FALSE;
if ($moderation_information
->isModeratedEntity($node)) {
// If no moderation transitions are available for unpublish_state then hide
// the unpublish_on scheduler field.
$options_without_none = array_diff_key($form['unpublish_state']['widget'][0]['#options'], [
'_none' => '',
]);
$return = count($options_without_none) == 0;
}
return $return;
}
/**
* Implements hook_scheduler_publish_action().
*
* This hook is called from schedulerManger::publish(). The return values are:
* 1 if the node has been processed here and hence should not be published via
* Scheduler.
* -1 if an exception is thrown, to abandon processing this node in Scheduler.
* 0 if not moderated, to let Scheduler process the node as normal.
*/
function scheduler_content_moderation_integration_scheduler_publish_action($node) {
/** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_information */
$moderation_information = \Drupal::service('content_moderation.moderation_information');
if (!$moderation_information
->isModeratedEntity($node)) {
return 0;
}
$state = $node->publish_state->value;
$node->publish_state->value = NULL;
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModerationInterface $type_plugin */
$type_plugin = $moderation_information
->getWorkflowForEntity($node)
->getTypePlugin();
try {
// If transition is not valid, throw exception.
$type_plugin
->getTransitionFromStateToState($node->moderation_state->value, $state);
$node
->set('moderation_state', $state);
return 1;
} catch (\InvalidArgumentException $exception) {
$node
->save();
return -1;
}
}
/**
* Implements hook_scheduler_unpublish_action().
*
* This hook is called from schedulerManger::unpublish(). The return values are:
* 1 if the node has been processed here and hence should not be unpublished
* via Scheduler.
* -1 if an exception is thrown, to abandon processing this node in Scheduler.
* 0 if not moderated, to let Scheduler process the node as normal.
*/
function scheduler_content_moderation_integration_scheduler_unpublish_action($node) {
/** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_information */
$moderation_information = \Drupal::service('content_moderation.moderation_information');
if (!$moderation_information
->isModeratedEntity($node)) {
return 0;
}
$state = $node->unpublish_state->value;
$node->unpublish_state->value = NULL;
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModerationInterface $type_plugin */
$type_plugin = $moderation_information
->getWorkflowForEntity($node)
->getTypePlugin();
try {
// If transition is not valid, throw exception.
$type_plugin
->getTransitionFromStateToState($node->moderation_state->value, $state);
$node
->set('moderation_state', $state);
return 1;
} catch (\InvalidArgumentException $exception) {
$node
->save();
return -1;
}
}