Update core 8.3.0
This commit is contained in:
parent
da7a7918f8
commit
cd7a898e66
6144 changed files with 132297 additions and 87747 deletions
|
@ -1,8 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: archived
|
||||
label: Archived
|
||||
published: false
|
||||
default_revision: true
|
||||
weight: -8
|
|
@ -1,8 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: draft
|
||||
label: Draft
|
||||
published: false
|
||||
default_revision: false
|
||||
weight: -10
|
|
@ -1,8 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: published
|
||||
label: Published
|
||||
published: true
|
||||
default_revision: true
|
||||
weight: -9
|
|
@ -1,11 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- content_moderation.state.archived
|
||||
- content_moderation.state.draft
|
||||
id: archived_draft
|
||||
label: 'Un-archive to Draft'
|
||||
stateFrom: archived
|
||||
stateTo: draft
|
||||
weight: -5
|
|
@ -1,11 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- content_moderation.state.archived
|
||||
- content_moderation.state.published
|
||||
id: archived_published
|
||||
label: 'Un-archive'
|
||||
stateFrom: archived
|
||||
stateTo: published
|
||||
weight: -4
|
|
@ -1,10 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- content_moderation.state.draft
|
||||
id: draft_draft
|
||||
label: 'Create New Draft'
|
||||
stateFrom: draft
|
||||
stateTo: draft
|
||||
weight: -10
|
|
@ -1,11 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- content_moderation.state.draft
|
||||
- content_moderation.state.published
|
||||
id: draft_published
|
||||
label: 'Publish'
|
||||
stateFrom: draft
|
||||
stateTo: published
|
||||
weight: -9
|
|
@ -1,11 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- content_moderation.state.archived
|
||||
- content_moderation.state.published
|
||||
id: published_archived
|
||||
label: 'Archive'
|
||||
stateFrom: published
|
||||
stateTo: archived
|
||||
weight: -6
|
|
@ -1,11 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- content_moderation.state.draft
|
||||
- content_moderation.state.published
|
||||
id: published_draft
|
||||
label: 'Create New Draft'
|
||||
stateFrom: published
|
||||
stateTo: draft
|
||||
weight: -8
|
|
@ -1,10 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- content_moderation.state.published
|
||||
id: published_published
|
||||
label: 'Publish'
|
||||
stateFrom: published
|
||||
stateTo: published
|
||||
weight: -7
|
|
@ -0,0 +1,63 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
id: editorial
|
||||
label: 'Editorial workflow'
|
||||
states:
|
||||
archived:
|
||||
label: Archived
|
||||
weight: 5
|
||||
draft:
|
||||
label: Draft
|
||||
weight: -5
|
||||
published:
|
||||
label: Published
|
||||
weight: 0
|
||||
transitions:
|
||||
archive:
|
||||
label: Archive
|
||||
from:
|
||||
- published
|
||||
to: archived
|
||||
weight: 2
|
||||
archived_draft:
|
||||
label: 'Restore to Draft'
|
||||
from:
|
||||
- archived
|
||||
to: draft
|
||||
weight: 3
|
||||
archived_published:
|
||||
label: Restore
|
||||
from:
|
||||
- archived
|
||||
to: published
|
||||
weight: 4
|
||||
create_new_draft:
|
||||
label: 'Create New Draft'
|
||||
from:
|
||||
- draft
|
||||
- published
|
||||
to: draft
|
||||
weight: 0
|
||||
publish:
|
||||
label: Publish
|
||||
from:
|
||||
- draft
|
||||
- published
|
||||
to: published
|
||||
weight: 1
|
||||
type: content_moderation
|
||||
type_settings:
|
||||
states:
|
||||
archived:
|
||||
published: false
|
||||
default_revision: true
|
||||
draft:
|
||||
published: false
|
||||
default_revision: false
|
||||
published:
|
||||
published: true
|
||||
default_revision: true
|
||||
entity_types: { }
|
|
@ -1,75 +1,3 @@
|
|||
content_moderation.state.*:
|
||||
type: config_entity
|
||||
label: 'Moderation state config'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
published:
|
||||
type: boolean
|
||||
label: 'Is published'
|
||||
default_revision:
|
||||
type: boolean
|
||||
label: 'Is default revision'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
|
||||
content_moderation.state_transition.*:
|
||||
type: config_entity
|
||||
label: 'Moderation state transition config'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
stateFrom:
|
||||
type: string
|
||||
label: 'From state'
|
||||
stateTo:
|
||||
type: string
|
||||
label: 'To state'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
|
||||
node.type.*.third_party.content_moderation:
|
||||
type: mapping
|
||||
label: 'Enable moderation states for this node type'
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: 'Moderation states enabled'
|
||||
allowed_moderation_states:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Moderation state'
|
||||
default_moderation_state:
|
||||
type: string
|
||||
label: 'Moderation state for new content'
|
||||
|
||||
block_content.type.*.third_party.content_moderation:
|
||||
type: mapping
|
||||
label: 'Enable moderation states for this block content type'
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: 'Moderation states enabled'
|
||||
allowed_moderation_states:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Moderation state'
|
||||
default_moderation_state:
|
||||
type: string
|
||||
label: 'Moderation state for new block content'
|
||||
|
||||
views.filter.latest_revision:
|
||||
type: views_filter
|
||||
label: 'Latest revision'
|
||||
|
@ -77,3 +5,29 @@ views.filter.latest_revision:
|
|||
value:
|
||||
type: string
|
||||
label: 'Value'
|
||||
|
||||
workflow.type_settings.content_moderation:
|
||||
type: mapping
|
||||
mapping:
|
||||
states:
|
||||
type: sequence
|
||||
label: 'Additional state configuration for content moderation'
|
||||
sequence:
|
||||
type: mapping
|
||||
label: 'States'
|
||||
mapping:
|
||||
published:
|
||||
type: boolean
|
||||
label: 'Is published'
|
||||
default_revision:
|
||||
type: boolean
|
||||
label: 'Is default revision'
|
||||
entity_types:
|
||||
type: sequence
|
||||
label: 'Entity types'
|
||||
sequence:
|
||||
type: sequence
|
||||
label: 'Bundles'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Bundle ID'
|
||||
|
|
|
@ -4,4 +4,6 @@ description: 'Provides moderation states for content'
|
|||
version: VERSION
|
||||
core: 8.x
|
||||
package: Core (Experimental)
|
||||
configure: content_moderation.overview
|
||||
configure: entity.workflow.collection
|
||||
dependencies:
|
||||
- workflows
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
entity.moderation_state.add_form:
|
||||
route_name: 'entity.moderation_state.add_form'
|
||||
title: 'Add moderation state'
|
||||
appears_on:
|
||||
- entity.moderation_state.collection
|
||||
|
||||
entity.moderation_state_transition.add_form:
|
||||
route_name: 'entity.moderation_state_transition.add_form'
|
||||
title: 'Add moderation state transition'
|
||||
appears_on:
|
||||
- entity.moderation_state_transition.collection
|
|
@ -1,21 +0,0 @@
|
|||
# Moderation state menu items definition
|
||||
content_moderation.overview:
|
||||
title: 'Content moderation'
|
||||
route_name: content_moderation.overview
|
||||
description: 'Configure states and transitions for entities.'
|
||||
parent: system.admin_config_workflow
|
||||
|
||||
entity.moderation_state.collection:
|
||||
title: 'Moderation states'
|
||||
route_name: entity.moderation_state.collection
|
||||
description: 'Administer moderation states.'
|
||||
parent: content_moderation.overview
|
||||
weight: 10
|
||||
|
||||
# Moderation state transition menu items definition
|
||||
entity.moderation_state_transition.collection:
|
||||
title: 'Moderation state transitions'
|
||||
route_name: entity.moderation_state_transition.collection
|
||||
description: 'Administer moderation states transitions.'
|
||||
parent: content_moderation.overview
|
||||
weight: 20
|
|
@ -1,3 +1,3 @@
|
|||
moderation_state.entities:
|
||||
content_moderation.workflows:
|
||||
deriver: 'Drupal\content_moderation\Plugin\Derivative\DynamicLocalTasks'
|
||||
weight: 100
|
||||
|
|
|
@ -18,9 +18,11 @@ use Drupal\Core\Entity\EntityTypeInterface;
|
|||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\node\Plugin\Action\PublishNode;
|
||||
use Drupal\node\Plugin\Action\UnpublishNode;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
|
@ -31,15 +33,13 @@ function content_moderation_help($route_name, RouteMatchInterface $route_match)
|
|||
case 'help.page.content_moderation':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . 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 <a href=":content_moderation">online documentation for the Content Moderation module</a>.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation']) . '</p>';
|
||||
$output .= '<p>' . t('The Content Moderation module provides moderation for content by applying workflows to 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>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Moderation states') . '</dt>';
|
||||
$output .= '<dd>' . t('Moderation states provide the <em>Draft</em> and <em>Archived</em> states as additions to the basic <em>Published</em> option. You can click the blue <em>Add Moderation state</em> button and create new states.') . '</dd>';
|
||||
$output .= '<dt>' . t('Moderation state transitions') . '</dt>';
|
||||
$output .= '<dd>' . 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.') . '</dd>';
|
||||
$output .= '<dt>' . t('Configuring workflows') . '</dt>';
|
||||
$output .= '<dd>' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '</p>';
|
||||
$output .= '<dt>' . t('Configure Content Moderation permissions') . '</dt>';
|
||||
$output .= '<dd>' . 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') . '</p>';
|
||||
$output .= '<dd>' . 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') . '</p>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
}
|
||||
|
@ -72,15 +72,6 @@ function content_moderation_entity_operation(EntityInterface $entity) {
|
|||
->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().
|
||||
*/
|
||||
|
@ -182,15 +173,17 @@ function content_moderation_node_access(NodeInterface $node, $operation, Account
|
|||
|
||||
$access_result->addCacheableDependency($node);
|
||||
}
|
||||
elseif ($operation === 'update' && $moderation_info->isModeratedEntity($node) && $node->moderation_state && $node->moderation_state->target_id) {
|
||||
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->getValidTransitionTargets($node, $account);
|
||||
$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);
|
||||
}
|
||||
|
@ -222,3 +215,39 @@ function content_moderation_action_info_alter(&$definitions) {
|
|||
$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);
|
||||
}
|
||||
|
|
|
@ -2,18 +2,13 @@ view any unpublished content:
|
|||
title: 'View any unpublished content'
|
||||
description: 'This permission is necessary for any users that may moderate content.'
|
||||
|
||||
'view moderation states':
|
||||
title: 'View moderation states'
|
||||
description: 'View moderation states.'
|
||||
'view content moderation':
|
||||
title: 'View content moderation'
|
||||
description: 'View content moderation.'
|
||||
|
||||
'administer moderation states':
|
||||
title: 'Administer moderation states'
|
||||
description: 'Create and edit moderation states.'
|
||||
'restrict access': TRUE
|
||||
|
||||
'administer moderation state transitions':
|
||||
title: 'Administer content moderation state transitions'
|
||||
description: 'Create and edit content moderation state transitions.'
|
||||
'administer content moderation':
|
||||
title: 'Administer content moderation'
|
||||
description: 'Administer workflows on content entities.'
|
||||
'restrict access': TRUE
|
||||
|
||||
view latest version:
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
content_moderation.overview:
|
||||
path: '/admin/config/workflow/moderation'
|
||||
defaults:
|
||||
_controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
|
||||
_title: 'Content moderation'
|
||||
requirements:
|
||||
_permission: 'access administration pages'
|
|
@ -6,10 +6,10 @@ services:
|
|||
- { name: paramconverter, priority: 5 }
|
||||
content_moderation.state_transition_validation:
|
||||
class: \Drupal\content_moderation\StateTransitionValidation
|
||||
arguments: ['@entity_type.manager', '@entity.query']
|
||||
arguments: ['@content_moderation.moderation_information']
|
||||
content_moderation.moderation_information:
|
||||
class: Drupal\content_moderation\ModerationInformation
|
||||
arguments: ['@entity_type.manager']
|
||||
arguments: ['@entity_type.manager', '@entity_type.bundle.info']
|
||||
access_check.latest_revision:
|
||||
class: Drupal\content_moderation\Access\LatestRevisionCheck
|
||||
arguments: ['@content_moderation.moderation_information']
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\workflows\StateInterface;
|
||||
|
||||
/**
|
||||
* A value object representing a workflow state for content moderation.
|
||||
*/
|
||||
class ContentModerationState implements StateInterface {
|
||||
|
||||
/**
|
||||
* The vanilla state object from the Workflow module.
|
||||
*
|
||||
* @var \Drupal\workflows\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* If entities should be published if in this state.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $published;
|
||||
|
||||
/**
|
||||
* If entities should be the default revision if in this state.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $defaultRevision;
|
||||
|
||||
/**
|
||||
* ContentModerationState constructor.
|
||||
*
|
||||
* Decorates state objects to add methods to determine if an entity should be
|
||||
* published or made the default revision.
|
||||
*
|
||||
* @param \Drupal\workflows\StateInterface $state
|
||||
* The vanilla state object from the Workflow module.
|
||||
* @param bool $published
|
||||
* (optional) TRUE if entities should be published if in this state, FALSE
|
||||
* if not. Defaults to FALSE.
|
||||
* @param bool $default_revision
|
||||
* (optional) TRUE if entities should be the default revision if in this
|
||||
* state, FALSE if not. Defaults to FALSE.
|
||||
*/
|
||||
public function __construct(StateInterface $state, $published = FALSE, $default_revision = FALSE) {
|
||||
$this->state = $state;
|
||||
$this->published = $published;
|
||||
$this->defaultRevision = $default_revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if entities should be published if in this state.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPublishedState() {
|
||||
return $this->published;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if entities should be the default revision if in this state.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefaultRevisionState() {
|
||||
return $this->defaultRevision;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function id() {
|
||||
return $this->state->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->state->label();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function weight() {
|
||||
return $this->state->weight();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function canTransitionTo($to_state_id) {
|
||||
return $this->state->canTransitionTo($to_state_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitionTo($to_state_id) {
|
||||
return $this->state->getTransitionTo($to_state_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitions() {
|
||||
return $this->state->getTransitions();
|
||||
}
|
||||
|
||||
}
|
|
@ -19,9 +19,9 @@ class ContentModerationStateStorageSchema extends SqlContentEntityStorageSchema
|
|||
// Creates an index to ensure that the lookup in
|
||||
// \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList::getModerationState()
|
||||
// is performant.
|
||||
$schema['content_moderation_state_field_data']['indexes'] += array(
|
||||
'content_moderation_state__lookup' => array('content_entity_type_id', 'content_entity_id', 'content_entity_revision_id'),
|
||||
);
|
||||
$schema['content_moderation_state_field_data']['indexes'] += [
|
||||
'content_moderation_state__lookup' => ['content_entity_type_id', 'content_entity_id', 'content_entity_revision_id'],
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
|
|
@ -55,14 +55,20 @@ class ContentModerationState extends ContentEntityBase implements ContentModerat
|
|||
->setTranslatable(TRUE)
|
||||
->setRevisionable(TRUE);
|
||||
|
||||
$fields['moderation_state'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Moderation state'))
|
||||
->setDescription(t('The moderation state of the referenced content.'))
|
||||
->setSetting('target_type', 'moderation_state')
|
||||
$fields['workflow'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Workflow'))
|
||||
->setDescription(t('The workflow the moderation state is in.'))
|
||||
->setSetting('target_type', 'workflow')
|
||||
->setRequired(TRUE)
|
||||
->setTranslatable(TRUE)
|
||||
->setRevisionable(TRUE)
|
||||
->addConstraint('ModerationState', []);
|
||||
->setRevisionable(TRUE);
|
||||
|
||||
$fields['moderation_state'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Moderation state'))
|
||||
->setDescription(t('The moderation state of the referenced content.'))
|
||||
->setRequired(TRUE)
|
||||
->setTranslatable(TRUE)
|
||||
->setRevisionable(TRUE);
|
||||
|
||||
$fields['content_entity_type_id'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Content entity type ID'))
|
||||
|
@ -142,7 +148,7 @@ class ContentModerationState extends ContentEntityBase implements ContentModerat
|
|||
* An array of default values.
|
||||
*/
|
||||
public static function getCurrentUserId() {
|
||||
return array(\Drupal::currentUser()->id());
|
||||
return [\Drupal::currentUser()->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,7 +161,7 @@ class ContentModerationState extends ContentEntityBase implements ContentModerat
|
|||
if ($related_entity instanceof TranslatableInterface) {
|
||||
$related_entity = $related_entity->getTranslation($this->activeLangcode);
|
||||
}
|
||||
$related_entity->moderation_state->target_id = $this->moderation_state->target_id;
|
||||
$related_entity->moderation_state = $this->moderation_state;
|
||||
return $related_entity->save();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace Drupal\content_moderation\Entity\Handler;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
@ -33,31 +33,11 @@ class ModerationHandler implements ModerationHandlerInterface, EntityHandlerInte
|
|||
// This is probably not necessary if configuration is setup correctly.
|
||||
$entity->setNewRevision(TRUE);
|
||||
$entity->isDefaultRevision($default_revision);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onBundleModerationConfigurationFormSubmit(ConfigEntityInterface $bundle) {
|
||||
// The Revisions portion of Entity API is not uniformly applied or
|
||||
// consistent. Until that's fixed, we'll make a best-attempt to apply it to
|
||||
// the common entity patterns so as to avoid every entity type needing to
|
||||
// implement this method, although some will still need to do so for now.
|
||||
// This is the API that should be universal, but isn't yet.
|
||||
// @see \Drupal\node\Entity\NodeType
|
||||
if (method_exists($bundle, 'setNewRevision')) {
|
||||
$bundle->setNewRevision(TRUE);
|
||||
// Update publishing status if it can be updated and if it needs updating.
|
||||
if (($entity instanceof EntityPublishedInterface) && $entity->isPublished() !== $published_state) {
|
||||
$published_state ? $entity->setPublished() : $entity->setUnpublished();
|
||||
}
|
||||
// This is the raw property used by NodeType, and likely others.
|
||||
elseif ($bundle->get('new_revision') !== NULL) {
|
||||
$bundle->set('new_revision', TRUE);
|
||||
}
|
||||
// This is the raw property used by BlockContentType, and maybe others.
|
||||
elseif ($bundle->get('revision') !== NULL) {
|
||||
$bundle->set('revision', TRUE);
|
||||
}
|
||||
|
||||
$bundle->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Drupal\content_moderation\Entity\Handler;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
|
@ -27,21 +26,6 @@ interface ModerationHandlerInterface {
|
|||
*/
|
||||
public function onPresave(ContentEntityInterface $entity, $default_revision, $published_state);
|
||||
|
||||
/**
|
||||
* Operates on the bundle definition that has been marked as moderated.
|
||||
*
|
||||
* Note: The values on the EntityModerationForm itself are already saved
|
||||
* so do not need to be saved here. If any changes are made to the bundle
|
||||
* object here it is this method's responsibility to call save() on it.
|
||||
*
|
||||
* The most common use case is to force revisions on for this bundle if
|
||||
* moderation is enabled. That, sadly, does not have a common API in core.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle
|
||||
* The bundle definition that is being saved.
|
||||
*/
|
||||
public function onBundleModerationConfigurationFormSubmit(ConfigEntityInterface $bundle);
|
||||
|
||||
/**
|
||||
* Alters entity forms to enforce revision handling.
|
||||
*
|
||||
|
|
|
@ -2,24 +2,40 @@
|
|||
|
||||
namespace Drupal\content_moderation\Entity\Handler;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Customizations for node entities.
|
||||
*/
|
||||
class NodeModerationHandler extends ModerationHandler {
|
||||
|
||||
/**
|
||||
* The moderation information service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\ModerationInformationInterface
|
||||
*/
|
||||
protected $moderationInfo;
|
||||
|
||||
/**
|
||||
* NodeModerationHandler constructor.
|
||||
*
|
||||
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
|
||||
* The moderation information service.
|
||||
*/
|
||||
public function __construct(ModerationInformationInterface $moderation_info) {
|
||||
$this->moderationInfo = $moderation_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onPresave(ContentEntityInterface $entity, $default_revision, $published_state) {
|
||||
if ($this->shouldModerate($entity, $published_state)) {
|
||||
parent::onPresave($entity, $default_revision, $published_state);
|
||||
// Only nodes have a concept of published.
|
||||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
$entity->setPublished($published_state);
|
||||
}
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$container->get('content_moderation.moderation_information')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,29 +54,11 @@ class NodeModerationHandler extends ModerationHandler {
|
|||
/* @var \Drupal\node\Entity\NodeType $entity */
|
||||
$entity = $form_state->getFormObject()->getEntity();
|
||||
|
||||
if ($entity->getThirdPartySetting('content_moderation', 'enabled', FALSE)) {
|
||||
if ($this->moderationInfo->getWorkflowForEntity($entity)) {
|
||||
// Force the revision checkbox on.
|
||||
$form['workflow']['options']['#default_value']['revision'] = 'revision';
|
||||
$form['workflow']['options']['revision']['#disabled'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity's default revision and/or state needs adjusting.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity to check.
|
||||
* @param bool $published_state
|
||||
* Whether the state being transitioned to is a published state or not.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE when either the default revision or the state needs to be updated.
|
||||
*/
|
||||
protected function shouldModerate(ContentEntityInterface $entity, $published_state) {
|
||||
// @todo clarify the first condition.
|
||||
// First condition is needed so you can add a translation.
|
||||
// Second condition checks to see if the published status has changed.
|
||||
return $entity->isDefaultTranslation() || $entity->isPublished() !== $published_state;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\content_moderation\ModerationStateInterface;
|
||||
|
||||
/**
|
||||
* Defines the Moderation state entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "moderation_state",
|
||||
* label = @Translation("Moderation state"),
|
||||
* handlers = {
|
||||
* "access" = "Drupal\content_moderation\ModerationStateAccessControlHandler",
|
||||
* "list_builder" = "Drupal\content_moderation\ModerationStateListBuilder",
|
||||
* "form" = {
|
||||
* "add" = "Drupal\content_moderation\Form\ModerationStateForm",
|
||||
* "edit" = "Drupal\content_moderation\Form\ModerationStateForm",
|
||||
* "delete" = "Drupal\content_moderation\Form\ModerationStateDeleteForm"
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
|
||||
* },
|
||||
* },
|
||||
* config_prefix = "state",
|
||||
* admin_permission = "administer moderation states",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* "uuid" = "uuid",
|
||||
* "weight" = "weight",
|
||||
* },
|
||||
* links = {
|
||||
* "add-form" = "/admin/config/workflow/moderation/states/add",
|
||||
* "edit-form" = "/admin/config/workflow/moderation/states/{moderation_state}",
|
||||
* "delete-form" = "/admin/config/workflow/moderation/states/{moderation_state}/delete",
|
||||
* "collection" = "/admin/config/workflow/moderation/states"
|
||||
* },
|
||||
* config_export = {
|
||||
* "id",
|
||||
* "label",
|
||||
* "published",
|
||||
* "default_revision",
|
||||
* "weight",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class ModerationState extends ConfigEntityBase implements ModerationStateInterface {
|
||||
|
||||
/**
|
||||
* The Moderation state ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The Moderation state label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* Whether this state represents a published node.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $published;
|
||||
|
||||
/**
|
||||
* Relative weight of this state.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $weight;
|
||||
|
||||
/**
|
||||
* Whether this state represents a default revision of the node.
|
||||
*
|
||||
* If this is a published state, then this property is ignored.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $default_revision;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isPublishedState() {
|
||||
return $this->published;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDefaultRevisionState() {
|
||||
return $this->published || $this->default_revision;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\content_moderation\ModerationStateTransitionInterface;
|
||||
|
||||
/**
|
||||
* Defines the Moderation state transition entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "moderation_state_transition",
|
||||
* label = @Translation("Moderation state transition"),
|
||||
* handlers = {
|
||||
* "list_builder" = "Drupal\content_moderation\ModerationStateTransitionListBuilder",
|
||||
* "form" = {
|
||||
* "add" = "Drupal\content_moderation\Form\ModerationStateTransitionForm",
|
||||
* "edit" = "Drupal\content_moderation\Form\ModerationStateTransitionForm",
|
||||
* "delete" = "Drupal\content_moderation\Form\ModerationStateTransitionDeleteForm"
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
|
||||
* },
|
||||
* },
|
||||
* config_prefix = "state_transition",
|
||||
* admin_permission = "administer moderation state transitions",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* "uuid" = "uuid",
|
||||
* "weight" = "weight"
|
||||
* },
|
||||
* links = {
|
||||
* "add-form" = "/admin/config/workflow/moderation/transitions/add",
|
||||
* "edit-form" = "/admin/config/workflow/moderation/transitions/{moderation_state_transition}",
|
||||
* "delete-form" = "/admin/config/workflow/moderation/transitions/{moderation_state_transition}/delete",
|
||||
* "collection" = "/admin/config/workflow/moderation/transitions"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ModerationStateTransition extends ConfigEntityBase implements ModerationStateTransitionInterface {
|
||||
|
||||
/**
|
||||
* The Moderation state transition ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The Moderation state transition label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* ID of from state.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $stateFrom;
|
||||
|
||||
/**
|
||||
* ID of to state.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $stateTo;
|
||||
|
||||
/**
|
||||
* Relative weight of this transition.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $weight;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
parent::calculateDependencies();
|
||||
|
||||
if ($this->stateFrom) {
|
||||
$this->addDependency('config', ModerationState::load($this->stateFrom)->getConfigDependencyName());
|
||||
}
|
||||
if ($this->stateTo) {
|
||||
$this->addDependency('config', ModerationState::load($this->stateTo)->getConfigDependencyName());
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFromState() {
|
||||
return $this->stateFrom;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getToState() {
|
||||
return $this->stateTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWeight() {
|
||||
return $this->weight;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,14 +2,16 @@
|
|||
|
||||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\content_moderation\Entity\ContentModerationState;
|
||||
use Drupal\content_moderation\Entity\ContentModerationState as ContentModerationStateEntity;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
use Drupal\content_moderation\Form\EntityModerationForm;
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -45,6 +47,13 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
*/
|
||||
protected $tracker;
|
||||
|
||||
/**
|
||||
* The entity bundle information service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityOperations object.
|
||||
*
|
||||
|
@ -56,12 +65,15 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
* The form builder.
|
||||
* @param \Drupal\content_moderation\RevisionTrackerInterface $tracker
|
||||
* The revision tracker.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
|
||||
* The entity bundle information service.
|
||||
*/
|
||||
public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, RevisionTrackerInterface $tracker) {
|
||||
public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, RevisionTrackerInterface $tracker, EntityTypeBundleInfoInterface $bundle_info) {
|
||||
$this->moderationInfo = $moderation_info;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->formBuilder = $form_builder;
|
||||
$this->tracker = $tracker;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,7 +84,8 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
$container->get('content_moderation.moderation_information'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('form_builder'),
|
||||
$container->get('content_moderation.revision_tracker')
|
||||
$container->get('content_moderation.revision_tracker'),
|
||||
$container->get('entity_type.bundle.info')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -86,20 +99,20 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
if (!$this->moderationInfo->isModeratedEntity($entity)) {
|
||||
return;
|
||||
}
|
||||
if ($entity->moderation_state->target_id) {
|
||||
$moderation_state = $this->entityTypeManager
|
||||
->getStorage('moderation_state')
|
||||
->load($entity->moderation_state->target_id);
|
||||
$published_state = $moderation_state->isPublishedState();
|
||||
|
||||
if ($entity->moderation_state->value) {
|
||||
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
|
||||
/** @var \Drupal\content_moderation\ContentModerationState $current_state */
|
||||
$current_state = $workflow->getState($entity->moderation_state->value);
|
||||
|
||||
// This entity is default if it is new, the default revision, or the
|
||||
// default revision is not published.
|
||||
$update_default_revision = $entity->isNew()
|
||||
|| $moderation_state->isDefaultRevisionState()
|
||||
|| !$this->isDefaultRevisionPublished($entity);
|
||||
|| $current_state->isDefaultRevisionState()
|
||||
|| !$this->isDefaultRevisionPublished($entity, $workflow);
|
||||
|
||||
// Fire per-entity-type logic for handling the save process.
|
||||
$this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->onPresave($entity, $update_default_revision, $published_state);
|
||||
$this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->onPresave($entity, $update_default_revision, $current_state->isPublishedState());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,15 +153,14 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
* The entity to update or create a moderation state for.
|
||||
*/
|
||||
protected function updateOrCreateFromEntity(EntityInterface $entity) {
|
||||
$moderation_state = $entity->moderation_state->target_id;
|
||||
$moderation_state = $entity->moderation_state->value;
|
||||
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
if (!$moderation_state) {
|
||||
$moderation_state = $this->entityTypeManager
|
||||
->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle())
|
||||
->getThirdPartySetting('content_moderation', 'default_moderation_state');
|
||||
$moderation_state = $workflow->getInitialState()->id();
|
||||
}
|
||||
|
||||
// @todo what if $entity->moderation_state->target_id is null at this point?
|
||||
// @todo what if $entity->moderation_state is null at this point?
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$entity_id = $entity->id();
|
||||
$entity_revision_id = $entity->getRevisionId();
|
||||
|
@ -157,6 +169,7 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
$entities = $storage->loadByProperties([
|
||||
'content_entity_type_id' => $entity_type_id,
|
||||
'content_entity_id' => $entity_id,
|
||||
'workflow' => $workflow->id(),
|
||||
]);
|
||||
|
||||
/** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */
|
||||
|
@ -166,6 +179,7 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
'content_entity_type_id' => $entity_type_id,
|
||||
'content_entity_id' => $entity_id,
|
||||
]);
|
||||
$content_moderation_state->workflow->target_id = $workflow->id();
|
||||
}
|
||||
else {
|
||||
// Create a new revision.
|
||||
|
@ -186,7 +200,7 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
// Create the ContentModerationState entity for the inserted entity.
|
||||
$content_moderation_state->set('content_entity_revision_id', $entity_revision_id);
|
||||
$content_moderation_state->set('moderation_state', $moderation_state);
|
||||
ContentModerationState::updateOrCreateFromEntity($content_moderation_state);
|
||||
ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,11 +255,13 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being saved.
|
||||
* @param \Drupal\workflows\WorkflowInterface $workflow
|
||||
* The workflow being applied to the entity.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the default revision is published. FALSE otherwise.
|
||||
*/
|
||||
protected function isDefaultRevisionPublished(EntityInterface $entity) {
|
||||
protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowInterface $workflow) {
|
||||
$storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
|
||||
$default_revision = $storage->load($entity->id());
|
||||
|
||||
|
@ -260,7 +276,7 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
$default_revision = $default_revision->getTranslation($entity->language()->getId());
|
||||
}
|
||||
|
||||
return $default_revision && $default_revision->moderation_state->entity->isPublishedState();
|
||||
return $default_revision && $workflow->getState($default_revision->moderation_state->value)->isPublishedState();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use Drupal\Core\Entity\ContentEntityFormInterface;
|
|||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
|
@ -49,6 +50,13 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The bundle information service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
|
@ -77,11 +85,16 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
* The moderation information service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* Entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
|
||||
* Bundle information service.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* Current user.
|
||||
*/
|
||||
public function __construct(TranslationInterface $translation, ModerationInformationInterface $moderation_information, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user) {
|
||||
public function __construct(TranslationInterface $translation, ModerationInformationInterface $moderation_information, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info, AccountInterface $current_user) {
|
||||
$this->stringTranslation = $translation;
|
||||
$this->moderationInfo = $moderation_information;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
|
@ -93,6 +106,7 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
$container->get('string_translation'),
|
||||
$container->get('content_moderation.moderation_information'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
@ -109,9 +123,16 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
* @see hook_entity_type_alter()
|
||||
*/
|
||||
public function entityTypeAlter(array &$entity_types) {
|
||||
foreach ($this->filterNonRevisionableEntityTypes($entity_types) as $type_name => $type) {
|
||||
$entity_types[$type_name] = $this->addModerationToEntityType($type);
|
||||
$entity_types[$type->get('bundle_of')] = $this->addModerationToEntity($entity_types[$type->get('bundle_of')]);
|
||||
foreach ($entity_types as $entity_type_id => $entity_type) {
|
||||
// The ContentModerationState entity type should never be moderated.
|
||||
if ($entity_type->isRevisionable() && $entity_type_id != 'content_moderation_state') {
|
||||
$entity_types[$entity_type_id] = $this->addModerationToEntityType($entity_type);
|
||||
// Add additional moderation support to entity types whose bundles are
|
||||
// managed by a config entity type.
|
||||
if ($entity_type->getBundleEntityType()) {
|
||||
$entity_types[$entity_type->getBundleEntityType()] = $this->addModerationToBundleEntityType($entity_types[$entity_type->getBundleEntityType()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +148,7 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
* @return \Drupal\Core\Entity\ContentEntityTypeInterface
|
||||
* The modified content entity definition.
|
||||
*/
|
||||
protected function addModerationToEntity(ContentEntityTypeInterface $type) {
|
||||
protected function addModerationToEntityType(ContentEntityTypeInterface $type) {
|
||||
if (!$type->hasHandlerClass('moderation')) {
|
||||
$handler_class = !empty($this->moderationHandlers[$type->id()]) ? $this->moderationHandlers[$type->id()] : ModerationHandler::class;
|
||||
$type->setHandlerClass('moderation', $handler_class);
|
||||
|
@ -161,7 +182,7 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
* @return \Drupal\Core\Config\Entity\ConfigEntityTypeInterface
|
||||
* The modified config entity definition.
|
||||
*/
|
||||
protected function addModerationToEntityType(ConfigEntityTypeInterface $type) {
|
||||
protected function addModerationToBundleEntityType(ConfigEntityTypeInterface $type) {
|
||||
if ($type->hasLinkTemplate('edit-form') && !$type->hasLinkTemplate('moderation-form')) {
|
||||
$type->setLinkTemplate('moderation-form', $type->getLinkTemplate('edit-form') . '/moderation');
|
||||
}
|
||||
|
@ -196,7 +217,7 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
$operations = [];
|
||||
$type = $entity->getEntityType();
|
||||
$bundle_of = $type->getBundleOf();
|
||||
if ($this->currentUser->hasPermission('administer moderation states') && $bundle_of &&
|
||||
if ($this->currentUser->hasPermission('administer content moderation') && $bundle_of &&
|
||||
$this->moderationInfo->canModerateEntitiesOfEntityType($this->entityTypeManager->getDefinition($bundle_of))
|
||||
) {
|
||||
$operations['manage-moderation'] = [
|
||||
|
@ -262,16 +283,12 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
* - bundle: The machine name of a bundle, such as "page" or "article".
|
||||
*/
|
||||
protected function getModeratedBundles() {
|
||||
/** @var ConfigEntityTypeInterface $type */
|
||||
foreach ($this->filterNonRevisionableEntityTypes($this->entityTypeManager->getDefinitions()) as $type_name => $type) {
|
||||
$result = $this->entityTypeManager
|
||||
->getStorage($type_name)
|
||||
->getQuery()
|
||||
->condition('third_party_settings.content_moderation.enabled', TRUE)
|
||||
->execute();
|
||||
|
||||
foreach ($result as $bundle_name) {
|
||||
yield ['entity' => $type->getBundleOf(), 'bundle' => $bundle_name];
|
||||
$entity_types = array_filter($this->entityTypeManager->getDefinitions(), [$this->moderationInfo, 'canModerateEntitiesOfEntityType']);
|
||||
foreach ($entity_types as $type_name => $type) {
|
||||
foreach ($this->bundleInfo->getBundleInfo($type_name) as $bundle_id => $bundle) {
|
||||
if ($this->moderationInfo->shouldModerateEntitiesOfBundle($type, $bundle_id)) {
|
||||
yield ['entity' => $type_name, 'bundle' => $bundle_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -291,15 +308,15 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
}
|
||||
|
||||
$fields = [];
|
||||
$fields['moderation_state'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel($this->t('Moderation state'))
|
||||
->setDescription($this->t('The moderation state of this piece of content.'))
|
||||
$fields['moderation_state'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Moderation state'))
|
||||
->setDescription(t('The moderation state of this piece of content.'))
|
||||
->setComputed(TRUE)
|
||||
->setClass(ModerationStateFieldItemList::class)
|
||||
->setSetting('target_type', 'moderation_state')
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'hidden',
|
||||
'region' => 'hidden',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
|
@ -316,24 +333,6 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds ModerationState constraint to bundles whose entities are moderated.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface[] $fields
|
||||
* The array of bundle field definitions.
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param string $bundle
|
||||
* The bundle.
|
||||
*
|
||||
* @see hook_entity_bundle_field_info_alter();
|
||||
*/
|
||||
public function entityBundleFieldInfoAlter(&$fields, EntityTypeInterface $entity_type, $bundle) {
|
||||
if (!empty($fields['moderation_state']) && $this->moderationInfo->shouldModerateEntitiesOfBundle($entity_type, $bundle)) {
|
||||
$fields['moderation_state']->addConstraint('ModerationState', []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters bundle forms to enforce revision handling.
|
||||
*
|
||||
|
@ -388,21 +387,4 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters entity type lists to return only revisionable entity types.
|
||||
*
|
||||
* @param EntityTypeInterface[] $entity_types
|
||||
* The master entity type list filter.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Entity\ConfigEntityTypeInterface[]
|
||||
* An array of revisionable entity types which are configuration entities.
|
||||
*/
|
||||
protected function filterNonRevisionableEntityTypes(array $entity_types) {
|
||||
return array_filter($entity_types, function (EntityTypeInterface $type) use ($entity_types) {
|
||||
return ($type instanceof ConfigEntityTypeInterface)
|
||||
&& ($bundle_of = $type->get('bundle_of'))
|
||||
&& $entity_types[$bundle_of]->isRevisionable();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
namespace Drupal\content_moderation\Form;
|
||||
|
||||
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
|
||||
use Drupal\content_moderation\Plugin\WorkflowType\ContentModeration;
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -50,146 +49,107 @@ class BundleModerationConfigurationForm extends EntityForm {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
/* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $bundle */
|
||||
$bundle = $form_state->getFormObject()->getEntity();
|
||||
$form['enable_moderation_state'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Enable moderation states.'),
|
||||
'#description' => $this->t('Content of this type must transition through moderation states in order to be published.'),
|
||||
'#default_value' => $bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE),
|
||||
/* @var \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle */
|
||||
$bundle = $this->getEntity();
|
||||
$bundle_of_entity_type = $this->entityTypeManager->getDefinition($bundle->getEntityType()->getBundleOf());
|
||||
/* @var \Drupal\workflows\WorkflowInterface[] $workflows */
|
||||
$workflows = $this->entityTypeManager->getStorage('workflow')->loadMultiple();
|
||||
|
||||
$options = array_map(function (WorkflowInterface $workflow) {
|
||||
return $workflow->label();
|
||||
}, array_filter($workflows, function (WorkflowInterface $workflow) {
|
||||
return $workflow->status() && $workflow->getTypePlugin() instanceof ContentModeration;
|
||||
}));
|
||||
|
||||
$selected_workflow = array_reduce($workflows, function ($carry, WorkflowInterface $workflow) use ($bundle_of_entity_type, $bundle) {
|
||||
$plugin = $workflow->getTypePlugin();
|
||||
if ($plugin instanceof ContentModeration && $plugin->appliesToEntityTypeAndBundle($bundle_of_entity_type->id(), $bundle->id())) {
|
||||
return $workflow->id();
|
||||
}
|
||||
return $carry;
|
||||
});
|
||||
$form['workflow'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Select the workflow to apply'),
|
||||
'#default_value' => $selected_workflow,
|
||||
'#options' => $options,
|
||||
'#required' => FALSE,
|
||||
'#empty_value' => '',
|
||||
];
|
||||
|
||||
$form['original_workflow'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $selected_workflow,
|
||||
];
|
||||
|
||||
$form['bundle'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $bundle->id(),
|
||||
];
|
||||
|
||||
$form['entity_type'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $bundle_of_entity_type->id(),
|
||||
];
|
||||
|
||||
// Add a special message when moderation is being disabled.
|
||||
if ($bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE)) {
|
||||
$form['enable_moderation_state_note'] = [
|
||||
if ($selected_workflow) {
|
||||
$form['enable_workflow_note'] = [
|
||||
'#type' => 'item',
|
||||
'#description' => $this->t('After disabling moderation, any existing forward drafts will be accessible via the "Revisions" tab.'),
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name=enable_moderation_state]' => ['checked' => FALSE],
|
||||
],
|
||||
],
|
||||
'#access' => !empty($selected_workflow)
|
||||
];
|
||||
}
|
||||
|
||||
$states = $this->entityTypeManager->getStorage('moderation_state')->loadMultiple();
|
||||
$label = function(ModerationState $state) {
|
||||
return $state->label();
|
||||
};
|
||||
|
||||
$options_published = array_map($label, array_filter($states, function(ModerationState $state) {
|
||||
return $state->isPublishedState();
|
||||
}));
|
||||
|
||||
$options_unpublished = array_map($label, array_filter($states, function(ModerationState $state) {
|
||||
return !$state->isPublishedState();
|
||||
}));
|
||||
|
||||
$form['allowed_moderation_states_unpublished'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Allowed moderation states (Unpublished)'),
|
||||
'#description' => $this->t('The allowed unpublished moderation states this content-type can be assigned.'),
|
||||
'#default_value' => $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($options_unpublished)),
|
||||
'#options' => $options_unpublished,
|
||||
'#required' => TRUE,
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name=enable_moderation_state]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$form['allowed_moderation_states_published'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Allowed moderation states (Published)'),
|
||||
'#description' => $this->t('The allowed published moderation states this content-type can be assigned.'),
|
||||
'#default_value' => $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($options_published)),
|
||||
'#options' => $options_published,
|
||||
'#required' => TRUE,
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name=enable_moderation_state]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// The key of the array needs to be a user-facing string so we have to fully
|
||||
// render the translatable string to a real string, or else PHP errors on an
|
||||
// object used as an array key.
|
||||
$options = [
|
||||
$this->t('Unpublished')->render() => $options_unpublished,
|
||||
$this->t('Published')->render() => $options_published,
|
||||
];
|
||||
|
||||
$form['default_moderation_state'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Default moderation state'),
|
||||
'#options' => $options,
|
||||
'#description' => $this->t('Select the moderation state for new content'),
|
||||
'#default_value' => $bundle->getThirdPartySetting('content_moderation', 'default_moderation_state', 'draft'),
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name=enable_moderation_state]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
$form['#entity_builders'][] = [$this, 'formBuilderCallback'];
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form builder callback.
|
||||
*
|
||||
* @todo This should be folded into the form method.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type identifier.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $bundle
|
||||
* The bundle entity updated with the submitted values.
|
||||
* @param array $form
|
||||
* The complete form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function formBuilderCallback($entity_type_id, EntityInterface $bundle, &$form, FormStateInterface $form_state) {
|
||||
// @todo https://www.drupal.org/node/2779933 write a test for this.
|
||||
if ($bundle instanceof ThirdPartySettingsInterface) {
|
||||
$bundle->setThirdPartySetting('content_moderation', 'enabled', $form_state->getValue('enable_moderation_state'));
|
||||
$bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys(array_filter($form_state->getValue('allowed_moderation_states_published') + $form_state->getValue('allowed_moderation_states_unpublished'))));
|
||||
$bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', $form_state->getValue('default_moderation_state'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->getValue('enable_moderation_state')) {
|
||||
$allowed = array_keys(array_filter($form_state->getValue('allowed_moderation_states_published') + $form_state->getValue('allowed_moderation_states_unpublished')));
|
||||
|
||||
if (($default = $form_state->getValue('default_moderation_state')) && !in_array($default, $allowed, TRUE)) {
|
||||
$form_state->setErrorByName('default_moderation_state', $this->t('The default moderation state must be one of the allowed states.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// If moderation is enabled, revisions MUST be enabled as well. Otherwise we
|
||||
// can't have forward revisions.
|
||||
if ($form_state->getValue('enable_moderation_state')) {
|
||||
/* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $bundle */
|
||||
$bundle = $form_state->getFormObject()->getEntity();
|
||||
|
||||
$this->entityTypeManager->getHandler($bundle->getEntityType()->getBundleOf(), 'moderation')->onBundleModerationConfigurationFormSubmit($bundle);
|
||||
}
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
|
||||
drupal_set_message($this->t('Your settings have been saved.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$entity_type_id = $form_state->getValue('entity_type');
|
||||
$bundle_id = $form_state->getValue('bundle');
|
||||
$new_workflow_id = $form_state->getValue('workflow');
|
||||
$original_workflow_id = $form_state->getValue('original_workflow');
|
||||
if ($new_workflow_id === $original_workflow_id) {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
if ($original_workflow_id) {
|
||||
/* @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entityTypeManager->getStorage('workflow')->load($original_workflow_id);
|
||||
$workflow->getTypePlugin()->removeEntityTypeAndBundle($entity_type_id, $bundle_id);
|
||||
$workflow->save();
|
||||
}
|
||||
if ($new_workflow_id) {
|
||||
/* @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entityTypeManager->getStorage('workflow')->load($new_workflow_id);
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id);
|
||||
$workflow->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#submit' => ['::submitForm', '::save'],
|
||||
];
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
namespace Drupal\content_moderation\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\content_moderation\Entity\ModerationStateTransition;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Drupal\content_moderation\StateTransitionValidation;
|
||||
use Drupal\workflows\Transition;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -30,13 +29,6 @@ class EntityModerationForm extends FormBase {
|
|||
*/
|
||||
protected $validation;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* EntityModerationForm constructor.
|
||||
*
|
||||
|
@ -44,13 +36,10 @@ class EntityModerationForm extends FormBase {
|
|||
* The moderation information service.
|
||||
* @param \Drupal\content_moderation\StateTransitionValidation $validation
|
||||
* The moderation state transition validation service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(ModerationInformationInterface $moderation_info, StateTransitionValidation $validation, EntityTypeManagerInterface $entity_type_manager) {
|
||||
public function __construct(ModerationInformationInterface $moderation_info, StateTransitionValidation $validation) {
|
||||
$this->moderationInfo = $moderation_info;
|
||||
$this->validation = $validation;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,8 +48,7 @@ class EntityModerationForm extends FormBase {
|
|||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('content_moderation.moderation_information'),
|
||||
$container->get('content_moderation.state_transition_validation'),
|
||||
$container->get('entity_type.manager')
|
||||
$container->get('content_moderation.state_transition_validation')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -75,20 +63,21 @@ class EntityModerationForm extends FormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ContentEntityInterface $entity = NULL) {
|
||||
/** @var \Drupal\content_moderation\Entity\ModerationState $current_state */
|
||||
$current_state = $entity->moderation_state->entity;
|
||||
$current_state = $entity->moderation_state->value;
|
||||
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
|
||||
|
||||
/** @var \Drupal\workflows\Transition[] $transitions */
|
||||
$transitions = $this->validation->getValidTransitions($entity, $this->currentUser());
|
||||
|
||||
// Exclude self-transitions.
|
||||
$transitions = array_filter($transitions, function(ModerationStateTransition $transition) use ($current_state) {
|
||||
return $transition->getToState() != $current_state->id();
|
||||
$transitions = array_filter($transitions, function(Transition $transition) use ($current_state) {
|
||||
return $transition->to()->id() != $current_state;
|
||||
});
|
||||
|
||||
$target_states = [];
|
||||
/** @var ModerationStateTransition $transition */
|
||||
|
||||
foreach ($transitions as $transition) {
|
||||
$target_states[$transition->getToState()] = $transition->label();
|
||||
$target_states[$transition->to()->id()] = $transition->to()->label();
|
||||
}
|
||||
|
||||
if (!count($target_states)) {
|
||||
|
@ -99,7 +88,7 @@ class EntityModerationForm extends FormBase {
|
|||
$form['current'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Status'),
|
||||
'#markup' => $current_state->label(),
|
||||
'#markup' => $workflow->getState($current_state)->label(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -137,23 +126,19 @@ class EntityModerationForm extends FormBase {
|
|||
|
||||
$new_state = $form_state->getValue('new_state');
|
||||
|
||||
// @todo should we just just be updating the content moderation state
|
||||
// entity? That would prevent setting the revision log.
|
||||
$entity->moderation_state->target_id = $new_state;
|
||||
$entity->set('moderation_state', $new_state);
|
||||
$entity->revision_log = $form_state->getValue('revision_log');
|
||||
|
||||
$entity->save();
|
||||
|
||||
drupal_set_message($this->t('The moderation state has been updated.'));
|
||||
|
||||
/** @var \Drupal\content_moderation\Entity\ModerationState $state */
|
||||
$state = $this->entityTypeManager->getStorage('moderation_state')->load($new_state);
|
||||
|
||||
$new_state = $this->moderationInfo->getWorkflowForEntity($entity)->getState($new_state);
|
||||
// The page we're on likely won't be visible if we just set the entity to
|
||||
// the default state, as we hide that latest-revision tab if there is no
|
||||
// forward revision. Redirect to the canonical URL instead, since that will
|
||||
// still exist.
|
||||
if ($state->isDefaultRevisionState()) {
|
||||
if ($new_state->isDefaultRevisionState()) {
|
||||
$form_state->setRedirectUrl($entity->toUrl('canonical'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Builds the form to delete Moderation state entities.
|
||||
*/
|
||||
class ModerationStateDeleteForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete %name?', array('%name' => $this->entity->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.moderation_state.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->entity->delete();
|
||||
|
||||
drupal_set_message($this->t(
|
||||
'Moderation state %label deleted.',
|
||||
['%label' => $this->entity->label()]
|
||||
));
|
||||
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Form;
|
||||
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class ModerationStateForm.
|
||||
*/
|
||||
class ModerationStateForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/* @var \Drupal\content_moderation\ModerationStateInterface $moderation_state */
|
||||
$moderation_state = $this->entity;
|
||||
$form['label'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $moderation_state->label(),
|
||||
'#description' => $this->t('Label for the Moderation state.'),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
|
||||
$form['id'] = array(
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $moderation_state->id(),
|
||||
'#machine_name' => array(
|
||||
'exists' => [ModerationState::class, 'load'],
|
||||
),
|
||||
'#disabled' => !$moderation_state->isNew(),
|
||||
);
|
||||
|
||||
$form['published'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Published'),
|
||||
'#description' => $this->t('When content reaches this state it should be published.'),
|
||||
'#default_value' => $moderation_state->isPublishedState(),
|
||||
];
|
||||
|
||||
$form['default_revision'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Default revision'),
|
||||
'#description' => $this->t('When content reaches this state it should be made the default revision; this is implied for published states.'),
|
||||
'#default_value' => $moderation_state->isDefaultRevisionState(),
|
||||
// @todo Add form #state to force "make default" on when "published" is
|
||||
// on for a state.
|
||||
// @see https://www.drupal.org/node/2645614
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$moderation_state = $this->entity;
|
||||
$status = $moderation_state->save();
|
||||
|
||||
switch ($status) {
|
||||
case SAVED_NEW:
|
||||
drupal_set_message($this->t('Created the %label Moderation state.', [
|
||||
'%label' => $moderation_state->label(),
|
||||
]));
|
||||
break;
|
||||
|
||||
default:
|
||||
drupal_set_message($this->t('Saved the %label Moderation state.', [
|
||||
'%label' => $moderation_state->label(),
|
||||
]));
|
||||
}
|
||||
$form_state->setRedirectUrl($moderation_state->toUrl('collection'));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Builds the form to delete Moderation state transition entities.
|
||||
*/
|
||||
class ModerationStateTransitionDeleteForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete %name?', array('%name' => $this->entity->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.moderation_state_transition.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->entity->delete();
|
||||
|
||||
drupal_set_message($this->t(
|
||||
'Moderation transition %label deleted.',
|
||||
['%label' => $this->entity->label()]
|
||||
));
|
||||
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class ModerationStateTransitionForm.
|
||||
*
|
||||
* @package Drupal\content_moderation\Form
|
||||
*/
|
||||
class ModerationStateTransitionForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity query factory.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $queryFactory;
|
||||
|
||||
/**
|
||||
* Constructs a new ModerationStateTransitionForm.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
|
||||
* The entity query factory.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, QueryFactory $query_factory) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->queryFactory = $query_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity_type.manager'), $container->get('entity.query'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/* @var \Drupal\content_moderation\ModerationStateTransitionInterface $moderation_state_transition */
|
||||
$moderation_state_transition = $this->entity;
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $moderation_state_transition->label(),
|
||||
'#description' => $this->t('Label for the Moderation state transition.'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $moderation_state_transition->id(),
|
||||
'#machine_name' => [
|
||||
'exists' => '\Drupal\content_moderation\Entity\ModerationStateTransition::load',
|
||||
],
|
||||
'#disabled' => !$moderation_state_transition->isNew(),
|
||||
];
|
||||
|
||||
$options = [];
|
||||
foreach ($this->entityTypeManager->getStorage('moderation_state')
|
||||
->loadMultiple() as $moderation_state) {
|
||||
$options[$moderation_state->id()] = $moderation_state->label();
|
||||
}
|
||||
|
||||
$form['container'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => ['container-inline'],
|
||||
],
|
||||
];
|
||||
|
||||
$form['container']['stateFrom'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Transition from'),
|
||||
'#options' => $options,
|
||||
'#required' => TRUE,
|
||||
'#empty_option' => $this->t('-- Select --'),
|
||||
'#default_value' => $moderation_state_transition->getFromState(),
|
||||
];
|
||||
|
||||
$form['container']['stateTo'] = [
|
||||
'#type' => 'select',
|
||||
'#options' => $options,
|
||||
'#required' => TRUE,
|
||||
'#title' => $this->t('Transition to'),
|
||||
'#empty_option' => $this->t('-- Select --'),
|
||||
'#default_value' => $moderation_state_transition->getToState(),
|
||||
];
|
||||
|
||||
// Make sure there's always at least a wide enough delta on weight to cover
|
||||
// the current value or the total number of transitions. That way we
|
||||
// never end up forcing a transition to change its weight needlessly.
|
||||
$num_transitions = $this->queryFactory->get('moderation_state_transition')
|
||||
->count()
|
||||
->execute();
|
||||
$delta = max(abs($moderation_state_transition->getWeight()), $num_transitions);
|
||||
|
||||
$form['weight'] = [
|
||||
'#type' => 'weight',
|
||||
'#delta' => $delta,
|
||||
'#options' => $options,
|
||||
'#title' => $this->t('Weight'),
|
||||
'#default_value' => $moderation_state_transition->getWeight(),
|
||||
'#description' => $this->t('Orders the transitions in moderation forms and the administrative listing. Heavier items will sink and the lighter items will be positioned nearer the top.'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$moderation_state_transition = $this->entity;
|
||||
$status = $moderation_state_transition->save();
|
||||
|
||||
switch ($status) {
|
||||
case SAVED_NEW:
|
||||
drupal_set_message($this->t('Created the %label Moderation state transition.', [
|
||||
'%label' => $moderation_state_transition->label(),
|
||||
]));
|
||||
break;
|
||||
|
||||
default:
|
||||
drupal_set_message($this->t('Saved the %label Moderation state transition.', [
|
||||
'%label' => $moderation_state_transition->label(),
|
||||
]));
|
||||
}
|
||||
$form_state->setRedirectUrl($moderation_state_transition->toUrl('collection'));
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace Drupal\content_moderation;
|
|||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
|
||||
|
@ -19,16 +20,24 @@ class ModerationInformation implements ModerationInformationInterface {
|
|||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The bundle information service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* Creates a new ModerationInformation instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
|
||||
* The bundle information service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,10 +63,8 @@ class ModerationInformation implements ModerationInformationInterface {
|
|||
*/
|
||||
public function shouldModerateEntitiesOfBundle(EntityTypeInterface $entity_type, $bundle) {
|
||||
if ($this->canModerateEntitiesOfEntityType($entity_type)) {
|
||||
$bundle_entity = $this->entityTypeManager->getStorage($entity_type->getBundleEntityType())->load($bundle);
|
||||
if ($bundle_entity) {
|
||||
return $bundle_entity->getThirdPartySetting('content_moderation', 'enabled', FALSE);
|
||||
}
|
||||
$bundles = $this->bundleInfo->getBundleInfo($entity_type->id());
|
||||
return isset($bundles[$bundle]['workflow']);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -123,10 +130,22 @@ class ModerationInformation implements ModerationInformationInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLiveRevision(ContentEntityInterface $entity) {
|
||||
$workflow = $this->getWorkflowForEntity($entity);
|
||||
return $this->isLatestRevision($entity)
|
||||
&& $entity->isDefaultRevision()
|
||||
&& $entity->moderation_state->entity
|
||||
&& $entity->moderation_state->entity->isPublishedState();
|
||||
&& $entity->moderation_state->value
|
||||
&& $workflow->getState($entity->moderation_state->value)->isPublishedState();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWorkflowForEntity(ContentEntityInterface $entity) {
|
||||
$bundles = $this->bundleInfo->getBundleInfo($entity->getEntityTypeId());
|
||||
if (isset($bundles[$entity->bundle()]['workflow'])) {
|
||||
return $this->entityTypeManager->getStorage('workflow')->load($bundles[$entity->bundle()]['workflow']);
|
||||
};
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -126,4 +126,15 @@ interface ModerationInformationInterface {
|
|||
*/
|
||||
public function isLiveRevision(ContentEntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Gets the workflow for the given content entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The content entity to get the workflow for.
|
||||
*
|
||||
* @return \Drupal\workflows\WorkflowInterface|null
|
||||
* The workflow entity. NULL if there is no workflow.
|
||||
*/
|
||||
public function getWorkflowForEntity(ContentEntityInterface $entity);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
|
||||
/**
|
||||
* Access controller for the Moderation State entity.
|
||||
*
|
||||
* @see \Drupal\workbench_moderation\Entity\ModerationState.
|
||||
*/
|
||||
class ModerationStateAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
$admin_access = parent::checkAccess($entity, $operation, $account);
|
||||
|
||||
// Allow view with other permission.
|
||||
if ($operation === 'view') {
|
||||
return AccessResult::allowedIfHasPermission($account, 'view moderation states')->orIf($admin_access);
|
||||
}
|
||||
|
||||
return $admin_access;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for defining Moderation state entities.
|
||||
*/
|
||||
interface ModerationStateInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Determines if content updated to this state should be published.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if content updated to this state should be published.
|
||||
*/
|
||||
public function isPublishedState();
|
||||
|
||||
/**
|
||||
* Determines if content updated to this state should be the default revision.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if content in this state should be the default revision.
|
||||
*/
|
||||
public function isDefaultRevisionState();
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\Core\Config\Entity\DraggableListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Provides a listing of Moderation state entities.
|
||||
*/
|
||||
class ModerationStateListBuilder extends DraggableListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'moderation_state_admin_overview_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = $this->t('Moderation state');
|
||||
$header['id'] = $this->t('Machine name');
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['label'] = $entity->label();
|
||||
$row['id']['#markup'] = $entity->id();
|
||||
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for defining Moderation state transition entities.
|
||||
*/
|
||||
interface ModerationStateTransitionInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Gets the from state for the given transition.
|
||||
*
|
||||
* @return string
|
||||
* The moderation state ID for the from state.
|
||||
*/
|
||||
public function getFromState();
|
||||
|
||||
/**
|
||||
* Gets the to state for the given transition.
|
||||
*
|
||||
* @return string
|
||||
* The moderation state ID for the to state.
|
||||
*/
|
||||
public function getToState();
|
||||
|
||||
/**
|
||||
* Gets the weight for the given transition.
|
||||
*
|
||||
* @return int
|
||||
* The weight of this transition.
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\Core\Config\Entity\DraggableListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\user\RoleStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a listing of Moderation state transition entities.
|
||||
*/
|
||||
class ModerationStateTransitionListBuilder extends DraggableListBuilder {
|
||||
|
||||
/**
|
||||
* Moderation state entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $stateStorage;
|
||||
|
||||
/**
|
||||
* The role storage.
|
||||
*
|
||||
* @var \Drupal\user\RoleStorageInterface
|
||||
*/
|
||||
protected $roleStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity.manager')->getStorage($entity_type->id()),
|
||||
$container->get('entity.manager')->getStorage('moderation_state'),
|
||||
$container->get('entity.manager')->getStorage('user_role')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new ModerationStateTransitionListBuilder.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* Entity Type.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $transition_storage
|
||||
* Moderation state transition entity storage.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $state_storage
|
||||
* Moderation state entity storage.
|
||||
* @param \Drupal\user\RoleStorageInterface $role_storage
|
||||
* The role storage.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $transition_storage, EntityStorageInterface $state_storage, RoleStorageInterface $role_storage) {
|
||||
parent::__construct($entity_type, $transition_storage);
|
||||
$this->stateStorage = $state_storage;
|
||||
$this->roleStorage = $role_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'content_moderation_transition_list';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['to'] = $this->t('To state');
|
||||
$header['label'] = $this->t('Button label');
|
||||
$header['roles'] = $this->t('Allowed roles');
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['to']['#markup'] = $this->stateStorage->load($entity->getToState())->label();
|
||||
$row['label'] = $entity->label();
|
||||
$row['roles']['#markup'] = implode(', ', user_role_names(FALSE, 'use ' . $entity->id() . ' transition'));
|
||||
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build = parent::render();
|
||||
|
||||
$build['item'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('On this screen you can define <em>transitions</em>. Every time an entity is saved, it undergoes a transition. It is not possible to save an entity if it tries do a transition not defined here. Transitions do not necessarily mean a state change, it is possible to transition from a state to the same state but that transition needs to be defined here as well.'),
|
||||
'#weight' => -5,
|
||||
];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$this->entities = $this->load();
|
||||
|
||||
// Get all the moderation states and sort them by weight.
|
||||
$states = $this->stateStorage->loadMultiple();
|
||||
uasort($states, array($this->entityType->getClass(), 'sort'));
|
||||
|
||||
/** @var \Drupal\content_moderation\ModerationStateTransitionInterface $entity */
|
||||
$groups = array_fill_keys(array_keys($states), []);
|
||||
foreach ($this->entities as $entity) {
|
||||
$groups[$entity->getFromState()][] = $entity;
|
||||
}
|
||||
|
||||
foreach ($groups as $group_name => $entities) {
|
||||
$form[$group_name] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('From @state to...', ['@state' => $states[$group_name]->label()]),
|
||||
// Make sure that the first group is always open.
|
||||
'#open' => $group_name === array_keys($groups)[0],
|
||||
];
|
||||
|
||||
$form[$group_name][$this->entitiesKey] = array(
|
||||
'#type' => 'table',
|
||||
'#header' => $this->buildHeader(),
|
||||
'#empty' => t('There is no @label yet.', array('@label' => $this->entityType->getLabel())),
|
||||
'#tabledrag' => array(
|
||||
array(
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'weight',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$delta = 10;
|
||||
// Change the delta of the weight field if have more than 20 entities.
|
||||
if (!empty($this->weightKey)) {
|
||||
$count = count($this->entities);
|
||||
if ($count > 20) {
|
||||
$delta = ceil($count / 2);
|
||||
}
|
||||
}
|
||||
foreach ($entities as $entity) {
|
||||
$row = $this->buildRow($entity);
|
||||
if (isset($row['label'])) {
|
||||
$row['label'] = array('#markup' => $row['label']);
|
||||
}
|
||||
if (isset($row['weight'])) {
|
||||
$row['weight']['#delta'] = $delta;
|
||||
}
|
||||
$form[$group_name][$this->entitiesKey][$entity->id()] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$form['actions']['#type'] = 'actions';
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save order'),
|
||||
'#button_type' => 'primary',
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
|
@ -95,7 +95,7 @@ class EntityRevisionConverter extends EntityConverter {
|
|||
// If the entity type is translatable, ensure we return the proper
|
||||
// translation object for the current context.
|
||||
if ($latest_revision instanceof EntityInterface && $entity instanceof TranslatableInterface) {
|
||||
$latest_revision = $this->entityManager->getTranslationFromContext($latest_revision, NULL, array('operation' => 'entity_upcast'));
|
||||
$latest_revision = $this->entityManager->getTranslationFromContext($latest_revision, NULL, ['operation' => 'entity_upcast']);
|
||||
}
|
||||
|
||||
if ($latest_revision->isRevisionTranslationAffected()) {
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
use Drupal\content_moderation\Entity\ModerationStateTransition;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Defines a class for dynamic permissions based on transitions.
|
||||
|
@ -20,24 +19,20 @@ class Permissions {
|
|||
* The transition permissions.
|
||||
*/
|
||||
public function transitionPermissions() {
|
||||
// @todo https://www.drupal.org/node/2779933 write a test for this.
|
||||
$perms = [];
|
||||
/* @var \Drupal\content_moderation\ModerationStateInterface[] $states */
|
||||
$states = ModerationState::loadMultiple();
|
||||
/* @var \Drupal\content_moderation\ModerationStateTransitionInterface $transition */
|
||||
foreach (ModerationStateTransition::loadMultiple() as $id => $transition) {
|
||||
$perms['use ' . $id . ' transition'] = [
|
||||
'title' => $this->t('Use the %transition_name transition', [
|
||||
'%transition_name' => $transition->label(),
|
||||
]),
|
||||
'description' => $this->t('Move content from %from state to %to state.', [
|
||||
'%from' => $states[$transition->getFromState()]->label(),
|
||||
'%to' => $states[$transition->getToState()]->label(),
|
||||
]),
|
||||
];
|
||||
$permissions = [];
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
foreach (Workflow::loadMultipleByType('content_moderation') as $id => $workflow) {
|
||||
foreach ($workflow->getTransitions() as $transition) {
|
||||
$permissions['use ' . $workflow->id() . ' transition ' . $transition->id()] = [
|
||||
'title' => $this->t('Use %transition transition from %workflow workflow.', [
|
||||
'%transition' => $transition->label(),
|
||||
'%workflow' => $workflow->label(),
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $perms;
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -53,23 +53,14 @@ class ModerationOptOutPublishNode extends PublishNode implements ContainerFactor
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
|
||||
drupal_set_message($this->t('One or more entities were skipped as they are under moderation and may not be directly published or unpublished.'));
|
||||
return;
|
||||
drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly published.", ['@bundle' => node_get_type_label($entity), '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
|
||||
$result = AccessResult::forbidden();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
parent::execute($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = parent::access($object, $account, TRUE)
|
||||
->andif(AccessResult::forbiddenIf($this->moderationInfo->isModeratedEntity($object))->addCacheableDependency($object));
|
||||
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
return parent::access($entity, $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -53,23 +53,14 @@ class ModerationOptOutUnpublishNode extends UnpublishNode implements ContainerFa
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
|
||||
drupal_set_message($this->t('One or more entities were skipped as they are under moderation and may not be directly published or unpublished.'));
|
||||
return;
|
||||
drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly unpublished.", ['@bundle' => node_get_type_label($entity), '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
|
||||
$result = AccessResult::forbidden();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
parent::execute($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = parent::access($object, $account, TRUE)
|
||||
->andif(AccessResult::forbiddenIf($this->moderationInfo->isModeratedEntity($object))->addCacheableDependency($object));
|
||||
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
return parent::access($entity, $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'content_moderation_state' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "content_moderation_state",
|
||||
* label = @Translation("Content moderation state"),
|
||||
* field_types = {
|
||||
* "string",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ContentModerationStateFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The moderation information service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\ModerationInformationInterface
|
||||
*/
|
||||
protected $moderationInformation;
|
||||
|
||||
/**
|
||||
* Create an instance of ContentModerationStateFormatter.
|
||||
*/
|
||||
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, ModerationInformationInterface $moderation_information) {
|
||||
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
|
||||
$this->moderationInformation = $moderation_information;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$configuration['field_definition'],
|
||||
$configuration['settings'],
|
||||
$configuration['label'],
|
||||
$configuration['view_mode'],
|
||||
$configuration['third_party_settings'],
|
||||
$container->get('content_moderation.moderation_information')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
$workflow = $this->moderationInformation->getWorkflowForEntity($items->getEntity());
|
||||
foreach ($items as $delta => $item) {
|
||||
$elements[$delta] = [
|
||||
'#markup' => $workflow->getState($item->value)->label(),
|
||||
];
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
return $field_definition->getName() === 'moderation_state' && $field_definition->getTargetEntityTypeId() !== 'content_moderation_state';
|
||||
}
|
||||
|
||||
}
|
|
@ -3,9 +3,7 @@
|
|||
namespace Drupal\content_moderation\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget;
|
||||
|
@ -23,7 +21,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* id = "moderation_state_default",
|
||||
* label = @Translation("Moderation state"),
|
||||
* field_types = {
|
||||
* "entity_reference"
|
||||
* "string"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
|
@ -36,20 +34,6 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
|||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Moderation state transition entity query.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
protected $moderationStateTransitionEntityQuery;
|
||||
|
||||
/**
|
||||
* Moderation state storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $moderationStateStorage;
|
||||
|
||||
/**
|
||||
* Moderation information service.
|
||||
*
|
||||
|
@ -64,13 +48,6 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
|||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Moderation state transition storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $moderationStateTransitionStorage;
|
||||
|
||||
/**
|
||||
* Moderation state transition validation service.
|
||||
*
|
||||
|
@ -95,22 +72,13 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
|||
* Current user service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* Entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $moderation_state_storage
|
||||
* Moderation state storage.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $moderation_state_transition_storage
|
||||
* Moderation state transition storage.
|
||||
* @param \Drupal\Core\Entity\Query\QueryInterface $entity_query
|
||||
* Moderation transition entity query service.
|
||||
* @param \Drupal\content_moderation\ModerationInformation $moderation_information
|
||||
* Moderation information service.
|
||||
* @param \Drupal\content_moderation\StateTransitionValidation $validator
|
||||
* Moderation state transition validation service
|
||||
*/
|
||||
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, EntityStorageInterface $moderation_state_storage, EntityStorageInterface $moderation_state_transition_storage, QueryInterface $entity_query, ModerationInformation $moderation_information, StateTransitionValidation $validator) {
|
||||
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidation $validator) {
|
||||
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
|
||||
$this->moderationStateTransitionEntityQuery = $entity_query;
|
||||
$this->moderationStateTransitionStorage = $moderation_state_transition_storage;
|
||||
$this->moderationStateStorage = $moderation_state_storage;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->currentUser = $current_user;
|
||||
$this->moderationInformation = $moderation_information;
|
||||
|
@ -129,9 +97,6 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
|||
$configuration['third_party_settings'],
|
||||
$container->get('current_user'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_type.manager')->getStorage('moderation_state'),
|
||||
$container->get('entity_type.manager')->getStorage('moderation_state_transition'),
|
||||
$container->get('entity.query')->get('moderation_state_transition', 'AND'),
|
||||
$container->get('content_moderation.moderation_information'),
|
||||
$container->get('content_moderation.state_transition_validation')
|
||||
);
|
||||
|
@ -151,19 +116,18 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
|||
return $element + ['#access' => FALSE];
|
||||
}
|
||||
|
||||
$default = $items->get($delta)->value ?: $bundle_entity->getThirdPartySetting('content_moderation', 'default_moderation_state', FALSE);
|
||||
/** @var \Drupal\content_moderation\ModerationStateInterface $default_state */
|
||||
$default_state = $this->entityTypeManager->getStorage('moderation_state')->load($default);
|
||||
if (!$default || !$default_state) {
|
||||
$workflow = $this->moderationInformation->getWorkflowForEntity($entity);
|
||||
$default = $items->get($delta)->value ? $workflow->getState($items->get($delta)->value) : $workflow->getInitialState();
|
||||
if (!$default) {
|
||||
throw new \UnexpectedValueException(sprintf('The %s bundle has an invalid moderation state configuration, moderation states are enabled but no default is set.', $bundle_entity->label()));
|
||||
}
|
||||
|
||||
/** @var \Drupal\workflows\Transition[] $transitions */
|
||||
$transitions = $this->validator->getValidTransitions($entity, $this->currentUser);
|
||||
|
||||
$target_states = [];
|
||||
/** @var \Drupal\content_moderation\Entity\ModerationStateTransition $transition */
|
||||
foreach ($transitions as $transition) {
|
||||
$target_states[$transition->getToState()] = $transition->label();
|
||||
$target_states[$transition->to()->id()] = $transition->label();
|
||||
}
|
||||
|
||||
// @todo https://www.drupal.org/node/2779933 write a test for this.
|
||||
|
@ -171,11 +135,11 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
|||
'#access' => FALSE,
|
||||
'#type' => 'select',
|
||||
'#options' => $target_states,
|
||||
'#default_value' => $default,
|
||||
'#published' => $default ? $default_state->isPublishedState() : FALSE,
|
||||
'#default_value' => $default->id(),
|
||||
'#published' => $default->isPublishedState(),
|
||||
'#key_column' => $this->column,
|
||||
];
|
||||
$element['#element_validate'][] = array(get_class($this), 'validateElement');
|
||||
$element['#element_validate'][] = [get_class($this), 'validateElement'];
|
||||
|
||||
// Use the dropbutton.
|
||||
$element['#process'][] = [get_called_class(), 'processActions'];
|
||||
|
@ -197,7 +161,7 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
|||
public static function updateStatus($entity_type_id, ContentEntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
$element = $form_state->getTriggeringElement();
|
||||
if (isset($element['#moderation_state'])) {
|
||||
$entity->moderation_state->target_id = $element['#moderation_state'];
|
||||
$entity->moderation_state->value = $element['#moderation_state'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,7 +213,7 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
return parent::isApplicable($field_definition) && $field_definition->getName() === 'moderation_state';
|
||||
return $field_definition->getName() === 'moderation_state';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace Drupal\content_moderation\Plugin\Field;
|
||||
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
use Drupal\Core\Field\EntityReferenceFieldItemList;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Field\FieldItemList;
|
||||
|
||||
/**
|
||||
* A computed field that provides a content entity's moderation state.
|
||||
|
@ -11,60 +11,75 @@ use Drupal\Core\Field\EntityReferenceFieldItemList;
|
|||
* It links content entities to a moderation state configuration entity via a
|
||||
* moderation state content entity.
|
||||
*/
|
||||
class ModerationStateFieldItemList extends EntityReferenceFieldItemList {
|
||||
class ModerationStateFieldItemList extends FieldItemList {
|
||||
|
||||
/**
|
||||
* Gets the moderation state entity linked to a content entity revision.
|
||||
* Gets the moderation state ID linked to a content entity revision.
|
||||
*
|
||||
* @return \Drupal\content_moderation\ModerationStateInterface|null
|
||||
* The moderation state configuration entity linked to a content entity
|
||||
* revision.
|
||||
* @return string|null
|
||||
* The moderation state ID linked to a content entity revision.
|
||||
*/
|
||||
protected function getModerationState() {
|
||||
protected function getModerationStateId() {
|
||||
$entity = $this->getEntity();
|
||||
|
||||
if (!\Drupal::service('content_moderation.moderation_information')->shouldModerateEntitiesOfBundle($entity->getEntityType(), $entity->bundle())) {
|
||||
/** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
|
||||
$moderation_info = \Drupal::service('content_moderation.moderation_information');
|
||||
if (!$moderation_info->shouldModerateEntitiesOfBundle($entity->getEntityType(), $entity->bundle())) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ($entity->id() && $entity->getRevisionId()) {
|
||||
$revisions = \Drupal::service('entity.query')->get('content_moderation_state')
|
||||
->condition('content_entity_type_id', $entity->getEntityTypeId())
|
||||
->condition('content_entity_id', $entity->id())
|
||||
->condition('content_entity_revision_id', $entity->getRevisionId())
|
||||
->allRevisions()
|
||||
->sort('revision_id', 'DESC')
|
||||
->execute();
|
||||
|
||||
if ($revision_to_load = key($revisions)) {
|
||||
/** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */
|
||||
$content_moderation_state = \Drupal::entityTypeManager()
|
||||
->getStorage('content_moderation_state')
|
||||
->loadRevision($revision_to_load);
|
||||
|
||||
// Return the correct translation.
|
||||
if ($entity->getEntityType()->hasKey('langcode')) {
|
||||
$langcode = $entity->language()->getId();
|
||||
if (!$content_moderation_state->hasTranslation($langcode)) {
|
||||
$content_moderation_state->addTranslation($langcode);
|
||||
}
|
||||
if ($content_moderation_state->language()->getId() !== $langcode) {
|
||||
$content_moderation_state = $content_moderation_state->getTranslation($langcode);
|
||||
}
|
||||
}
|
||||
|
||||
return $content_moderation_state->get('moderation_state')->entity;
|
||||
}
|
||||
// Existing entities will have a corresponding content_moderation_state
|
||||
// entity associated with them.
|
||||
if (!$entity->isNew() && $content_moderation_state = $this->loadContentModerationStateRevision($entity)) {
|
||||
return $content_moderation_state->moderation_state->value;
|
||||
}
|
||||
|
||||
// It is possible that the bundle does not exist at this point. For example,
|
||||
// the node type form creates a fake Node entity to get default values.
|
||||
// @see \Drupal\node\NodeTypeForm::form()
|
||||
$bundle_entity = \Drupal::entityTypeManager()
|
||||
->getStorage($entity->getEntityType()->getBundleEntityType())
|
||||
->load($entity->bundle());
|
||||
if ($bundle_entity && ($default = $bundle_entity->getThirdPartySetting('content_moderation', 'default_moderation_state'))) {
|
||||
return ModerationState::load($default);
|
||||
$workflow = $moderation_info->getWorkflowForEntity($entity);
|
||||
return $workflow ? $workflow->getInitialState()->id() : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the content moderation state revision associated with an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity the content moderation state entity will be loaded from.
|
||||
*
|
||||
* @return \Drupal\content_moderation\ContentModerationStateInterface|null
|
||||
* The content_moderation_state revision or FALSE if none exists.
|
||||
*/
|
||||
protected function loadContentModerationStateRevision(ContentEntityInterface $entity) {
|
||||
$moderation_info = \Drupal::service('content_moderation.moderation_information');
|
||||
$content_moderation_storage = \Drupal::entityTypeManager()->getStorage('content_moderation_state');
|
||||
|
||||
$revisions = \Drupal::service('entity.query')->get('content_moderation_state')
|
||||
->condition('content_entity_type_id', $entity->getEntityTypeId())
|
||||
->condition('content_entity_id', $entity->id())
|
||||
// Ensure the correct revision is loaded in scenarios where a revision is
|
||||
// being reverted.
|
||||
->condition('content_entity_revision_id', $entity->isNewRevision() ? $entity->getLoadedRevisionId() : $entity->getRevisionId())
|
||||
->condition('workflow', $moderation_info->getWorkflowForEntity($entity)->id())
|
||||
->allRevisions()
|
||||
->sort('revision_id', 'DESC')
|
||||
->execute();
|
||||
if (empty($revisions)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */
|
||||
$content_moderation_state = $content_moderation_storage->loadRevision(key($revisions));
|
||||
if ($entity->getEntityType()->hasKey('langcode')) {
|
||||
$langcode = $entity->language()->getId();
|
||||
if (!$content_moderation_state->hasTranslation($langcode)) {
|
||||
$content_moderation_state->addTranslation($langcode);
|
||||
}
|
||||
if ($content_moderation_state->language()->getId() !== $langcode) {
|
||||
$content_moderation_state = $content_moderation_state->getTranslation($langcode);
|
||||
}
|
||||
}
|
||||
return $content_moderation_state;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,10 +108,11 @@ class ModerationStateFieldItemList extends EntityReferenceFieldItemList {
|
|||
// Compute the value of the moderation state.
|
||||
$index = 0;
|
||||
if (!isset($this->list[$index]) || $this->list[$index]->isEmpty()) {
|
||||
$moderation_state = $this->getModerationState();
|
||||
|
||||
$moderation_state = $this->getModerationStateId();
|
||||
// Do not store NULL values in the static cache.
|
||||
if ($moderation_state) {
|
||||
$this->list[$index] = $this->createItem($index, ['entity' => $moderation_state]);
|
||||
$this->list[$index] = $this->createItem($index, $moderation_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\content_moderation\Plugin\Menu;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Menu\LocalTaskDefault;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
@ -25,9 +26,9 @@ class EditTab extends LocalTaskDefault implements ContainerFactoryPluginInterfac
|
|||
protected $moderationInfo;
|
||||
|
||||
/**
|
||||
* The entity.
|
||||
* The entity if determinable from the route or FALSE.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityInterface
|
||||
* @var \Drupal\Core\Entity\ContentEntityInterface|FALSE
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
|
@ -69,8 +70,8 @@ class EditTab extends LocalTaskDefault implements ContainerFactoryPluginInterfac
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteParameters(RouteMatchInterface $route_match) {
|
||||
// Override the node here with the latest revision.
|
||||
$this->entity = $route_match->getParameter($this->pluginDefinition['entity_type_id']);
|
||||
$entity_parameter = $route_match->getParameter($this->pluginDefinition['entity_type_id']);
|
||||
$this->entity = $entity_parameter instanceof ContentEntityInterface ? $route_match->getParameter($this->pluginDefinition['entity_type_id']) : FALSE;
|
||||
return parent::getRouteParameters($route_match);
|
||||
}
|
||||
|
||||
|
@ -78,12 +79,11 @@ class EditTab extends LocalTaskDefault implements ContainerFactoryPluginInterfac
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle() {
|
||||
if (!$this->moderationInfo->isModeratedEntity($this->entity)) {
|
||||
// Moderation isn't enabled.
|
||||
// If the entity couldn't be loaded or moderation isn't enabled.
|
||||
if (!$this->entity || !$this->moderationInfo->isModeratedEntity($this->entity)) {
|
||||
return parent::getTitle();
|
||||
}
|
||||
|
||||
// @todo https://www.drupal.org/node/2779933 write a test for this.
|
||||
return $this->moderationInfo->isLiveRevision($this->entity)
|
||||
? $this->t('New draft')
|
||||
: $this->t('Edit draft');
|
||||
|
@ -93,11 +93,12 @@ class EditTab extends LocalTaskDefault implements ContainerFactoryPluginInterfac
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
// @todo https://www.drupal.org/node/2779933 write a test for this.
|
||||
$tags = parent::getCacheTags();
|
||||
// Tab changes if node or node-type is modified.
|
||||
$tags = array_merge($tags, $this->entity->getCacheTags());
|
||||
$tags[] = $this->entity->getEntityType()->getBundleEntityType() . ':' . $this->entity->bundle();
|
||||
if ($this->entity) {
|
||||
$tags = array_merge($tags, $this->entity->getCacheTags());
|
||||
$tags[] = $this->entity->getEntityType()->getBundleEntityType() . ':' . $this->entity->bundle();
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Drupal\content_moderation\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\content_moderation\Entity\ModerationState as ModerationStateEntity;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
|
@ -93,21 +92,13 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements
|
|||
$original_entity = $original_entity->getTranslation($entity->language()->getId());
|
||||
}
|
||||
|
||||
if ($entity->moderation_state->target_id) {
|
||||
$new_state_id = $entity->moderation_state->target_id;
|
||||
}
|
||||
else {
|
||||
$new_state_id = $default = $this->entityTypeManager
|
||||
->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle())
|
||||
->getThirdPartySetting('content_moderation', 'default_moderation_state');
|
||||
}
|
||||
if ($new_state_id) {
|
||||
$new_state = ModerationStateEntity::load($new_state_id);
|
||||
}
|
||||
// @todo - what if $new_state_id references something that does not exist or
|
||||
$workflow = $this->moderationInformation->getWorkflowForEntity($entity);
|
||||
$new_state = $workflow->getState($entity->moderation_state->value) ?: $workflow->getInitialState();
|
||||
$original_state = $workflow->getState($original_entity->moderation_state->value);
|
||||
// @todo - what if $new_state references something that does not exist or
|
||||
// is null.
|
||||
if (!$this->validation->isTransitionAllowed($original_entity->moderation_state->entity, $new_state)) {
|
||||
$this->context->addViolation($constraint->message, ['%from' => $original_entity->moderation_state->entity->label(), '%to' => $new_state->label()]);
|
||||
if (!$original_state->canTransitionTo($new_state->id())) {
|
||||
$this->context->addViolation($constraint->message, ['%from' => $original_state->label(), '%to' => $new_state->label()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,9 +117,9 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements
|
|||
protected function isFirstTimeModeration(EntityInterface $entity) {
|
||||
$original_entity = $this->moderationInformation->getLatestRevision($entity->getEntityTypeId(), $entity->id());
|
||||
|
||||
$original_id = $original_entity->moderation_state->target_id;
|
||||
$original_id = $original_entity->moderation_state;
|
||||
|
||||
return !($entity->moderation_state->target_id && $original_entity && $original_id);
|
||||
return !($entity->moderation_state && $original_entity && $original_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,292 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Plugin\WorkflowType;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\content_moderation\ContentModerationState;
|
||||
use Drupal\workflows\Plugin\WorkflowTypeBase;
|
||||
use Drupal\workflows\StateInterface;
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Attaches workflows to content entity types and their bundles.
|
||||
*
|
||||
* @WorkflowType(
|
||||
* id = "content_moderation",
|
||||
* label = @Translation("Content moderation"),
|
||||
* required_states = {
|
||||
* "draft",
|
||||
* "published",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class ContentModeration extends WorkflowTypeBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creates an instance of the ContentModeration WorkflowType plugin.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initializeWorkflow(WorkflowInterface $workflow) {
|
||||
$workflow
|
||||
->addState('draft', $this->t('Draft'))
|
||||
->setStateWeight('draft', -5)
|
||||
->addState('published', $this->t('Published'))
|
||||
->setStateWeight('published', 0)
|
||||
->addTransition('create_new_draft', $this->t('Create New Draft'), ['draft', 'published'], 'draft')
|
||||
->addTransition('publish', $this->t('Publish'), ['draft', 'published'], 'published');
|
||||
return $workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkWorkflowAccess(WorkflowInterface $entity, $operation, AccountInterface $account) {
|
||||
if ($operation === 'view') {
|
||||
return AccessResult::allowedIfHasPermission($account, 'view content moderation');
|
||||
}
|
||||
return parent::checkWorkflowAccess($entity, $operation, $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decorateState(StateInterface $state) {
|
||||
if (isset($this->configuration['states'][$state->id()])) {
|
||||
$state = new ContentModerationState($state, $this->configuration['states'][$state->id()]['published'], $this->configuration['states'][$state->id()]['default_revision']);
|
||||
}
|
||||
else {
|
||||
$state = new ContentModerationState($state);
|
||||
}
|
||||
return $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildStateConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, StateInterface $state = NULL) {
|
||||
/** @var \Drupal\content_moderation\ContentModerationState $state */
|
||||
$is_required_state = isset($state) ? in_array($state->id(), $this->getRequiredStates(), TRUE) : FALSE;
|
||||
|
||||
$form = [];
|
||||
$form['published'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Published'),
|
||||
'#description' => $this->t('When content reaches this state it should be published.'),
|
||||
'#default_value' => isset($state) ? $state->isPublishedState() : FALSE,
|
||||
'#disabled' => $is_required_state,
|
||||
];
|
||||
|
||||
$form['default_revision'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Default revision'),
|
||||
'#description' => $this->t('When content reaches this state it should be made the default revision; this is implied for published states.'),
|
||||
'#default_value' => isset($state) ? $state->isDefaultRevisionState() : FALSE,
|
||||
'#disabled' => $is_required_state,
|
||||
// @todo Add form #state to force "make default" on when "published" is
|
||||
// on for a state.
|
||||
// @see https://www.drupal.org/node/2645614
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity types the workflow is applied to.
|
||||
*
|
||||
* @return string[]
|
||||
* The entity types the workflow is applied to.
|
||||
*/
|
||||
public function getEntityTypes() {
|
||||
return array_keys($this->configuration['entity_types']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets any bundles the workflow is applied to for the given entity type.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID to get the bundles for.
|
||||
*
|
||||
* @return string[]
|
||||
* The bundles of the entity type the workflow is applied to or an empty
|
||||
* array if the entity type is not applied to the workflow.
|
||||
*/
|
||||
public function getBundlesForEntityType($entity_type_id) {
|
||||
return isset($this->configuration['entity_types'][$entity_type_id]) ? $this->configuration['entity_types'][$entity_type_id] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the workflow applies to the supplied entity type and bundle.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID to check.
|
||||
* @param string $bundle_id
|
||||
* The bundle ID to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the workflow applies to the supplied entity type ID and bundle
|
||||
* ID. FALSE if not.
|
||||
*/
|
||||
public function appliesToEntityTypeAndBundle($entity_type_id, $bundle_id) {
|
||||
return in_array($bundle_id, $this->getBundlesForEntityType($entity_type_id), TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an entity type ID / bundle ID from the workflow.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID to remove.
|
||||
* @param string $bundle_id
|
||||
* The bundle ID to remove.
|
||||
*/
|
||||
public function removeEntityTypeAndBundle($entity_type_id, $bundle_id) {
|
||||
$key = array_search($bundle_id, $this->configuration['entity_types'][$entity_type_id], TRUE);
|
||||
if ($key !== FALSE) {
|
||||
unset($this->configuration['entity_types'][$entity_type_id][$key]);
|
||||
if (empty($this->configuration['entity_types'][$entity_type_id])) {
|
||||
unset($this->configuration['entity_types'][$entity_type_id]);
|
||||
}
|
||||
else {
|
||||
$this->configuration['entity_types'][$entity_type_id] = array_values($this->configuration['entity_types'][$entity_type_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entity type ID / bundle ID to the workflow.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID to add. It is responsibility of the caller to provide
|
||||
* a valid entity type ID.
|
||||
* @param string $bundle_id
|
||||
* The bundle ID to add. It is responsibility of the caller to provide a
|
||||
* valid bundle ID.
|
||||
*/
|
||||
public function addEntityTypeAndBundle($entity_type_id, $bundle_id) {
|
||||
if (!$this->appliesToEntityTypeAndBundle($entity_type_id, $bundle_id)) {
|
||||
$this->configuration['entity_types'][$entity_type_id][] = $bundle_id;
|
||||
sort($this->configuration['entity_types'][$entity_type_id]);
|
||||
ksort($this->configuration['entity_types']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
// This plugin does not store anything per transition.
|
||||
return [
|
||||
'states' => [
|
||||
'draft' => [
|
||||
'published' => FALSE,
|
||||
'default_revision' => FALSE,
|
||||
],
|
||||
'published' => [
|
||||
'published' => TRUE,
|
||||
'default_revision' => TRUE,
|
||||
],
|
||||
],
|
||||
'entity_types' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
foreach ($this->getEntityTypes() as $entity_type_id) {
|
||||
$entity_definition = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
foreach ($this->getBundlesForEntityType($entity_type_id) as $bundle) {
|
||||
$dependency = $entity_definition->getBundleConfigDependency($bundle);
|
||||
$dependencies[$dependency['type']][] = $dependency['name'];
|
||||
}
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
$changed = parent::onDependencyRemoval($dependencies);
|
||||
|
||||
// When bundle config entities are removed, ensure they are cleaned up from
|
||||
// the workflow.
|
||||
foreach ($dependencies['config'] as $removed_config) {
|
||||
if ($entity_type_id = $removed_config->getEntityType()->getBundleOf()) {
|
||||
$bundle_id = $removed_config->id();
|
||||
$this->removeEntityTypeAndBundle($entity_type_id, $bundle_id);
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// When modules that provide entity types are removed, ensure they are also
|
||||
// removed from the workflow.
|
||||
if (!empty($dependencies['module'])) {
|
||||
// Gather all entity definitions provided by the dependent modules which
|
||||
// are being removed.
|
||||
$module_entity_definitions = [];
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $entity_definition) {
|
||||
if (in_array($entity_definition->getProvider(), $dependencies['module'])) {
|
||||
$module_entity_definitions[] = $entity_definition;
|
||||
}
|
||||
}
|
||||
|
||||
// For all entity types provided by the uninstalled modules, remove any
|
||||
// configuration for those types.
|
||||
foreach ($module_entity_definitions as $module_entity_definition) {
|
||||
foreach ($this->getBundlesForEntityType($module_entity_definition->id()) as $bundle) {
|
||||
$this->removeEntityTypeAndBundle($module_entity_definition->id(), $bundle);
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration() {
|
||||
$configuration = parent::getConfiguration();
|
||||
// Ensure that states and entity types are ordered consistently.
|
||||
ksort($configuration['states']);
|
||||
ksort($configuration['entity_types']);
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
}
|
|
@ -111,7 +111,7 @@ class EntityModerationRouteProvider implements EntityRouteProviderInterface, Ent
|
|||
* type does not support fields.
|
||||
*/
|
||||
protected function getEntityTypeIdKeyType(EntityTypeInterface $entity_type) {
|
||||
if (!$entity_type->isSubclassOf(FieldableEntityInterface::class)) {
|
||||
if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class EntityTypeModerationRouteProvider implements EntityRouteProviderInterface
|
|||
'_entity_form' => "{$entity_type_id}.moderation",
|
||||
'_title' => 'Moderation',
|
||||
])
|
||||
->setRequirement('_permission', 'administer moderation states')
|
||||
->setRequirement('_permission', 'administer content moderation')
|
||||
->setOption('parameters', [
|
||||
$entity_type_id => ['type' => 'entity:' . $entity_type_id],
|
||||
]);
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\content_moderation\Entity\ModerationStateTransition;
|
||||
use Drupal\workflows\Transition;
|
||||
|
||||
/**
|
||||
* Validates whether a certain state transition is allowed.
|
||||
|
@ -14,18 +12,11 @@ use Drupal\content_moderation\Entity\ModerationStateTransition;
|
|||
class StateTransitionValidation implements StateTransitionValidationInterface {
|
||||
|
||||
/**
|
||||
* Entity type manager.
|
||||
* The moderation information service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
* @var \Drupal\content_moderation\ModerationInformationInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Entity query factory.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $queryFactory;
|
||||
protected $moderationInfo;
|
||||
|
||||
/**
|
||||
* Stores the possible state transitions.
|
||||
|
@ -37,211 +28,23 @@ class StateTransitionValidation implements StateTransitionValidationInterface {
|
|||
/**
|
||||
* Constructs a new StateTransitionValidation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
|
||||
* The entity query factory.
|
||||
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
|
||||
* The moderation information service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, QueryFactory $query_factory) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->queryFactory = $query_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a mapping of possible transitions.
|
||||
*
|
||||
* This method is uncached and will recalculate the list on every request.
|
||||
* In most cases you want to use getPossibleTransitions() instead.
|
||||
*
|
||||
* @see static::getPossibleTransitions()
|
||||
*
|
||||
* @return array[]
|
||||
* An array containing all possible transitions. Each entry is keyed by the
|
||||
* "from" state, and the value is an array of all legal "to" states based
|
||||
* on the currently defined transition objects.
|
||||
*/
|
||||
protected function calculatePossibleTransitions() {
|
||||
$transitions = $this->transitionStorage()->loadMultiple();
|
||||
|
||||
$possible_transitions = [];
|
||||
/** @var \Drupal\content_moderation\ModerationStateTransitionInterface $transition */
|
||||
foreach ($transitions as $transition) {
|
||||
$possible_transitions[$transition->getFromState()][] = $transition->getToState();
|
||||
}
|
||||
return $possible_transitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mapping of possible transitions.
|
||||
*
|
||||
* @return array[]
|
||||
* An array containing all possible transitions. Each entry is keyed by the
|
||||
* "from" state, and the value is an array of all legal "to" states based
|
||||
* on the currently defined transition objects.
|
||||
*/
|
||||
protected function getPossibleTransitions() {
|
||||
if (empty($this->possibleTransitions)) {
|
||||
$this->possibleTransitions = $this->calculatePossibleTransitions();
|
||||
}
|
||||
return $this->possibleTransitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValidTransitionTargets(ContentEntityInterface $entity, AccountInterface $user) {
|
||||
$bundle = $this->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle());
|
||||
|
||||
$states_for_bundle = $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', []);
|
||||
|
||||
/** @var \Drupal\content_moderation\Entity\ModerationState $current_state */
|
||||
$current_state = $entity->moderation_state->entity;
|
||||
|
||||
$all_transitions = $this->getPossibleTransitions();
|
||||
$destination_ids = $all_transitions[$current_state->id()];
|
||||
|
||||
$destination_ids = array_intersect($states_for_bundle, $destination_ids);
|
||||
$destinations = $this->entityTypeManager->getStorage('moderation_state')->loadMultiple($destination_ids);
|
||||
|
||||
return array_filter($destinations, function(ModerationStateInterface $destination_state) use ($current_state, $user) {
|
||||
return $this->userMayTransition($current_state, $destination_state, $user);
|
||||
});
|
||||
public function __construct(ModerationInformationInterface $moderation_info) {
|
||||
$this->moderationInfo = $moderation_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user) {
|
||||
$bundle = $this->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle());
|
||||
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
|
||||
$current_state = $entity->moderation_state->value ? $workflow->getState($entity->moderation_state->value) : $workflow->getInitialState();
|
||||
|
||||
/** @var \Drupal\content_moderation\Entity\ModerationState $current_state */
|
||||
$current_state = $entity->moderation_state->entity;
|
||||
$current_state_id = $current_state ? $current_state->id() : $bundle->getThirdPartySetting('content_moderation', 'default_moderation_state');
|
||||
|
||||
// Determine the states that are legal on this bundle.
|
||||
$legal_bundle_states = $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', []);
|
||||
|
||||
// Legal transitions include those that are possible from the current state,
|
||||
// filtered by those whose target is legal on this bundle and that the
|
||||
// user has access to execute.
|
||||
$transitions = array_filter($this->getTransitionsFrom($current_state_id), function(ModerationStateTransition $transition) use ($legal_bundle_states, $user) {
|
||||
return in_array($transition->getToState(), $legal_bundle_states, TRUE)
|
||||
&& $user->hasPermission('use ' . $transition->id() . ' transition');
|
||||
return array_filter($current_state->getTransitions(), function(Transition $transition) use ($workflow, $user) {
|
||||
return $user->hasPermission('use ' . $workflow->id() . ' transition ' . $transition->id());
|
||||
});
|
||||
|
||||
return $transitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of possible transitions from a given state.
|
||||
*
|
||||
* This list is based only on those transitions that exist, not what
|
||||
* transitions are legal in a given context.
|
||||
*
|
||||
* @param string $state_name
|
||||
* The machine name of the state from which we are transitioning.
|
||||
*
|
||||
* @return ModerationStateTransition[]
|
||||
* A list of possible transitions from a given state.
|
||||
*/
|
||||
protected function getTransitionsFrom($state_name) {
|
||||
$result = $this->transitionStateQuery()
|
||||
->condition('stateFrom', $state_name)
|
||||
->sort('weight')
|
||||
->execute();
|
||||
|
||||
return $this->transitionStorage()->loadMultiple($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function userMayTransition(ModerationStateInterface $from, ModerationStateInterface $to, AccountInterface $user) {
|
||||
if ($transition = $this->getTransitionFromStates($from, $to)) {
|
||||
return $user->hasPermission('use ' . $transition->id() . ' transition');
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transition object that transitions from one state to another.
|
||||
*
|
||||
* @param \Drupal\content_moderation\ModerationStateInterface $from
|
||||
* The origin state.
|
||||
* @param \Drupal\content_moderation\ModerationStateInterface $to
|
||||
* The destination state.
|
||||
*
|
||||
* @return ModerationStateTransition|null
|
||||
* A transition object, or NULL if there is no such transition.
|
||||
*/
|
||||
protected function getTransitionFromStates(ModerationStateInterface $from, ModerationStateInterface $to) {
|
||||
$from = $this->transitionStateQuery()
|
||||
->condition('stateFrom', $from->id())
|
||||
->condition('stateTo', $to->id())
|
||||
->execute();
|
||||
|
||||
$transitions = $this->transitionStorage()->loadMultiple($from);
|
||||
|
||||
if ($transitions) {
|
||||
return current($transitions);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTransitionAllowed(ModerationStateInterface $from, ModerationStateInterface $to) {
|
||||
$allowed_transitions = $this->calculatePossibleTransitions();
|
||||
if (isset($allowed_transitions[$from->id()])) {
|
||||
return in_array($to->id(), $allowed_transitions[$from->id()], TRUE);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a transition state entity query.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Query\QueryInterface
|
||||
* A transition state entity query.
|
||||
*/
|
||||
protected function transitionStateQuery() {
|
||||
return $this->queryFactory->get('moderation_state_transition', 'AND');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transition entity storage service.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityStorageInterface
|
||||
* The transition state entity storage.
|
||||
*/
|
||||
protected function transitionStorage() {
|
||||
return $this->entityTypeManager->getStorage('moderation_state_transition');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state entity storage service.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityStorageInterface
|
||||
* The moderation state entity storage.
|
||||
*/
|
||||
protected function stateStorage() {
|
||||
return $this->entityTypeManager->getStorage('moderation_state');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific bundle entity.
|
||||
*
|
||||
* @param string $bundle_entity_type_id
|
||||
* The bundle entity type ID.
|
||||
* @param string $bundle_id
|
||||
* The bundle ID.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Entity\ConfigEntityInterface|null
|
||||
* The specific bundle entity.
|
||||
*/
|
||||
protected function loadBundleEntity($bundle_entity_type_id, $bundle_id) {
|
||||
return $this->entityTypeManager->getStorage($bundle_entity_type_id)->load($bundle_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,20 +10,6 @@ use Drupal\Core\Session\AccountInterface;
|
|||
*/
|
||||
interface StateTransitionValidationInterface {
|
||||
|
||||
/**
|
||||
* Gets a list of states a user may transition an entity to.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity to be transitioned.
|
||||
* @param \Drupal\Core\Session\AccountInterface $user
|
||||
* The account that wants to perform a transition.
|
||||
*
|
||||
* @return \Drupal\content_moderation\Entity\ModerationState[]
|
||||
* Returns an array of States to which the specified user may transition the
|
||||
* entity.
|
||||
*/
|
||||
public function getValidTransitionTargets(ContentEntityInterface $entity, AccountInterface $user);
|
||||
|
||||
/**
|
||||
* Gets a list of transitions that are legal for this user on this entity.
|
||||
*
|
||||
|
@ -32,40 +18,9 @@ interface StateTransitionValidationInterface {
|
|||
* @param \Drupal\Core\Session\AccountInterface $user
|
||||
* The account that wants to perform a transition.
|
||||
*
|
||||
* @return \Drupal\content_moderation\Entity\ModerationStateTransition[]
|
||||
* @return \Drupal\workflows\Transition[]
|
||||
* The list of transitions that are legal for this user on this entity.
|
||||
*/
|
||||
public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user);
|
||||
|
||||
/**
|
||||
* Determines if a user is allowed to transition from one state to another.
|
||||
*
|
||||
* This method will also return FALSE if there is no transition between the
|
||||
* specified states at all.
|
||||
*
|
||||
* @param \Drupal\content_moderation\ModerationStateInterface $from
|
||||
* The origin state.
|
||||
* @param \Drupal\content_moderation\ModerationStateInterface $to
|
||||
* The destination state.
|
||||
* @param \Drupal\Core\Session\AccountInterface $user
|
||||
* The user to validate.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the given user may transition between those two states.
|
||||
*/
|
||||
public function userMayTransition(ModerationStateInterface $from, ModerationStateInterface $to, AccountInterface $user);
|
||||
|
||||
/**
|
||||
* Determines a transition allowed.
|
||||
*
|
||||
* @param \Drupal\content_moderation\ModerationStateInterface $from
|
||||
* The origin state.
|
||||
* @param \Drupal\content_moderation\ModerationStateInterface $to
|
||||
* The destination state.
|
||||
*
|
||||
* @return bool
|
||||
* Is the transition allowed.
|
||||
*/
|
||||
public function isTransitionAllowed(ModerationStateInterface $from, ModerationStateInterface $to);
|
||||
|
||||
}
|
||||
|
|
|
@ -15,10 +15,7 @@ class ModerationFormTest extends ModerationStateTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE, [
|
||||
'draft',
|
||||
'published',
|
||||
], 'draft');
|
||||
$this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
|
||||
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
|
||||
}
|
||||
|
||||
|
|
|
@ -59,12 +59,7 @@ class ModerationStateBlockTest extends ModerationStateTestBase {
|
|||
|
||||
// Enable moderation for custom blocks at
|
||||
// admin/structure/block/block-content/manage/basic/moderation.
|
||||
$edit = [
|
||||
'enable_moderation_state' => TRUE,
|
||||
'allowed_moderation_states_unpublished[draft]' => TRUE,
|
||||
'allowed_moderation_states_published[published]' => TRUE,
|
||||
'default_moderation_state' => 'draft',
|
||||
];
|
||||
$edit = ['workflow' => 'editorial'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertText(t('Your settings have been saved.'));
|
||||
|
||||
|
@ -78,11 +73,11 @@ class ModerationStateBlockTest extends ModerationStateTestBase {
|
|||
$this->assertText(t('basic Moderated block has been created.'));
|
||||
|
||||
// Place the block in the Sidebar First region.
|
||||
$instance = array(
|
||||
$instance = [
|
||||
'id' => 'moderated_block',
|
||||
'settings[label]' => $edit['info[0][value]'],
|
||||
'region' => 'sidebar_first',
|
||||
);
|
||||
];
|
||||
$block = BlockContent::load(1);
|
||||
$url = 'admin/structure/block/add/block_content:' . $block->uuid() . '/' . $this->config('system.theme')->get('default');
|
||||
$this->drupalPostForm($url, $instance, t('Save block'));
|
||||
|
|
|
@ -18,13 +18,7 @@ class ModerationStateNodeTest extends ModerationStateTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->createContentTypeFromUi(
|
||||
'Moderated content',
|
||||
'moderated_content',
|
||||
TRUE,
|
||||
['draft', 'needs_review', 'published'],
|
||||
'draft'
|
||||
);
|
||||
$this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
|
||||
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
|
||||
}
|
||||
|
||||
|
@ -35,19 +29,11 @@ class ModerationStateNodeTest extends ModerationStateTestBase {
|
|||
$this->drupalPostForm('node/add/moderated_content', [
|
||||
'title[0][value]' => 'moderated content',
|
||||
], t('Save and Create New Draft'));
|
||||
$nodes = \Drupal::entityTypeManager()
|
||||
->getStorage('node')
|
||||
->loadByProperties([
|
||||
'title' => 'moderated content',
|
||||
]);
|
||||
|
||||
if (!$nodes) {
|
||||
$node = $this->getNodeByTitle('moderated content');
|
||||
if (!$node) {
|
||||
$this->fail('Test node was not saved correctly.');
|
||||
return;
|
||||
}
|
||||
|
||||
$node = reset($nodes);
|
||||
$this->assertEqual('draft', $node->moderation_state->target_id);
|
||||
$this->assertEqual('draft', $node->moderation_state->value);
|
||||
|
||||
$path = 'node/' . $node->id() . '/edit';
|
||||
// Set up published revision.
|
||||
|
@ -56,39 +42,34 @@ class ModerationStateNodeTest extends ModerationStateTestBase {
|
|||
/* @var \Drupal\node\NodeInterface $node */
|
||||
$node = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
|
||||
$this->assertTrue($node->isPublished());
|
||||
$this->assertEqual('published', $node->moderation_state->target_id);
|
||||
$this->assertEqual('published', $node->moderation_state->value);
|
||||
|
||||
// Verify that the state field is not shown.
|
||||
$this->assertNoText('Published');
|
||||
|
||||
// Delete the node.
|
||||
$this->drupalPostForm('node/' . $node->id() . '/delete', array(), t('Delete'));
|
||||
$this->drupalPostForm('node/' . $node->id() . '/delete', [], t('Delete'));
|
||||
$this->assertText(t('The Moderated content moderated content has been deleted.'));
|
||||
|
||||
// Disable content moderation.
|
||||
$this->drupalPostForm('admin/structure/types/manage/moderated_content/moderation', ['workflow' => ''], t('Save'));
|
||||
$this->drupalGet('admin/structure/types/manage/moderated_content/moderation');
|
||||
$this->assertFieldByName('enable_moderation_state');
|
||||
$this->assertFieldChecked('edit-enable-moderation-state');
|
||||
$this->drupalPostForm(NULL, ['enable_moderation_state' => FALSE], t('Save'));
|
||||
$this->drupalGet('admin/structure/types/manage/moderated_content/moderation');
|
||||
$this->assertFieldByName('enable_moderation_state');
|
||||
$this->assertNoFieldChecked('edit-enable-moderation-state');
|
||||
$this->assertOptionSelected('edit-workflow', '');
|
||||
// Ensure the parent environment is up-to-date.
|
||||
// @see content_moderation_workflow_insert()
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
// Create a new node.
|
||||
$this->drupalPostForm('node/add/moderated_content', [
|
||||
'title[0][value]' => 'non-moderated content',
|
||||
], t('Save and publish'));
|
||||
|
||||
$nodes = \Drupal::entityTypeManager()
|
||||
->getStorage('node')
|
||||
->loadByProperties([
|
||||
'title' => 'non-moderated content',
|
||||
]);
|
||||
|
||||
if (!$nodes) {
|
||||
$node = $this->getNodeByTitle('non-moderated content');
|
||||
if (!$node) {
|
||||
$this->fail('Non-moderated test node was not saved correctly.');
|
||||
return;
|
||||
}
|
||||
|
||||
$node = reset($nodes);
|
||||
$this->assertEqual(NULL, $node->moderation_state->target_id);
|
||||
$this->assertEqual(NULL, $node->moderation_state->value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,12 +42,17 @@ class ModerationStateNodeTypeTest extends ModerationStateTestBase {
|
|||
], t('Save and publish'));
|
||||
$this->assertText('Not moderated Test has been created.');
|
||||
|
||||
// Now enable moderation state.
|
||||
$this->enableModerationThroughUi(
|
||||
'not_moderated',
|
||||
['draft', 'needs_review', 'published'],
|
||||
'draft'
|
||||
);
|
||||
// Now enable moderation state, ensuring all the expected links and tabs are
|
||||
// present.
|
||||
$this->drupalGet('admin/structure/types');
|
||||
$this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation');
|
||||
$this->drupalGet('admin/structure/types/manage/not_moderated');
|
||||
$this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation');
|
||||
$this->drupalGet('admin/structure/types/manage/not_moderated/moderation');
|
||||
$this->assertOptionSelected('edit-workflow', '');
|
||||
$this->assertNoLink('Delete');
|
||||
$edit['workflow'] = 'editorial';
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
|
||||
// And make sure it works.
|
||||
$nodes = \Drupal::entityTypeManager()->getStorage('node')
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Tests;
|
||||
|
||||
/**
|
||||
* Tests moderation state config entity.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ModerationStateStatesTest extends ModerationStateTestBase {
|
||||
|
||||
/**
|
||||
* Tests route access/permissions.
|
||||
*/
|
||||
public function testAccess() {
|
||||
$paths = [
|
||||
'admin/config/workflow/moderation',
|
||||
'admin/config/workflow/moderation/states',
|
||||
'admin/config/workflow/moderation/states/add',
|
||||
'admin/config/workflow/moderation/states/draft',
|
||||
'admin/config/workflow/moderation/states/draft/delete',
|
||||
];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
// No access.
|
||||
$this->assertResponse(403);
|
||||
}
|
||||
$this->drupalLogin($this->adminUser);
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
// User has access.
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests administration of moderation state entity.
|
||||
*/
|
||||
public function testStateAdministration() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/config/workflow/moderation');
|
||||
$this->assertLink('Moderation states');
|
||||
$this->assertLink('Moderation state transitions');
|
||||
$this->clickLink('Moderation states');
|
||||
$this->assertLink('Add moderation state');
|
||||
$this->assertText('Draft');
|
||||
// Edit the draft.
|
||||
$this->clickLink('Edit', 0);
|
||||
$this->assertFieldByName('label', 'Draft');
|
||||
$this->assertNoFieldChecked('edit-published');
|
||||
$this->drupalPostForm(NULL, [
|
||||
'label' => 'Drafty',
|
||||
], t('Save'));
|
||||
$this->assertText('Saved the Drafty Moderation state.');
|
||||
$this->drupalGet('admin/config/workflow/moderation/states/draft');
|
||||
$this->assertFieldByName('label', 'Drafty');
|
||||
$this->drupalPostForm(NULL, [
|
||||
'label' => 'Draft',
|
||||
], t('Save'));
|
||||
$this->assertText('Saved the Draft Moderation state.');
|
||||
$this->clickLink(t('Add moderation state'));
|
||||
$this->drupalPostForm(NULL, [
|
||||
'label' => 'Expired',
|
||||
'id' => 'expired',
|
||||
], t('Save'));
|
||||
$this->assertText('Created the Expired Moderation state.');
|
||||
$this->drupalGet('admin/config/workflow/moderation/states/expired');
|
||||
$this->clickLink('Delete');
|
||||
$this->assertText('Are you sure you want to delete Expired?');
|
||||
$this->drupalPostForm(NULL, [], t('Delete'));
|
||||
$this->assertText('Moderation state Expired deleted');
|
||||
}
|
||||
|
||||
}
|
|
@ -5,10 +5,12 @@ namespace Drupal\content_moderation\Tests;
|
|||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
|
||||
/**
|
||||
* Defines a base class for moderation state tests.
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0.
|
||||
* Use \Drupal\Tests\content_moderation\Functional\ModerationStateTestBase instead.
|
||||
*/
|
||||
abstract class ModerationStateTestBase extends WebTestBase {
|
||||
|
||||
|
@ -30,18 +32,15 @@ abstract class ModerationStateTestBase extends WebTestBase {
|
|||
* @var array
|
||||
*/
|
||||
protected $permissions = [
|
||||
'administer moderation states',
|
||||
'administer moderation state transitions',
|
||||
'use draft_draft transition',
|
||||
'use draft_published transition',
|
||||
'use published_draft transition',
|
||||
'use published_archived transition',
|
||||
'administer content moderation',
|
||||
'access administration pages',
|
||||
'administer content types',
|
||||
'administer nodes',
|
||||
'view latest version',
|
||||
'view any unpublished content',
|
||||
'access content overview',
|
||||
'use editorial transition create_new_draft',
|
||||
'use editorial transition publish',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -67,6 +66,21 @@ abstract class ModerationStateTestBase extends WebTestBase {
|
|||
$this->drupalPlaceBlock('local_actions_block', ['id' => 'actions_block']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permission machine name for a transition.
|
||||
*
|
||||
* @param string $workflow_id
|
||||
* The workflow ID.
|
||||
* @param string $transition_id
|
||||
* The transition ID.
|
||||
*
|
||||
* @return string
|
||||
* The permission machine name for a transition.
|
||||
*/
|
||||
protected function getWorkflowTransitionPermission($workflow_id, $transition_id) {
|
||||
return 'use ' . $workflow_id . ' transition ' . $transition_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a content-type from the UI.
|
||||
*
|
||||
|
@ -76,12 +90,10 @@ abstract class ModerationStateTestBase extends WebTestBase {
|
|||
* Machine name.
|
||||
* @param bool $moderated
|
||||
* TRUE if should be moderated.
|
||||
* @param string[] $allowed_states
|
||||
* Array of allowed state IDs.
|
||||
* @param string $default_state
|
||||
* Default state.
|
||||
* @param string $workflow_id
|
||||
* The workflow to attach to the bundle.
|
||||
*/
|
||||
protected function createContentTypeFromUi($content_type_name, $content_type_id, $moderated = FALSE, array $allowed_states = [], $default_state = NULL) {
|
||||
protected function createContentTypeFromUi($content_type_name, $content_type_id, $moderated = FALSE, $workflow_id = 'editorial') {
|
||||
$this->drupalGet('admin/structure/types');
|
||||
$this->clickLink('Add content type');
|
||||
$edit = [
|
||||
|
@ -91,7 +103,7 @@ abstract class ModerationStateTestBase extends WebTestBase {
|
|||
$this->drupalPostForm(NULL, $edit, t('Save content type'));
|
||||
|
||||
if ($moderated) {
|
||||
$this->enableModerationThroughUi($content_type_id, $allowed_states, $default_state);
|
||||
$this->enableModerationThroughUi($content_type_id, $workflow_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,31 +112,16 @@ abstract class ModerationStateTestBase extends WebTestBase {
|
|||
*
|
||||
* @param string $content_type_id
|
||||
* Machine name.
|
||||
* @param string[] $allowed_states
|
||||
* Array of allowed state IDs.
|
||||
* @param string $default_state
|
||||
* Default state.
|
||||
* @param string $workflow_id
|
||||
* The workflow to attach to the bundle.
|
||||
*/
|
||||
protected function enableModerationThroughUi($content_type_id, array $allowed_states, $default_state) {
|
||||
$this->drupalGet('admin/structure/types');
|
||||
$this->assertLinkByHref('admin/structure/types/manage/' . $content_type_id . '/moderation');
|
||||
$this->drupalGet('admin/structure/types/manage/' . $content_type_id);
|
||||
$this->assertLinkByHref('admin/structure/types/manage/' . $content_type_id . '/moderation');
|
||||
$this->drupalGet('admin/structure/types/manage/' . $content_type_id . '/moderation');
|
||||
$this->assertFieldByName('enable_moderation_state');
|
||||
$this->assertNoFieldChecked('edit-enable-moderation-state');
|
||||
|
||||
$edit['enable_moderation_state'] = 1;
|
||||
|
||||
/** @var ModerationState $state */
|
||||
foreach (ModerationState::loadMultiple() as $state) {
|
||||
$key = $state->isPublishedState() ? 'allowed_moderation_states_published[' . $state->id() . ']' : 'allowed_moderation_states_unpublished[' . $state->id() . ']';
|
||||
$edit[$key] = in_array($state->id(), $allowed_states, TRUE) ? $state->id() : FALSE;
|
||||
}
|
||||
|
||||
$edit['default_moderation_state'] = $default_state;
|
||||
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
protected function enableModerationThroughUi($content_type_id, $workflow_id = 'editorial') {
|
||||
$edit['workflow'] = $workflow_id;
|
||||
$this->drupalPostForm('admin/structure/types/manage/' . $content_type_id . '/moderation', $edit, t('Save'));
|
||||
// Ensure the parent environment is up-to-date.
|
||||
// @see content_moderation_workflow_insert()
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Tests;
|
||||
|
||||
/**
|
||||
* Tests moderation state transition config entity.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ModerationStateTransitionsTest extends ModerationStateTestBase {
|
||||
|
||||
/**
|
||||
* Tests route access/permissions.
|
||||
*/
|
||||
public function testAccess() {
|
||||
$paths = [
|
||||
'admin/config/workflow/moderation/transitions',
|
||||
'admin/config/workflow/moderation/transitions/add',
|
||||
'admin/config/workflow/moderation/transitions/draft_published',
|
||||
'admin/config/workflow/moderation/transitions/draft_published/delete',
|
||||
];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
// No access.
|
||||
$this->assertResponse(403);
|
||||
}
|
||||
$this->drupalLogin($this->adminUser);
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
// User has access.
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests administration of moderation state transition entity.
|
||||
*/
|
||||
public function testTransitionAdministration() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->drupalGet('admin/config/workflow/moderation');
|
||||
$this->clickLink('Moderation state transitions');
|
||||
$this->assertLink('Add moderation state transition');
|
||||
$this->assertText('Create New Draft');
|
||||
|
||||
// Edit the Draft » Draft review.
|
||||
$this->drupalGet('admin/config/workflow/moderation/transitions/draft_draft');
|
||||
$this->assertFieldByName('label', 'Create New Draft');
|
||||
$this->assertFieldByName('stateFrom', 'draft');
|
||||
$this->assertFieldByName('stateTo', 'draft');
|
||||
$this->drupalPostForm(NULL, [
|
||||
'label' => 'Create Draft',
|
||||
], t('Save'));
|
||||
$this->assertText('Saved the Create Draft Moderation state transition.');
|
||||
$this->drupalGet('admin/config/workflow/moderation/transitions/draft_draft');
|
||||
$this->assertFieldByName('label', 'Create Draft');
|
||||
// Now set it back.
|
||||
$this->drupalPostForm(NULL, [
|
||||
'label' => 'Create New Draft',
|
||||
], t('Save'));
|
||||
$this->assertText('Saved the Create New Draft Moderation state transition.');
|
||||
|
||||
// Add a new state.
|
||||
$this->drupalGet('admin/config/workflow/moderation/states/add');
|
||||
$this->drupalPostForm(NULL, [
|
||||
'label' => 'Expired',
|
||||
'id' => 'expired',
|
||||
], t('Save'));
|
||||
$this->assertText('Created the Expired Moderation state.');
|
||||
|
||||
// Add a new transition.
|
||||
$this->drupalGet('admin/config/workflow/moderation/transitions');
|
||||
$this->clickLink(t('Add moderation state transition'));
|
||||
$this->drupalPostForm(NULL, [
|
||||
'label' => 'Published » Expired',
|
||||
'id' => 'published_expired',
|
||||
'stateFrom' => 'published',
|
||||
'stateTo' => 'expired',
|
||||
], t('Save'));
|
||||
$this->assertText('Created the Published » Expired Moderation state transition.');
|
||||
|
||||
// Delete the new transition.
|
||||
$this->drupalGet('admin/config/workflow/moderation/transitions/published_expired');
|
||||
$this->clickLink('Delete');
|
||||
$this->assertText('Are you sure you want to delete Published » Expired?');
|
||||
$this->drupalPostForm(NULL, [], t('Delete'));
|
||||
$this->assertText('Moderation transition Published » Expired deleted');
|
||||
}
|
||||
|
||||
}
|
|
@ -193,17 +193,14 @@ class ViewsData {
|
|||
'base' => $content_moderation_state_entity_base_table,
|
||||
'base field' => 'content_entity_id',
|
||||
'relationship field' => $entity_type->getKey('id'),
|
||||
'join_extra' => [
|
||||
'extra' => [
|
||||
[
|
||||
'field' => 'content_entity_type_id',
|
||||
'value' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'field' => 'content_entity_revision_id',
|
||||
'left_field' => $entity_type->getKey('revision'),
|
||||
],
|
||||
],
|
||||
],
|
||||
'field' => ['default_formatter' => 'content_moderation_state'],
|
||||
];
|
||||
|
||||
$revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
|
||||
|
@ -215,13 +212,14 @@ class ViewsData {
|
|||
'base' => $content_moderation_state_entity_revision_base_table,
|
||||
'base field' => 'content_entity_revision_id',
|
||||
'relationship field' => $entity_type->getKey('revision'),
|
||||
'join_extra' => [
|
||||
'extra' => [
|
||||
[
|
||||
'field' => 'content_entity_type_id',
|
||||
'value' => $entity_type_id,
|
||||
],
|
||||
],
|
||||
],
|
||||
'field' => ['default_formatter' => 'content_moderation_state'],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
name: 'Content moderation test local task'
|
||||
type: module
|
||||
description: 'Provides a local task for testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- content_moderation
|
||||
- node
|
|
@ -0,0 +1,4 @@
|
|||
entity.node.test_local_task_without_upcast_node:
|
||||
route_name: entity.node.test_local_task_without_upcast_node
|
||||
base_route: entity.node.canonical
|
||||
title: 'Task Without Upcast Node'
|
|
@ -0,0 +1,7 @@
|
|||
entity.node.test_local_task_without_upcast_node:
|
||||
path: '/node/{node}/task-without-upcast-node'
|
||||
defaults:
|
||||
_title: 'Page Without Upcast Node'
|
||||
_controller: '\Drupal\content_moderation_test_local_task\Controller\TestLocalTaskController::methodWithoutUpcastNode'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation_test_local_task\Controller;
|
||||
|
||||
/**
|
||||
* A test controller.
|
||||
*/
|
||||
class TestLocalTaskController {
|
||||
|
||||
/**
|
||||
* A method which does not hint the node parameter to avoid upcasting.
|
||||
*/
|
||||
public function methodWithoutUpcastNode($node) {
|
||||
return ['#markup' => 'It works!'];
|
||||
}
|
||||
|
||||
}
|
|
@ -300,9 +300,7 @@ display:
|
|||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
type: content_moderation_state
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
|
|
|
@ -193,9 +193,7 @@ display:
|
|||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: false
|
||||
type: content_moderation_state
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
|
@ -258,9 +256,7 @@ display:
|
|||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: false
|
||||
type: content_moderation_state
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
|
@ -323,8 +319,7 @@ display:
|
|||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_entity_id
|
||||
settings: { }
|
||||
type: string
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
|
|
|
@ -306,7 +306,7 @@ display:
|
|||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_entity_id
|
||||
type: string
|
||||
settings: { }
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
|
@ -370,7 +370,7 @@ display:
|
|||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_entity_id
|
||||
type: string
|
||||
settings: { }
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
|
|
|
@ -191,7 +191,7 @@ display:
|
|||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_entity_id
|
||||
type: string
|
||||
settings: { }
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test the workflow type plugin in the content_moderation module.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ContentModerationWorkflowTypeTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$admin = $this->drupalCreateUser([
|
||||
'administer workflows',
|
||||
]);
|
||||
$this->drupalLogin($admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a new workflow using the content moderation plugin.
|
||||
*/
|
||||
public function testNewWorkflow() {
|
||||
$entity_bundle_info = \Drupal::service('entity_type.bundle.info');
|
||||
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/add', [
|
||||
'label' => 'Test Workflow',
|
||||
'id' => 'test_workflow',
|
||||
'workflow_type' => 'content_moderation',
|
||||
], 'Save');
|
||||
|
||||
// Make sure the test workflow includes the default states and transitions.
|
||||
$this->assertSession()->pageTextContains('Draft');
|
||||
$this->assertSession()->pageTextContains('Published');
|
||||
$this->assertSession()->pageTextContains('Create New Draft');
|
||||
$this->assertSession()->pageTextContains('Publish');
|
||||
|
||||
// Ensure after a workflow is created, the bundle information can be
|
||||
// refreshed.
|
||||
$entity_bundle_info->clearCachedBundles();
|
||||
$this->assertNotEmpty($entity_bundle_info->getAllBundleInfo());
|
||||
|
||||
$this->clickLink('Add a new state');
|
||||
$this->submitForm([
|
||||
'label' => 'Test State',
|
||||
'id' => 'test_state',
|
||||
'type_settings[content_moderation][published]' => TRUE,
|
||||
'type_settings[content_moderation][default_revision]' => FALSE,
|
||||
], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created Test State state.');
|
||||
|
||||
// Ensure that the published settings cannot be changed.
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test_workflow/state/published');
|
||||
$this->assertSession()->fieldDisabled('type_settings[content_moderation][published]');
|
||||
$this->assertSession()->fieldDisabled('type_settings[content_moderation][default_revision]');
|
||||
|
||||
// Ensure that the draft settings cannot be changed.
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test_workflow/state/draft');
|
||||
$this->assertSession()->fieldDisabled('type_settings[content_moderation][published]');
|
||||
$this->assertSession()->fieldDisabled('type_settings[content_moderation][default_revision]');
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace Drupal\Tests\content_moderation\Functional;
|
|||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests the "Latest Revision" views filter.
|
||||
|
@ -25,7 +26,7 @@ class LatestRevisionViewsFilterTest extends BrowserTestBase {
|
|||
* Tests view shows the correct node IDs.
|
||||
*/
|
||||
public function testViewShowsCorrectNids() {
|
||||
$node_type = $this->createNodeType('Test', 'test');
|
||||
$this->createNodeType('Test', 'test');
|
||||
|
||||
$permissions = [
|
||||
'access content',
|
||||
|
@ -45,8 +46,9 @@ class LatestRevisionViewsFilterTest extends BrowserTestBase {
|
|||
$node_0->save();
|
||||
|
||||
// Now enable moderation for subsequent nodes.
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'test');
|
||||
$workflow->save();
|
||||
|
||||
// Make a node that is only ever in Draft.
|
||||
/** @var Node $node_1 */
|
||||
|
@ -55,7 +57,7 @@ class LatestRevisionViewsFilterTest extends BrowserTestBase {
|
|||
'title' => 'Node 1 - Rev 1',
|
||||
'uid' => $editor1->id(),
|
||||
]);
|
||||
$node_1->moderation_state->target_id = 'draft';
|
||||
$node_1->moderation_state->value = 'draft';
|
||||
$node_1->save();
|
||||
|
||||
// Make a node that is in Draft, then Published.
|
||||
|
@ -65,11 +67,11 @@ class LatestRevisionViewsFilterTest extends BrowserTestBase {
|
|||
'title' => 'Node 2 - Rev 1',
|
||||
'uid' => $editor1->id(),
|
||||
]);
|
||||
$node_2->moderation_state->target_id = 'draft';
|
||||
$node_2->moderation_state->value = 'draft';
|
||||
$node_2->save();
|
||||
|
||||
$node_2->setTitle('Node 2 - Rev 2');
|
||||
$node_2->moderation_state->target_id = 'published';
|
||||
$node_2->moderation_state->value = 'published';
|
||||
$node_2->save();
|
||||
|
||||
// Make a node that is in Draft, then Published, then Draft.
|
||||
|
@ -79,15 +81,15 @@ class LatestRevisionViewsFilterTest extends BrowserTestBase {
|
|||
'title' => 'Node 3 - Rev 1',
|
||||
'uid' => $editor1->id(),
|
||||
]);
|
||||
$node_3->moderation_state->target_id = 'draft';
|
||||
$node_3->moderation_state->value = 'draft';
|
||||
$node_3->save();
|
||||
|
||||
$node_3->setTitle('Node 3 - Rev 2');
|
||||
$node_3->moderation_state->target_id = 'published';
|
||||
$node_3->moderation_state->value = 'published';
|
||||
$node_3->save();
|
||||
|
||||
$node_3->setTitle('Node 3 - Rev 3');
|
||||
$node_3->moderation_state->target_id = 'draft';
|
||||
$node_3->moderation_state->value = 'draft';
|
||||
$node_3->save();
|
||||
|
||||
// Now show the View, and confirm that only the correct titles are showing.
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\simpletest\ContentTypeCreationTrait;
|
||||
use Drupal\simpletest\NodeCreationTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Test the content moderation local task.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class LocalTaskTest extends BrowserTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
use NodeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation_test_local_task',
|
||||
'content_moderation',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* A test node.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $testNode;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('local_tasks_block', ['id' => 'tabs_block']);
|
||||
$this->drupalLogin($this->createUser(['bypass node access']));
|
||||
|
||||
$node_type = $this->createContentType([
|
||||
'type' => 'test_content_type',
|
||||
]);
|
||||
|
||||
// Now enable moderation for subsequent nodes.
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', $node_type->id());
|
||||
$workflow->save();
|
||||
|
||||
$this->testNode = $this->createNode([
|
||||
'type' => $node_type->id(),
|
||||
'moderation_state' => 'draft',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests local tasks behave with content_moderation enabled.
|
||||
*/
|
||||
public function testLocalTasks() {
|
||||
// The default state is a draft.
|
||||
$this->drupalGet(sprintf('node/%s', $this->testNode->id()));
|
||||
$this->assertTasks('Edit draft');
|
||||
|
||||
// When published as the live revision, the label changes.
|
||||
$this->testNode->moderation_state = 'published';
|
||||
$this->testNode->save();
|
||||
$this->drupalGet(sprintf('node/%s', $this->testNode->id()));
|
||||
$this->assertTasks('New draft');
|
||||
|
||||
$tags = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$this->assertContains('node:1', $tags);
|
||||
$this->assertContains('node_type:test_content_type', $tags);
|
||||
|
||||
// Without an upcast node, the state cannot be determined.
|
||||
$this->clickLink('Task Without Upcast Node');
|
||||
$this->assertTasks('Edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the correct tasks appear.
|
||||
*
|
||||
* @param string $edit_tab_label
|
||||
* The edit tab label to assert.
|
||||
*/
|
||||
protected function assertTasks($edit_tab_label) {
|
||||
$this->assertSession()->linkExists('View');
|
||||
$this->assertSession()->linkExists('Task Without Upcast Node');
|
||||
$this->assertSession()->linkExists($edit_tab_label);
|
||||
$this->assertSession()->linkExists('Delete');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\simpletest\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Test the content moderation actions.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ModerationActionsTest extends BrowserTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation',
|
||||
'node',
|
||||
'views',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$moderated_bundle = $this->createContentType(['type' => 'moderated_bundle']);
|
||||
$moderated_bundle->save();
|
||||
$standard_bundle = $this->createContentType(['type' => 'standard_bundle']);
|
||||
$standard_bundle->save();
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'moderated_bundle');
|
||||
$workflow->save();
|
||||
|
||||
$admin = $this->drupalCreateUser([
|
||||
'access content overview',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the node status actions report moderation status to users correctly.
|
||||
*
|
||||
* @dataProvider nodeStatusActionsTestCases
|
||||
*/
|
||||
public function testNodeStatusActions($action, $bundle, $warning_appears, $starting_status, $final_status) {
|
||||
// Create and run an action on a node.
|
||||
$node = Node::create([
|
||||
'type' => $bundle,
|
||||
'title' => $this->randomString(),
|
||||
'status' => $starting_status,
|
||||
]);
|
||||
if ($bundle == 'moderated_bundle') {
|
||||
$node->moderation_state->value = $starting_status ? 'published' : 'draft';
|
||||
}
|
||||
$node->save();
|
||||
|
||||
$this->drupalPostForm('admin/content', [
|
||||
'node_bulk_form[0]' => TRUE,
|
||||
'action' => $action,
|
||||
], 'Apply to selected items');
|
||||
|
||||
if ($warning_appears) {
|
||||
if ($action == 'node_publish_action') {
|
||||
$this->assertSession()
|
||||
->elementContains('css', '.messages--warning', node_get_type_label($node) . ' content items were skipped as they are under moderation and may not be directly published.');
|
||||
}
|
||||
else {
|
||||
$this->assertSession()
|
||||
->elementContains('css', '.messages--warning', node_get_type_label($node) . ' content items were skipped as they are under moderation and may not be directly unpublished.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->elementNotExists('css', '.messages--warning');
|
||||
}
|
||||
|
||||
// Ensure after the action has run, the node matches the expected status.
|
||||
$node = Node::load($node->id());
|
||||
$this->assertEquals($node->isPublished(), $final_status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cases for ::testNodeStatusActions.
|
||||
*
|
||||
* @return array
|
||||
* An array of test cases.
|
||||
*/
|
||||
public function nodeStatusActionsTestCases() {
|
||||
return [
|
||||
'Moderated bundle shows warning (publish action)' => [
|
||||
'node_publish_action',
|
||||
'moderated_bundle',
|
||||
TRUE,
|
||||
// If the node starts out unpublished, the action should not work.
|
||||
FALSE,
|
||||
FALSE,
|
||||
],
|
||||
'Moderated bundle shows warning (unpublish action)' => [
|
||||
'node_unpublish_action',
|
||||
'moderated_bundle',
|
||||
TRUE,
|
||||
// If the node starts out published, the action should not work.
|
||||
TRUE,
|
||||
TRUE,
|
||||
],
|
||||
'Normal bundle works (publish action)' => [
|
||||
'node_publish_action',
|
||||
'standard_bundle',
|
||||
FALSE,
|
||||
// If the node starts out unpublished, the action should work.
|
||||
FALSE,
|
||||
TRUE,
|
||||
],
|
||||
'Normal bundle works (unpublish action)' => [
|
||||
'node_unpublish_action',
|
||||
'standard_bundle',
|
||||
FALSE,
|
||||
// If the node starts out published, the action should work.
|
||||
TRUE,
|
||||
FALSE,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Tests;
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
/**
|
||||
* Test content_moderation functionality with localization and translation.
|
||||
|
@ -28,13 +28,7 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
|||
$this->drupalLogin($this->rootUser);
|
||||
|
||||
// Enable moderation on Article node type.
|
||||
$this->createContentTypeFromUi(
|
||||
'Article',
|
||||
'article',
|
||||
TRUE,
|
||||
['draft', 'published', 'archived'],
|
||||
'draft'
|
||||
);
|
||||
$this->createContentTypeFromUi('Article', 'article', TRUE);
|
||||
|
||||
// Add French language.
|
||||
$edit = [
|
||||
|
@ -103,9 +97,9 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
|||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertEqual('French node', $french_node->label());
|
||||
|
||||
$this->assertEqual($english_node->moderation_state->target_id, 'published');
|
||||
$this->assertEqual($english_node->moderation_state->value, 'published');
|
||||
$this->assertTrue($english_node->isPublished());
|
||||
$this->assertEqual($french_node->moderation_state->target_id, 'draft');
|
||||
$this->assertEqual($french_node->moderation_state->value, 'draft');
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
|
||||
// Create another article with its translation. This time we will publish
|
||||
|
@ -133,9 +127,9 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
|||
$this->assertText(t('Article Translated node has been updated.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertEqual($french_node->moderation_state->target_id, 'published');
|
||||
$this->assertEqual($french_node->moderation_state->value, 'published');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$this->assertEqual($english_node->moderation_state->target_id, 'draft');
|
||||
$this->assertEqual($english_node->moderation_state->value, 'draft');
|
||||
$this->assertFalse($english_node->isPublished());
|
||||
|
||||
// Now check that we can create a new draft of the translation.
|
||||
|
@ -146,7 +140,7 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
|||
$this->assertText(t('Article New draft of translated node has been updated.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertEqual($french_node->moderation_state->target_id, 'published');
|
||||
$this->assertEqual($french_node->moderation_state->value, 'published');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$this->assertEqual($french_node->getTitle(), 'Translated node', 'The default revision of the published translation remains the same.');
|
||||
|
||||
|
@ -158,7 +152,7 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
|||
$this->assertText(t('The moderation state has been updated.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertEqual($french_node->moderation_state->target_id, 'published');
|
||||
$this->assertEqual($french_node->moderation_state->value, 'published');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$this->assertEqual($french_node->getTitle(), 'New draft of translated node', 'The draft has replaced the published revision.');
|
||||
|
||||
|
@ -166,7 +160,7 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
|||
$this->drupalPostForm('node/' . $english_node->id() . '/edit', [], t('Save and Publish (this translation)'));
|
||||
$this->assertText(t('Article Another node has been updated.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
|
||||
$this->assertEqual($english_node->moderation_state->target_id, 'published');
|
||||
$this->assertEqual($english_node->moderation_state->value, 'published');
|
||||
|
||||
// Archive the node and its translation.
|
||||
$this->drupalPostForm('node/' . $english_node->id() . '/edit', [], t('Save and Archive (this translation)'));
|
||||
|
@ -175,9 +169,9 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
|||
$this->assertText(t('Article New draft of translated node has been updated.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertEqual($english_node->moderation_state->target_id, 'archived');
|
||||
$this->assertEqual($english_node->moderation_state->value, 'archived');
|
||||
$this->assertFalse($english_node->isPublished());
|
||||
$this->assertEqual($french_node->moderation_state->target_id, 'archived');
|
||||
$this->assertEqual($french_node->moderation_state->value, 'archived');
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
|
||||
// Create another article with its translation. This time publishing english
|
|
@ -5,6 +5,7 @@ namespace Drupal\Tests\content_moderation\Functional;
|
|||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests the view access control handler for moderation state entities.
|
||||
|
@ -31,7 +32,7 @@ class ModerationStateAccessTest extends BrowserTestBase {
|
|||
$permissions = [
|
||||
'access content',
|
||||
'view all revisions',
|
||||
'view moderation states',
|
||||
'view content moderation',
|
||||
];
|
||||
$editor1 = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($editor1);
|
||||
|
@ -41,7 +42,7 @@ class ModerationStateAccessTest extends BrowserTestBase {
|
|||
'title' => 'Draft node',
|
||||
'uid' => $editor1->id(),
|
||||
]);
|
||||
$node_1->moderation_state->target_id = 'draft';
|
||||
$node_1->moderation_state->value = 'draft';
|
||||
$node_1->save();
|
||||
|
||||
$node_2 = Node::create([
|
||||
|
@ -49,26 +50,26 @@ class ModerationStateAccessTest extends BrowserTestBase {
|
|||
'title' => 'Published node',
|
||||
'uid' => $editor1->id(),
|
||||
]);
|
||||
$node_2->moderation_state->target_id = 'published';
|
||||
$node_2->moderation_state->value = 'published';
|
||||
$node_2->save();
|
||||
|
||||
// Resave the node with a new state.
|
||||
$node_2->setTitle('Archived node');
|
||||
$node_2->moderation_state->target_id = 'archived';
|
||||
$node_2->moderation_state->value = 'archived';
|
||||
$node_2->save();
|
||||
|
||||
// Now show the View, and confirm that the state labels are showing.
|
||||
$this->drupalGet('/latest');
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertTrue($page->hasLink('Draft'));
|
||||
$this->assertTrue($page->hasLink('Archived'));
|
||||
$this->assertFalse($page->hasLink('Published'));
|
||||
$this->assertTrue($page->hasContent('Draft'));
|
||||
$this->assertTrue($page->hasContent('Archived'));
|
||||
$this->assertFalse($page->hasContent('Published'));
|
||||
|
||||
// Now log in as an admin and test the same thing.
|
||||
$permissions = [
|
||||
'access content',
|
||||
'view all revisions',
|
||||
'administer moderation states',
|
||||
'administer content moderation',
|
||||
];
|
||||
$admin1 = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($admin1);
|
||||
|
@ -76,9 +77,9 @@ class ModerationStateAccessTest extends BrowserTestBase {
|
|||
$this->drupalGet('/latest');
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertEquals(200, $this->getSession()->getStatusCode());
|
||||
$this->assertTrue($page->hasLink('Draft'));
|
||||
$this->assertTrue($page->hasLink('Archived'));
|
||||
$this->assertFalse($page->hasLink('Published'));
|
||||
$this->assertTrue($page->hasContent('Draft'));
|
||||
$this->assertTrue($page->hasContent('Archived'));
|
||||
$this->assertFalse($page->hasContent('Published'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,9 +99,11 @@ class ModerationStateAccessTest extends BrowserTestBase {
|
|||
'type' => $machine_name,
|
||||
'label' => $label,
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->save();
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', $machine_name);
|
||||
$workflow->save();
|
||||
return $node_type;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Defines a base class for moderation state tests.
|
||||
*/
|
||||
abstract class ModerationStateTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Profile to use.
|
||||
*/
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* Permissions to grant admin user.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $permissions = [
|
||||
'administer content moderation',
|
||||
'access administration pages',
|
||||
'administer content types',
|
||||
'administer nodes',
|
||||
'view latest version',
|
||||
'view any unpublished content',
|
||||
'access content overview',
|
||||
'use editorial transition create_new_draft',
|
||||
'use editorial transition publish',
|
||||
];
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation',
|
||||
'block',
|
||||
'block_content',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets the test up.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->adminUser = $this->drupalCreateUser($this->permissions);
|
||||
$this->drupalPlaceBlock('local_tasks_block', ['id' => 'tabs_block']);
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
$this->drupalPlaceBlock('local_actions_block', ['id' => 'actions_block']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permission machine name for a transition.
|
||||
*
|
||||
* @param string $workflow_id
|
||||
* The workflow ID.
|
||||
* @param string $transition_id
|
||||
* The transition ID.
|
||||
*
|
||||
* @return string
|
||||
* The permission machine name for a transition.
|
||||
*/
|
||||
protected function getWorkflowTransitionPermission($workflow_id, $transition_id) {
|
||||
return 'use ' . $workflow_id . ' transition ' . $transition_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a content-type from the UI.
|
||||
*
|
||||
* @param string $content_type_name
|
||||
* Content type human name.
|
||||
* @param string $content_type_id
|
||||
* Machine name.
|
||||
* @param bool $moderated
|
||||
* TRUE if should be moderated.
|
||||
* @param string $workflow_id
|
||||
* The workflow to attach to the bundle.
|
||||
*/
|
||||
protected function createContentTypeFromUi($content_type_name, $content_type_id, $moderated = FALSE, $workflow_id = 'editorial') {
|
||||
$this->drupalGet('admin/structure/types');
|
||||
$this->clickLink('Add content type');
|
||||
$edit = [
|
||||
'name' => $content_type_name,
|
||||
'type' => $content_type_id,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save content type'));
|
||||
|
||||
if ($moderated) {
|
||||
$this->enableModerationThroughUi($content_type_id, $workflow_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable moderation for a specified content type, using the UI.
|
||||
*
|
||||
* @param string $content_type_id
|
||||
* Machine name.
|
||||
* @param string $workflow_id
|
||||
* The workflow to attach to the bundle.
|
||||
*/
|
||||
protected function enableModerationThroughUi($content_type_id, $workflow_id = 'editorial') {
|
||||
$edit['workflow'] = $workflow_id;
|
||||
$this->drupalPostForm('admin/structure/types/manage/' . $content_type_id . '/moderation', $edit, t('Save'));
|
||||
// Ensure the parent environment is up-to-date.
|
||||
// @see content_moderation_workflow_insert()
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants given user permission to create content of given type.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* User to grant permission to.
|
||||
* @param string $content_type_id
|
||||
* Content type ID.
|
||||
*/
|
||||
protected function grantUserPermissionToCreateContentOfType(AccountInterface $account, $content_type_id) {
|
||||
$role_ids = $account->getRoles(TRUE);
|
||||
/* @var \Drupal\user\RoleInterface $role */
|
||||
$role_id = reset($role_ids);
|
||||
$role = Role::load($role_id);
|
||||
$role->grantPermission(sprintf('create %s content', $content_type_id));
|
||||
$role->grantPermission(sprintf('edit any %s content', $content_type_id));
|
||||
$role->grantPermission(sprintf('delete any %s content', $content_type_id));
|
||||
$role->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Tests;
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
/**
|
||||
* Tests permission access control around nodes.
|
||||
|
@ -15,13 +15,7 @@ class NodeAccessTest extends ModerationStateTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->createContentTypeFromUi(
|
||||
'Moderated content',
|
||||
'moderated_content',
|
||||
TRUE,
|
||||
['draft', 'published'],
|
||||
'draft'
|
||||
);
|
||||
$this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
|
||||
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
|
||||
}
|
||||
|
||||
|
@ -35,20 +29,11 @@ class NodeAccessTest extends ModerationStateTestBase {
|
|||
$this->drupalPostForm('node/add/moderated_content', [
|
||||
'title[0][value]' => 'moderated content',
|
||||
], t('Save and Create New Draft'));
|
||||
$nodes = \Drupal::entityTypeManager()
|
||||
->getStorage('node')
|
||||
->loadByProperties([
|
||||
'title' => 'moderated content',
|
||||
]);
|
||||
|
||||
if (!$nodes) {
|
||||
$node = $this->getNodeByTitle('moderated content');
|
||||
if (!$node) {
|
||||
$this->fail('Test node was not saved correctly.');
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = reset($nodes);
|
||||
|
||||
$view_path = 'node/' . $node->id();
|
||||
$edit_path = 'node/' . $node->id() . '/edit';
|
||||
$latest_path = 'node/' . $node->id() . '/latest';
|
||||
|
@ -75,8 +60,7 @@ class NodeAccessTest extends ModerationStateTestBase {
|
|||
|
||||
// Now make a new user and verify that the new user's access is correct.
|
||||
$user = $this->createUser([
|
||||
'use draft_draft transition',
|
||||
'use published_draft transition',
|
||||
'use editorial transition create_new_draft',
|
||||
'view latest version',
|
||||
'view any unpublished content',
|
||||
]);
|
||||
|
@ -92,7 +76,7 @@ class NodeAccessTest extends ModerationStateTestBase {
|
|||
|
||||
// Now make another user, who should not be able to see forward revisions.
|
||||
$user = $this->createUser([
|
||||
'use published_draft transition',
|
||||
'use editorial transition create_new_draft',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\content_moderation\Permissions;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Test to ensure content moderation permissions are generated correctly.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ContentModerationPermissionsTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'workflows',
|
||||
'content_moderation',
|
||||
'workflow_type_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('workflow');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test permissions generated by content moderation.
|
||||
*
|
||||
* @dataProvider permissionsTestCases
|
||||
*/
|
||||
public function testPermissions($workflow, $permissions) {
|
||||
Workflow::create($workflow)->save();
|
||||
$this->assertEquals($permissions, (new Permissions())->transitionPermissions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cases for ::testPermissions
|
||||
*
|
||||
* @return array
|
||||
* Content moderation permissions based test cases.
|
||||
*/
|
||||
public function permissionsTestCases() {
|
||||
return [
|
||||
'Simple Content Moderation Workflow' => [
|
||||
[
|
||||
'id' => 'simple_workflow',
|
||||
'label' => 'Simple Workflow',
|
||||
'type' => 'content_moderation',
|
||||
'transitions' => [
|
||||
'publish' => [
|
||||
'label' => 'Publish',
|
||||
'from' => ['draft'],
|
||||
'to' => 'published',
|
||||
'weight' => 0,
|
||||
],
|
||||
'unpublish' => [
|
||||
'label' => 'Unpublish',
|
||||
'from' => ['published'],
|
||||
'to' => 'draft',
|
||||
'weight' => 0,
|
||||
],
|
||||
],
|
||||
'states' => [
|
||||
'draft' => [
|
||||
'label' => 'Draft',
|
||||
'weight' => -5,
|
||||
],
|
||||
'published' => [
|
||||
'label' => 'Published',
|
||||
'weight' => 0,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'use simple_workflow transition publish' => [
|
||||
'title' => 'Use <em class="placeholder">Publish</em> transition from <em class="placeholder">Simple Workflow</em> workflow.',
|
||||
],
|
||||
'use simple_workflow transition unpublish' => [
|
||||
'title' => 'Use <em class="placeholder">Unpublish</em> transition from <em class="placeholder">Simple Workflow</em> workflow.',
|
||||
],
|
||||
],
|
||||
],
|
||||
'Non Content Moderation Workflow' => [
|
||||
[
|
||||
'id' => 'morning',
|
||||
'label' => 'Morning',
|
||||
'type' => 'workflow_type_test',
|
||||
'transitions' => [
|
||||
'drink_coffee' => [
|
||||
'label' => 'Drink Coffee',
|
||||
'from' => ['tired'],
|
||||
'to' => 'awake',
|
||||
'weight' => 0,
|
||||
],
|
||||
],
|
||||
'states' => [
|
||||
'awake' => [
|
||||
'label' => 'Awake',
|
||||
'weight' => -5,
|
||||
],
|
||||
'tired' => [
|
||||
'label' => 'Tired',
|
||||
'weight' => -0,
|
||||
],
|
||||
],
|
||||
],
|
||||
[]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\config\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
use Drupal\content_moderation\Entity\ModerationStateTransition;
|
||||
|
||||
/**
|
||||
* Ensures that content moderation schema is correct.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ContentModerationSchemaTest extends KernelTestBase {
|
||||
|
||||
use SchemaCheckTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation',
|
||||
'node',
|
||||
'user',
|
||||
'block_content',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests content moderation default schema.
|
||||
*/
|
||||
public function testContentModerationDefaultConfig() {
|
||||
$this->installConfig(['content_moderation']);
|
||||
$typed_config = \Drupal::service('config.typed');
|
||||
$moderation_states = ModerationState::loadMultiple();
|
||||
foreach ($moderation_states as $moderation_state) {
|
||||
$this->assertConfigSchema($typed_config, $moderation_state->getEntityType()->getConfigPrefix() . '.' . $moderation_state->id(), $moderation_state->toArray());
|
||||
}
|
||||
$moderation_state_transitions = ModerationStateTransition::loadMultiple();
|
||||
foreach ($moderation_state_transitions as $moderation_state_transition) {
|
||||
$this->assertConfigSchema($typed_config, $moderation_state_transition->getEntityType()->getConfigPrefix() . '.' . $moderation_state_transition->id(), $moderation_state_transition->toArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests content moderation third party schema for node types.
|
||||
*/
|
||||
public function testContentModerationNodeTypeConfig() {
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installConfig(['content_moderation']);
|
||||
$typed_config = \Drupal::service('config.typed');
|
||||
$moderation_states = ModerationState::loadMultiple();
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($moderation_states));
|
||||
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', '');
|
||||
$node_type->save();
|
||||
$this->assertConfigSchema($typed_config, $node_type->getEntityType()->getConfigPrefix() . '.' . $node_type->id(), $node_type->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests content moderation third party schema for block content types.
|
||||
*/
|
||||
public function testContentModerationBlockContentTypeConfig() {
|
||||
$this->installEntitySchema('block_content');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installConfig(['content_moderation']);
|
||||
$typed_config = \Drupal::service('config.typed');
|
||||
$moderation_states = ModerationState::loadMultiple();
|
||||
$block_content_type = BlockContentType::create([
|
||||
'id' => 'basic',
|
||||
'label' => 'basic',
|
||||
'revision' => TRUE,
|
||||
]);
|
||||
$block_content_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$block_content_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($moderation_states));
|
||||
$block_content_type->setThirdPartySetting('content_moderation', 'default_moderation_state', '');
|
||||
$block_content_type->save();
|
||||
$this->assertConfigSchema($typed_config, $block_content_type->getEntityType()->getConfigPrefix() . '.' . $block_content_type->id(), $block_content_type->toArray());
|
||||
}
|
||||
|
||||
}
|
|
@ -3,14 +3,16 @@
|
|||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\content_moderation\Entity\ContentModerationState;
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\entity_test\Entity\EntityTestWithBundle;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests links between a content entity and a content_moderation_state entity.
|
||||
|
@ -25,14 +27,21 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
public static $modules = [
|
||||
'entity_test',
|
||||
'node',
|
||||
'block_content',
|
||||
'content_moderation',
|
||||
'user',
|
||||
'system',
|
||||
'language',
|
||||
'content_translation',
|
||||
'text',
|
||||
'workflows',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityTypeManager
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -43,35 +52,57 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('entity_test_with_bundle');
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->installEntitySchema('block_content');
|
||||
$this->installEntitySchema('content_moderation_state');
|
||||
$this->installConfig('content_moderation');
|
||||
|
||||
$this->entityTypeManager = $this->container->get('entity_type.manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic monolingual content moderation through the API.
|
||||
*
|
||||
* @dataProvider basicModerationTestCases
|
||||
*/
|
||||
public function testBasicModeration() {
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
|
||||
$node_type->save();
|
||||
$node = Node::create([
|
||||
'type' => 'example',
|
||||
public function testBasicModeration($entity_type_id) {
|
||||
// Make the 'entity_test_with_bundle' entity type revisionable.
|
||||
if ($entity_type_id == 'entity_test_with_bundle') {
|
||||
$this->setEntityTestWithBundleKeys(['revision' => 'revision_id']);
|
||||
}
|
||||
|
||||
$entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
$bundle_id = $entity_type_id;
|
||||
$bundle_entity_type_id = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType();
|
||||
if ($bundle_entity_type_id) {
|
||||
$bundle_entity_type_definition = $this->entityTypeManager->getDefinition($bundle_entity_type_id);
|
||||
$entity_type_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
|
||||
|
||||
$entity_type = $entity_type_storage->create([
|
||||
$bundle_entity_type_definition->getKey('id') => 'example',
|
||||
]);
|
||||
$entity_type->save();
|
||||
$bundle_id = $entity_type->id();
|
||||
}
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id);
|
||||
$workflow->save();
|
||||
|
||||
$entity = $entity_storage->create([
|
||||
'title' => 'Test title',
|
||||
$this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id,
|
||||
]);
|
||||
$node->save();
|
||||
$node = $this->reloadNode($node);
|
||||
$this->assertEquals('draft', $node->moderation_state->entity->id());
|
||||
$entity->save();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEquals('draft', $entity->moderation_state->value);
|
||||
|
||||
$published = ModerationState::load('published');
|
||||
$node->moderation_state->entity = $published;
|
||||
$node->save();
|
||||
$entity->moderation_state->value = 'published';
|
||||
$entity->save();
|
||||
|
||||
$node = $this->reloadNode($node);
|
||||
$this->assertEquals('published', $node->moderation_state->entity->id());
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEquals('published', $entity->moderation_state->value);
|
||||
|
||||
// Change the state without saving the node.
|
||||
$content_moderation_state = ContentModerationState::load(1);
|
||||
|
@ -79,26 +110,76 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
$content_moderation_state->setNewRevision(TRUE);
|
||||
$content_moderation_state->save();
|
||||
|
||||
$node = $this->reloadNode($node, 3);
|
||||
$this->assertEquals('draft', $node->moderation_state->entity->id());
|
||||
$this->assertFalse($node->isPublished());
|
||||
$entity = $this->reloadEntity($entity, 3);
|
||||
$this->assertEquals('draft', $entity->moderation_state->value);
|
||||
if ($entity instanceof EntityPublishedInterface) {
|
||||
$this->assertFalse($entity->isPublished());
|
||||
}
|
||||
|
||||
// Get the default revision.
|
||||
$node = $this->reloadNode($node);
|
||||
$this->assertTrue($node->isPublished());
|
||||
$this->assertEquals(2, $node->getRevisionId());
|
||||
$entity = $this->reloadEntity($entity);
|
||||
if ($entity instanceof EntityPublishedInterface) {
|
||||
$this->assertTrue((bool) $entity->isPublished());
|
||||
}
|
||||
$this->assertEquals(2, $entity->getRevisionId());
|
||||
|
||||
$node->moderation_state->target_id = 'published';
|
||||
$node->save();
|
||||
$entity->moderation_state->value = 'published';
|
||||
$entity->save();
|
||||
|
||||
$node = $this->reloadNode($node, 4);
|
||||
$this->assertEquals('published', $node->moderation_state->entity->id());
|
||||
$entity = $this->reloadEntity($entity, 4);
|
||||
$this->assertEquals('published', $entity->moderation_state->value);
|
||||
|
||||
// Get the default revision.
|
||||
$node = $this->reloadNode($node);
|
||||
$this->assertTrue($node->isPublished());
|
||||
$this->assertEquals(4, $node->getRevisionId());
|
||||
$entity = $this->reloadEntity($entity);
|
||||
if ($entity instanceof EntityPublishedInterface) {
|
||||
$this->assertTrue((bool) $entity->isPublished());
|
||||
}
|
||||
$this->assertEquals(4, $entity->getRevisionId());
|
||||
|
||||
// Update the node to archived which will then be the default revision.
|
||||
$entity->moderation_state->value = 'archived';
|
||||
$entity->save();
|
||||
|
||||
// Revert to the previous (published) revision.
|
||||
$previous_revision = $entity_storage->loadRevision(4);
|
||||
$previous_revision->isDefaultRevision(TRUE);
|
||||
$previous_revision->setNewRevision(TRUE);
|
||||
$previous_revision->save();
|
||||
|
||||
// Get the default revision.
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEquals('published', $entity->moderation_state->value);
|
||||
if ($entity instanceof EntityPublishedInterface) {
|
||||
$this->assertTrue($entity->isPublished());
|
||||
}
|
||||
|
||||
// Set an invalid moderation state.
|
||||
$this->setExpectedException(EntityStorageException::class);
|
||||
$entity->moderation_state->value = 'foobar';
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cases for basic moderation test.
|
||||
*/
|
||||
public function basicModerationTestCases() {
|
||||
return [
|
||||
'Nodes' => [
|
||||
'node',
|
||||
],
|
||||
'Block content' => [
|
||||
'block_content',
|
||||
],
|
||||
'Test Entity with Bundle' => [
|
||||
'entity_test_with_bundle',
|
||||
],
|
||||
'Test entity - revisions, data table, and published interface' => [
|
||||
'entity_test_mulrevpub',
|
||||
],
|
||||
'Entity Test with revisions' => [
|
||||
'entity_test_rev',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,75 +191,77 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
|
||||
$node_type->save();
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
|
||||
$english_node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Test title',
|
||||
]);
|
||||
// Revision 1 (en).
|
||||
$english_node
|
||||
->setPublished(FALSE)
|
||||
->setUnpublished()
|
||||
->save();
|
||||
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
|
||||
$this->assertEquals('draft', $english_node->moderation_state->value);
|
||||
$this->assertFalse($english_node->isPublished());
|
||||
|
||||
// Create a French translation.
|
||||
$french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
|
||||
$french_node->setPublished(FALSE);
|
||||
$french_node->setUnpublished();
|
||||
// Revision 1 (fr).
|
||||
$french_node->save();
|
||||
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
|
||||
$this->assertEquals('draft', $french_node->moderation_state->entity->id());
|
||||
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
|
||||
$this->assertEquals('draft', $french_node->moderation_state->value);
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
|
||||
// Move English node to create another draft.
|
||||
$english_node = $this->reloadNode($english_node);
|
||||
$english_node->moderation_state->target_id = 'draft';
|
||||
$english_node = $this->reloadEntity($english_node);
|
||||
$english_node->moderation_state->value = 'draft';
|
||||
// Revision 2 (en, fr).
|
||||
$english_node->save();
|
||||
$english_node = $this->reloadNode($english_node);
|
||||
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
|
||||
$english_node = $this->reloadEntity($english_node);
|
||||
$this->assertEquals('draft', $english_node->moderation_state->value);
|
||||
|
||||
// French node should still be in draft.
|
||||
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
|
||||
$this->assertEquals('draft', $french_node->moderation_state->entity->id());
|
||||
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
|
||||
$this->assertEquals('draft', $french_node->moderation_state->value);
|
||||
|
||||
// Publish the French node.
|
||||
$french_node->moderation_state->target_id = 'published';
|
||||
$french_node->moderation_state->value = 'published';
|
||||
// Revision 3 (en, fr).
|
||||
$french_node->save();
|
||||
$french_node = $this->reloadNode($french_node)->getTranslation('fr');
|
||||
$french_node = $this->reloadEntity($french_node)->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$this->assertEquals('published', $french_node->moderation_state->entity->id());
|
||||
$this->assertEquals('published', $french_node->moderation_state->value);
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$english_node = $french_node->getTranslation('en');
|
||||
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
|
||||
$this->assertEquals('draft', $english_node->moderation_state->value);
|
||||
|
||||
// Publish the English node.
|
||||
$english_node->moderation_state->target_id = 'published';
|
||||
$english_node->moderation_state->value = 'published';
|
||||
// Revision 4 (en, fr).
|
||||
$english_node->save();
|
||||
$english_node = $this->reloadNode($english_node);
|
||||
$english_node = $this->reloadEntity($english_node);
|
||||
$this->assertTrue($english_node->isPublished());
|
||||
|
||||
// Move the French node back to draft.
|
||||
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
|
||||
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$french_node->moderation_state->target_id = 'draft';
|
||||
$french_node->moderation_state->value = 'draft';
|
||||
// Revision 5 (en, fr).
|
||||
$french_node->save();
|
||||
$french_node = $this->reloadNode($english_node, 5)->getTranslation('fr');
|
||||
$french_node = $this->reloadEntity($english_node, 5)->getTranslation('fr');
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
$this->assertTrue($french_node->getTranslation('en')->isPublished());
|
||||
|
||||
// Republish the French node.
|
||||
$french_node->moderation_state->target_id = 'published';
|
||||
$french_node->moderation_state->value = 'published';
|
||||
// Revision 6 (en, fr).
|
||||
$french_node->save();
|
||||
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
|
||||
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
|
||||
// Change the EN state without saving the node.
|
||||
|
@ -187,11 +270,11 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
$content_moderation_state->setNewRevision(TRUE);
|
||||
// Revision 7 (en, fr).
|
||||
$content_moderation_state->save();
|
||||
$english_node = $this->reloadNode($french_node, $french_node->getRevisionId() + 1);
|
||||
$english_node = $this->reloadEntity($french_node, $french_node->getRevisionId() + 1);
|
||||
|
||||
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
|
||||
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
|
||||
$this->assertEquals('published', $french_node->moderation_state->entity->id());
|
||||
$this->assertEquals('draft', $english_node->moderation_state->value);
|
||||
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
|
||||
$this->assertEquals('published', $french_node->moderation_state->value);
|
||||
|
||||
// This should unpublish the French node.
|
||||
$content_moderation_state = ContentModerationState::load(1);
|
||||
|
@ -201,16 +284,16 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
// Revision 8 (en, fr).
|
||||
$content_moderation_state->save();
|
||||
|
||||
$english_node = $this->reloadNode($english_node, $english_node->getRevisionId());
|
||||
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
|
||||
$french_node = $this->reloadNode($english_node, '8')->getTranslation('fr');
|
||||
$this->assertEquals('draft', $french_node->moderation_state->entity->id());
|
||||
$english_node = $this->reloadEntity($english_node, $english_node->getRevisionId());
|
||||
$this->assertEquals('draft', $english_node->moderation_state->value);
|
||||
$french_node = $this->reloadEntity($english_node, '8')->getTranslation('fr');
|
||||
$this->assertEquals('draft', $french_node->moderation_state->value);
|
||||
// Switching the moderation state to an unpublished state should update the
|
||||
// entity.
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
|
||||
// Get the default english node.
|
||||
$english_node = $this->reloadNode($english_node);
|
||||
$english_node = $this->reloadEntity($english_node);
|
||||
$this->assertTrue($english_node->isPublished());
|
||||
$this->assertEquals(6, $english_node->getRevisionId());
|
||||
}
|
||||
|
@ -220,25 +303,18 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
*/
|
||||
public function testNonTranslatableEntityTypeModeration() {
|
||||
// Make the 'entity_test_with_bundle' entity type revisionable.
|
||||
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
|
||||
$keys = $entity_type->getKeys();
|
||||
$keys['revision'] = 'revision_id';
|
||||
$entity_type->set('entity_keys', $keys);
|
||||
\Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
|
||||
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
|
||||
$this->setEntityTestWithBundleKeys(['revision' => 'revision_id']);
|
||||
|
||||
// Create a test bundle.
|
||||
$entity_test_bundle = EntityTestBundle::create([
|
||||
'id' => 'example',
|
||||
]);
|
||||
$entity_test_bundle->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$entity_test_bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', [
|
||||
'draft',
|
||||
'published'
|
||||
]);
|
||||
$entity_test_bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
|
||||
$entity_test_bundle->save();
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_with_bundle', 'example');
|
||||
$workflow->save();
|
||||
|
||||
// Check that the tested entity type is not translatable.
|
||||
$entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
|
||||
$this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
|
||||
|
@ -248,12 +324,12 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
'type' => 'example'
|
||||
]);
|
||||
$entity_test_with_bundle->save();
|
||||
$this->assertEquals('draft', $entity_test_with_bundle->moderation_state->entity->id());
|
||||
$this->assertEquals('draft', $entity_test_with_bundle->moderation_state->value);
|
||||
|
||||
$entity_test_with_bundle->moderation_state->target_id = 'published';
|
||||
$entity_test_with_bundle->moderation_state->value = 'published';
|
||||
$entity_test_with_bundle->save();
|
||||
|
||||
$this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->entity->id());
|
||||
$this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -263,26 +339,18 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
public function testNonLangcodeEntityTypeModeration() {
|
||||
// Make the 'entity_test_with_bundle' entity type revisionable and unset
|
||||
// the langcode entity key.
|
||||
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
|
||||
$keys = $entity_type->getKeys();
|
||||
$keys['revision'] = 'revision_id';
|
||||
unset($keys['langcode']);
|
||||
$entity_type->set('entity_keys', $keys);
|
||||
\Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
|
||||
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
|
||||
$this->setEntityTestWithBundleKeys(['revision' => 'revision_id'], ['langcode']);
|
||||
|
||||
// Create a test bundle.
|
||||
$entity_test_bundle = EntityTestBundle::create([
|
||||
'id' => 'example',
|
||||
]);
|
||||
$entity_test_bundle->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$entity_test_bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', [
|
||||
'draft',
|
||||
'published'
|
||||
]);
|
||||
$entity_test_bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
|
||||
$entity_test_bundle->save();
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_with_bundle', 'example');
|
||||
$workflow->save();
|
||||
|
||||
// Check that the tested entity type is not translatable.
|
||||
$entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
|
||||
$this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
|
||||
|
@ -292,33 +360,94 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
'type' => 'example'
|
||||
]);
|
||||
$entity_test_with_bundle->save();
|
||||
$this->assertEquals('draft', $entity_test_with_bundle->moderation_state->entity->id());
|
||||
$this->assertEquals('draft', $entity_test_with_bundle->moderation_state->value);
|
||||
|
||||
$entity_test_with_bundle->moderation_state->target_id = 'published';
|
||||
$entity_test_with_bundle->moderation_state->value = 'published';
|
||||
$entity_test_with_bundle->save();
|
||||
|
||||
$this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->entity->id());
|
||||
$this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the node after clearing the static cache.
|
||||
* Set the keys on the test entity type.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node to reload.
|
||||
* @param int|false $revision_id
|
||||
* @param array $keys
|
||||
* The entity keys to override
|
||||
* @param array $remove_keys
|
||||
* Keys to remove.
|
||||
*/
|
||||
protected function setEntityTestWithBundleKeys($keys, $remove_keys = []) {
|
||||
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
|
||||
$original_keys = $entity_type->getKeys();
|
||||
foreach ($remove_keys as $remove_key) {
|
||||
unset($original_keys[$remove_key]);
|
||||
}
|
||||
$entity_type->set('entity_keys', $keys + $original_keys);
|
||||
\Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
|
||||
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the dependencies of the workflow when using content moderation.
|
||||
*/
|
||||
public function testWorkflowDependencies() {
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->save();
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
// Test both a config and non-config based bundle and entity type.
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
|
||||
$workflow->save();
|
||||
|
||||
$this->assertEquals([
|
||||
'module' => [
|
||||
'content_moderation',
|
||||
'entity_test',
|
||||
],
|
||||
'config' => [
|
||||
'node.type.example',
|
||||
],
|
||||
], $workflow->getDependencies());
|
||||
|
||||
$entity_types = $workflow->getTypePlugin()->getEntityTypes();
|
||||
$this->assertTrue(in_array('node', $entity_types));
|
||||
$this->assertTrue(in_array('entity_test_rev', $entity_types));
|
||||
|
||||
// Delete the node type and ensure it is removed from the workflow.
|
||||
$node_type->delete();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$entity_types = $workflow->getTypePlugin()->getEntityTypes();
|
||||
$this->assertFalse(in_array('node', $entity_types));
|
||||
|
||||
// Uninstall entity test and ensure it's removed from the workflow.
|
||||
$this->container->get('config.manager')->uninstall('module', 'entity_test');
|
||||
$workflow = Workflow::load('editorial');
|
||||
$entity_types = $workflow->getTypePlugin()->getEntityTypes();
|
||||
$this->assertFalse(in_array('entity_test_rev', $entity_types));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the entity after clearing the static cache.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to reload.
|
||||
* @param int|bool $revision_id
|
||||
* The specific revision ID to load. Defaults FALSE and just loads the
|
||||
* default revision.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* The reloaded node.
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The reloaded entity.
|
||||
*/
|
||||
protected function reloadNode(NodeInterface $node, $revision_id = FALSE) {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('node');
|
||||
$storage->resetCache([$node->id()]);
|
||||
protected function reloadEntity(EntityInterface $entity, $revision_id = FALSE) {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
|
||||
$storage->resetCache([$entity->id()]);
|
||||
if ($revision_id) {
|
||||
return $storage->loadRevision($revision_id);
|
||||
}
|
||||
return $storage->load($node->id());
|
||||
return $storage->load($entity->id());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests the API of the ContentModeration workflow type plugin.
|
||||
*
|
||||
* @group content_moderation
|
||||
*
|
||||
* @coversDefaultClass \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration
|
||||
*/
|
||||
class ContentModerationWorkflowTypeApiTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* A workflow for testing.
|
||||
*
|
||||
* @var \Drupal\workflows\Entity\Workflow;
|
||||
*/
|
||||
protected $workflow;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'workflows',
|
||||
'content_moderation',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->workflow = Workflow::create(['id' => 'test', 'type' => 'content_moderation']);
|
||||
$this->workflow
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getBundlesForEntityType
|
||||
* @covers ::addEntityTypeAndBundle
|
||||
* @covers ::removeEntityTypeAndBundle
|
||||
*/
|
||||
public function testGetBundlesForEntityType() {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $workflow_plugin */
|
||||
$workflow_plugin = $this->workflow->getTypePlugin();
|
||||
// The content moderation plugin does not validate the existence of the
|
||||
// entity type or bundle.
|
||||
$this->assertEquals([], $workflow_plugin->getBundlesForEntityType('fake_node'));
|
||||
$workflow_plugin->addEntityTypeAndBundle('fake_node', 'fake_page');
|
||||
$this->assertEquals(['fake_page'], $workflow_plugin->getBundlesForEntityType('fake_node'));
|
||||
$this->assertEquals([], $workflow_plugin->getBundlesForEntityType('fake_block'));
|
||||
$workflow_plugin->removeEntityTypeAndBundle('fake_node', 'fake_page');
|
||||
$this->assertEquals([], $workflow_plugin->getBundlesForEntityType('fake_node'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::appliesToEntityTypeAndBundle
|
||||
* @covers ::addEntityTypeAndBundle
|
||||
* @covers ::removeEntityTypeAndBundle
|
||||
*/
|
||||
public function testAppliesToEntityTypeAndBundle() {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $workflow_plugin */
|
||||
$workflow_plugin = $this->workflow->getTypePlugin();
|
||||
// The content moderation plugin does not validate the existence of the
|
||||
// entity type or bundle.
|
||||
$this->assertFalse($workflow_plugin->appliesToEntityTypeAndBundle('fake_node', 'fake_page'));
|
||||
$workflow_plugin->addEntityTypeAndBundle('fake_node', 'fake_page');
|
||||
$this->assertTrue($workflow_plugin->appliesToEntityTypeAndBundle('fake_node', 'fake_page'));
|
||||
$this->assertFalse($workflow_plugin->appliesToEntityTypeAndBundle('fake_block', 'fake_custom'));
|
||||
$workflow_plugin->removeEntityTypeAndBundle('fake_node', 'fake_page');
|
||||
$this->assertFalse($workflow_plugin->appliesToEntityTypeAndBundle('fake_node', 'fake_page'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addEntityTypeAndBundle
|
||||
*/
|
||||
public function testAddEntityTypeAndBundle() {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $workflow_plugin */
|
||||
$workflow_plugin = $this->workflow->getTypePlugin();
|
||||
|
||||
// The bundles are intentionally added in reverse alphabetical order.
|
||||
$workflow_plugin->addEntityTypeAndBundle('fake_node', 'fake_page');
|
||||
$workflow_plugin->addEntityTypeAndBundle('fake_node', 'fake_article');
|
||||
|
||||
// Add another entity type that comes alphabetically before 'fake_node'.
|
||||
$workflow_plugin->addEntityTypeAndBundle('fake_block', 'fake_custom');
|
||||
|
||||
// The entity type keys and bundle values should be sorted alphabetically.
|
||||
// The bundle array index should not reflect the order in which they are
|
||||
// added.
|
||||
$this->assertSame(
|
||||
['fake_block' => ['fake_custom'], 'fake_node' => ['fake_article', 'fake_page']],
|
||||
$workflow_plugin->getConfiguration()['entity_types']
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -4,9 +4,9 @@ namespace Drupal\Tests\content_moderation\Kernel;
|
|||
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\EntityOperations
|
||||
|
@ -23,6 +23,7 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
'node',
|
||||
'user',
|
||||
'system',
|
||||
'workflows',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -47,8 +48,10 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
'type' => 'page',
|
||||
'label' => 'Page',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'page');
|
||||
$workflow->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +63,7 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
'type' => 'page',
|
||||
'title' => 'A',
|
||||
]);
|
||||
$page->moderation_state->target_id = 'draft';
|
||||
$page->moderation_state->value = 'draft';
|
||||
$page->save();
|
||||
|
||||
$id = $page->id();
|
||||
|
@ -75,7 +78,7 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
|
||||
// Moderate the entity to published.
|
||||
$page->setTitle('B');
|
||||
$page->moderation_state->target_id = 'published';
|
||||
$page->moderation_state->value = 'published';
|
||||
$page->save();
|
||||
|
||||
// Verify the entity is now published and public.
|
||||
|
@ -86,7 +89,7 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
|
||||
// Make a new forward-revision in Draft.
|
||||
$page->setTitle('C');
|
||||
$page->moderation_state->target_id = 'draft';
|
||||
$page->moderation_state->value = 'draft';
|
||||
$page->save();
|
||||
|
||||
// Verify normal loads return the still-default previous version.
|
||||
|
@ -105,7 +108,7 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
$this->assertEquals('C', $page->getTitle());
|
||||
|
||||
$page->setTitle('D');
|
||||
$page->moderation_state->target_id = 'published';
|
||||
$page->moderation_state->value = 'published';
|
||||
$page->save();
|
||||
|
||||
// Verify normal loads return the still-default previous version.
|
||||
|
@ -116,7 +119,7 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
|
||||
// Now check that we can immediately add a new published revision over it.
|
||||
$page->setTitle('E');
|
||||
$page->moderation_state->target_id = 'published';
|
||||
$page->moderation_state->value = 'published';
|
||||
$page->save();
|
||||
|
||||
$page = Node::load($id);
|
||||
|
@ -134,7 +137,7 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
'type' => 'page',
|
||||
'title' => 'A',
|
||||
]);
|
||||
$page->moderation_state->target_id = 'published';
|
||||
$page->moderation_state->value = 'published';
|
||||
$page->save();
|
||||
|
||||
$id = $page->id();
|
||||
|
@ -151,29 +154,12 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
* Verifies that an unpublished state may be made the default revision.
|
||||
*/
|
||||
public function testArchive() {
|
||||
$published_id = $this->randomMachineName();
|
||||
$published_state = ModerationState::create([
|
||||
'id' => $published_id,
|
||||
'label' => $this->randomString(),
|
||||
'published' => TRUE,
|
||||
'default_revision' => TRUE,
|
||||
]);
|
||||
$published_state->save();
|
||||
|
||||
$archived_id = $this->randomMachineName();
|
||||
$archived_state = ModerationState::create([
|
||||
'id' => $archived_id,
|
||||
'label' => $this->randomString(),
|
||||
'published' => FALSE,
|
||||
'default_revision' => TRUE,
|
||||
]);
|
||||
$archived_state->save();
|
||||
|
||||
$page = Node::create([
|
||||
'type' => 'page',
|
||||
'title' => $this->randomString(),
|
||||
]);
|
||||
$page->moderation_state->target_id = $published_id;
|
||||
|
||||
$page->moderation_state->value = 'published';
|
||||
$page->save();
|
||||
|
||||
$id = $page->id();
|
||||
|
@ -184,7 +170,7 @@ class EntityOperationsTest extends KernelTestBase {
|
|||
|
||||
// When the page is moderated to the archived state, then the latest
|
||||
// revision should be the default revision, and it should be unpublished.
|
||||
$page->moderation_state->target_id = $archived_id;
|
||||
$page->moderation_state->value = 'archived';
|
||||
$page->save();
|
||||
$new_revision_id = $page->getRevisionId();
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use Drupal\entity_test\Entity\EntityTest;
|
|||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\ParamConverter\EntityRevisionConverter
|
||||
|
@ -19,6 +20,7 @@ class EntityRevisionConverterTest extends KernelTestBase {
|
|||
'system',
|
||||
'content_moderation',
|
||||
'node',
|
||||
'workflows',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -59,17 +61,21 @@ class EntityRevisionConverterTest extends KernelTestBase {
|
|||
* @covers ::convert
|
||||
*/
|
||||
public function testConvertWithRevisionableEntityType() {
|
||||
$this->installConfig(['content_moderation']);
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'article',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
|
||||
$workflow->save();
|
||||
|
||||
$revision_ids = [];
|
||||
$node = Node::create([
|
||||
'title' => 'test',
|
||||
'type' => 'article',
|
||||
]);
|
||||
$node->moderation_state->value = 'published';
|
||||
$node->save();
|
||||
|
||||
$revision_ids[] = $node->getRevisionId();
|
||||
|
@ -79,7 +85,7 @@ class EntityRevisionConverterTest extends KernelTestBase {
|
|||
$revision_ids[] = $node->getRevisionId();
|
||||
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->isDefaultRevision(FALSE);
|
||||
$node->moderation_state->value = 'draft';
|
||||
$node->save();
|
||||
$revision_ids[] = $node->getRevisionId();
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use Drupal\KernelTests\KernelTestBase;
|
|||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\Plugin\Validation\Constraint\ModerationStateConstraintValidator
|
||||
|
@ -23,6 +24,7 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
|||
'system',
|
||||
'language',
|
||||
'content_translation',
|
||||
'workflows',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -47,20 +49,23 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
|||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
|
||||
$node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Test title',
|
||||
]);
|
||||
$node->moderation_state->target_id = 'draft';
|
||||
$node->moderation_state->value = 'draft';
|
||||
$node->save();
|
||||
|
||||
$node->moderation_state->target_id = 'published';
|
||||
$node->moderation_state->value = 'published';
|
||||
$this->assertCount(0, $node->validate());
|
||||
$node->save();
|
||||
|
||||
$this->assertEquals('published', $node->moderation_state->entity->id());
|
||||
$this->assertEquals('published', $node->moderation_state->value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,16 +77,19 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
|||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
|
||||
$node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Test title',
|
||||
]);
|
||||
$node->moderation_state->target_id = 'draft';
|
||||
$node->moderation_state->value = 'draft';
|
||||
$node->save();
|
||||
|
||||
$node->moderation_state->target_id = 'archived';
|
||||
$node->moderation_state->value = 'archived';
|
||||
$violations = $node->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
|
||||
|
@ -106,12 +114,9 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
|||
$nid = $node->id();
|
||||
|
||||
// Enable moderation for our node type.
|
||||
/** @var NodeType $node_type */
|
||||
$node_type = NodeType::load('example');
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
|
||||
$node = Node::load($nid);
|
||||
|
||||
|
@ -155,12 +160,9 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
|||
$node_fr->save();
|
||||
|
||||
// Enable moderation for our node type.
|
||||
/** @var NodeType $node_type */
|
||||
$node_type = NodeType::load('example');
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
|
||||
// Reload the French version of the node.
|
||||
$node = Node::load($nid);
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\content_moderation\Entity\ModerationState;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\Entity\ModerationState
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ModerationStateEntityTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['content_moderation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('moderation_state');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify moderation state methods based on entity properties.
|
||||
*
|
||||
* @covers ::isPublishedState
|
||||
* @covers ::isDefaultRevisionState
|
||||
*
|
||||
* @dataProvider moderationStateProvider
|
||||
*/
|
||||
public function testModerationStateProperties($published, $default_revision, $is_published, $is_default) {
|
||||
$moderation_state_id = $this->randomMachineName();
|
||||
$moderation_state = ModerationState::create([
|
||||
'id' => $moderation_state_id,
|
||||
'label' => $this->randomString(),
|
||||
'published' => $published,
|
||||
'default_revision' => $default_revision,
|
||||
]);
|
||||
$moderation_state->save();
|
||||
|
||||
$moderation_state = ModerationState::load($moderation_state_id);
|
||||
$this->assertEquals($is_published, $moderation_state->isPublishedState());
|
||||
$this->assertEquals($is_default, $moderation_state->isDefaultRevisionState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testModerationStateProperties.
|
||||
*/
|
||||
public function moderationStateProvider() {
|
||||
return [
|
||||
// Draft, Needs review; should not touch the default revision.
|
||||
[FALSE, FALSE, FALSE, FALSE],
|
||||
// Published; this state should update and publish the default revision.
|
||||
[TRUE, TRUE, TRUE, TRUE],
|
||||
// Archive; this state should update but not publish the default revision.
|
||||
[FALSE, TRUE, FALSE, TRUE],
|
||||
// We try to prevent creating this state via the UI, but when a moderation
|
||||
// state is a published state, it should also become the default revision.
|
||||
[TRUE, FALSE, TRUE, TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace Drupal\Tests\content_moderation\Kernel;
|
|||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList
|
||||
|
@ -22,6 +23,7 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
|||
'user',
|
||||
'system',
|
||||
'language',
|
||||
'workflows',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -44,10 +46,11 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
|||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft']);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
|
||||
$this->testNode = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Test title',
|
||||
|
@ -61,7 +64,7 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
|||
* Test the field item list when accessing an index.
|
||||
*/
|
||||
public function testArrayIndex() {
|
||||
$this->assertEquals('draft', $this->testNode->moderation_state[0]->entity->id());
|
||||
$this->assertEquals('draft', $this->testNode->moderation_state[0]->value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +73,7 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
|||
public function testArrayIteration() {
|
||||
$states = [];
|
||||
foreach ($this->testNode->moderation_state as $item) {
|
||||
$states[] = $item->entity->id();
|
||||
$states[] = $item->value;
|
||||
}
|
||||
$this->assertEquals(['draft'], $states);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Test the state field formatter.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class StateFormatterTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'workflows',
|
||||
'content_moderation',
|
||||
'entity_test',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->installEntitySchema('content_moderation_state');
|
||||
$this->installConfig('content_moderation');
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
|
||||
$workflow->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the embed field.
|
||||
*
|
||||
* @dataProvider formatterTestCases
|
||||
*/
|
||||
public function testStateFieldFormatter($field_value, $formatter_settings, $expected_output) {
|
||||
$entity = EntityTestRev::create([
|
||||
'moderation_state' => $field_value,
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$field_output = $this->container->get('renderer')->executeInRenderContext(new RenderContext(), function() use ($entity, $formatter_settings) {
|
||||
return $entity->moderation_state->view($formatter_settings);
|
||||
});
|
||||
|
||||
$this->assertEquals($expected_output, $field_output[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cases for ::
|
||||
*/
|
||||
public function formatterTestCases() {
|
||||
return [
|
||||
'Draft State' => [
|
||||
'draft',
|
||||
[
|
||||
'type' => 'content_moderation_state',
|
||||
'settings' => [],
|
||||
],
|
||||
[
|
||||
'#markup' => 'Draft',
|
||||
],
|
||||
],
|
||||
'Published State' => [
|
||||
'published',
|
||||
[
|
||||
'type' => 'content_moderation_state',
|
||||
'settings' => [],
|
||||
],
|
||||
[
|
||||
'#markup' => 'Published',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests the views integration of content_moderation.
|
||||
|
@ -21,6 +23,8 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
|||
'content_moderation_test_views',
|
||||
'node',
|
||||
'content_moderation',
|
||||
'workflows',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -30,6 +34,7 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
|||
parent::setUp($import_test_views);
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('content_moderation_state');
|
||||
$this->installSchema('node', 'node_access');
|
||||
|
@ -39,8 +44,11 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
|||
$node_type = NodeType::create([
|
||||
'type' => 'page',
|
||||
]);
|
||||
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'page');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_mulrevpub', 'entity_test_mulrevpub');
|
||||
$workflow->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,14 +61,23 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
|||
'type' => 'page',
|
||||
'title' => 'Test title first revision',
|
||||
]);
|
||||
$node->moderation_state->target_id = 'published';
|
||||
$node->moderation_state->value = 'published';
|
||||
$node->save();
|
||||
|
||||
// Create a totally unrelated entity to ensure the extra join information
|
||||
// joins by the correct entity type.
|
||||
$unrelated_entity = EntityTestMulRevPub::create([
|
||||
'id' => $node->id(),
|
||||
]);
|
||||
$unrelated_entity->save();
|
||||
|
||||
$this->assertEquals($unrelated_entity->id(), $node->id());
|
||||
|
||||
$revision = clone $node;
|
||||
$revision->setNewRevision(TRUE);
|
||||
$revision->isDefaultRevision(FALSE);
|
||||
$revision->title->value = 'Test title second revision';
|
||||
$revision->moderation_state->target_id = 'draft';
|
||||
$revision->moderation_state->value = 'draft';
|
||||
$revision->save();
|
||||
|
||||
$view = Views::getView('test_content_moderation_latest_revision');
|
||||
|
@ -90,14 +107,14 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
|||
'type' => 'page',
|
||||
'title' => 'Test title first revision',
|
||||
]);
|
||||
$node->moderation_state->target_id = 'published';
|
||||
$node->moderation_state->value = 'published';
|
||||
$node->save();
|
||||
|
||||
$revision = clone $node;
|
||||
$revision->setNewRevision(TRUE);
|
||||
$revision->isDefaultRevision(FALSE);
|
||||
$revision->title->value = 'Test title second revision';
|
||||
$revision->moderation_state->target_id = 'draft';
|
||||
$revision->moderation_state->value = 'draft';
|
||||
$revision->save();
|
||||
|
||||
$view = Views::getView('test_content_moderation_revision_test');
|
||||
|
@ -105,15 +122,15 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
|||
|
||||
$expected_result = [
|
||||
[
|
||||
'revision_id' => $node->getRevisionId(),
|
||||
'vid' => $node->getRevisionId(),
|
||||
'moderation_state' => 'published',
|
||||
],
|
||||
[
|
||||
'revision_id' => $revision->getRevisionId(),
|
||||
'vid' => $revision->getRevisionId(),
|
||||
'moderation_state' => 'draft',
|
||||
],
|
||||
];
|
||||
$this->assertIdenticalResultset($view, $expected_result, ['revision_id' => 'revision_id', 'moderation_state' => 'moderation_state']);
|
||||
$this->assertIdenticalResultset($view, $expected_result, ['vid' => 'vid', 'moderation_state' => 'moderation_state']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,14 +141,14 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
|||
'type' => 'page',
|
||||
'title' => 'Test title first revision',
|
||||
]);
|
||||
$node->moderation_state->target_id = 'published';
|
||||
$node->moderation_state->value = 'published';
|
||||
$node->save();
|
||||
|
||||
$revision = clone $node;
|
||||
$revision->setNewRevision(TRUE);
|
||||
$revision->isDefaultRevision(FALSE);
|
||||
$revision->title->value = 'Test title second revision';
|
||||
$revision->moderation_state->target_id = 'draft';
|
||||
$revision->moderation_state->value = 'draft';
|
||||
$revision->save();
|
||||
|
||||
$view = Views::getView('test_content_moderation_base_table_test');
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
namespace Drupal\Tests\content_moderation\Unit;
|
||||
|
||||
use Drupal\content_moderation\Entity\Handler\ModerationHandler;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\ContentEntityType;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\content_moderation\ModerationInformation;
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\ModerationInformation
|
||||
|
@ -30,43 +31,42 @@ class ModerationInformationTest extends \PHPUnit_Framework_TestCase {
|
|||
/**
|
||||
* Returns a mock Entity Type Manager.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_bundle_storage
|
||||
* Entity bundle storage.
|
||||
*
|
||||
* @return EntityTypeManagerInterface
|
||||
* The mocked entity type manager.
|
||||
*/
|
||||
protected function getEntityTypeManager(EntityStorageInterface $entity_bundle_storage) {
|
||||
protected function getEntityTypeManager() {
|
||||
$entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
|
||||
$entity_type_manager->getStorage('entity_test_bundle')->willReturn($entity_bundle_storage);
|
||||
return $entity_type_manager->reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up content moderation and entity manager mocking.
|
||||
*
|
||||
* @param bool $status
|
||||
* TRUE if content_moderation should be enabled, FALSE if not.
|
||||
* @param string $bundle
|
||||
* The bundle ID.
|
||||
* @param string|null $workflow
|
||||
* The workflow ID. If nul no workflow information is added to the bundle.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
* The mocked entity type manager.
|
||||
*/
|
||||
public function setupModerationEntityManager($status) {
|
||||
$bundle = $this->prophesize(ConfigEntityInterface::class);
|
||||
$bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE)->willReturn($status);
|
||||
public function setupModerationBundleInfo($bundle, $workflow = NULL) {
|
||||
$bundle_info_array = [];
|
||||
if ($workflow) {
|
||||
$bundle_info_array['workflow'] = $workflow;
|
||||
}
|
||||
$bundle_info = $this->prophesize(EntityTypeBundleInfoInterface::class);
|
||||
$bundle_info->getBundleInfo("test_entity_type")->willReturn([$bundle => $bundle_info_array]);
|
||||
|
||||
$entity_storage = $this->prophesize(EntityStorageInterface::class);
|
||||
$entity_storage->load('test_bundle')->willReturn($bundle->reveal());
|
||||
|
||||
return $this->getEntityTypeManager($entity_storage->reveal());
|
||||
return $bundle_info->reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerBoolean
|
||||
* @dataProvider providerWorkflow
|
||||
* @covers ::isModeratedEntity
|
||||
*/
|
||||
public function testIsModeratedEntity($status) {
|
||||
$moderation_information = new ModerationInformation($this->setupModerationEntityManager($status), $this->getUser());
|
||||
public function testIsModeratedEntity($workflow, $expected) {
|
||||
$moderation_information = new ModerationInformation($this->getEntityTypeManager(), $this->setupModerationBundleInfo('test_bundle', $workflow));
|
||||
|
||||
$entity_type = new ContentEntityType([
|
||||
'id' => 'test_entity_type',
|
||||
|
@ -77,50 +77,55 @@ class ModerationInformationTest extends \PHPUnit_Framework_TestCase {
|
|||
$entity->getEntityType()->willReturn($entity_type);
|
||||
$entity->bundle()->willReturn('test_bundle');
|
||||
|
||||
$this->assertEquals($status, $moderation_information->isModeratedEntity($entity->reveal()));
|
||||
$this->assertEquals($expected, $moderation_information->isModeratedEntity($entity->reveal()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::isModeratedEntity
|
||||
* @dataProvider providerWorkflow
|
||||
* @covers ::getWorkflowForEntity
|
||||
*/
|
||||
public function testIsModeratedEntityForNonBundleEntityType() {
|
||||
$entity_type = new ContentEntityType([
|
||||
'id' => 'test_entity_type',
|
||||
]);
|
||||
public function testGetWorkflowForEntity($workflow) {
|
||||
$entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
|
||||
if ($workflow) {
|
||||
$workflow_entity = $this->prophesize(WorkflowInterface::class)->reveal();
|
||||
$workflow_storage = $this->prophesize(EntityStorageInterface::class);
|
||||
$workflow_storage->load('workflow')->willReturn($workflow_entity)->shouldBeCalled();
|
||||
$entity_type_manager->getStorage('workflow')->willReturn($workflow_storage->reveal());
|
||||
}
|
||||
else {
|
||||
$workflow_entity = NULL;
|
||||
}
|
||||
$moderation_information = new ModerationInformation($entity_type_manager->reveal(), $this->setupModerationBundleInfo('test_bundle', $workflow));
|
||||
$entity = $this->prophesize(ContentEntityInterface::class);
|
||||
$entity->getEntityType()->willReturn($entity_type);
|
||||
$entity->bundle()->willReturn('test_entity_type');
|
||||
$entity->getEntityTypeId()->willReturn('test_entity_type');
|
||||
$entity->bundle()->willReturn('test_bundle');
|
||||
|
||||
$entity_storage = $this->prophesize(EntityStorageInterface::class);
|
||||
$entity_type_manager = $this->getEntityTypeManager($entity_storage->reveal());
|
||||
$moderation_information = new ModerationInformation($entity_type_manager, $this->getUser());
|
||||
|
||||
$this->assertEquals(FALSE, $moderation_information->isModeratedEntity($entity->reveal()));
|
||||
$this->assertEquals($workflow_entity, $moderation_information->getWorkflowForEntity($entity->reveal()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerBoolean
|
||||
* @dataProvider providerWorkflow
|
||||
* @covers ::shouldModerateEntitiesOfBundle
|
||||
*/
|
||||
public function testShouldModerateEntities($status) {
|
||||
public function testShouldModerateEntities($workflow, $expected) {
|
||||
$entity_type = new ContentEntityType([
|
||||
'id' => 'test_entity_type',
|
||||
'bundle_entity_type' => 'entity_test_bundle',
|
||||
'handlers' => ['moderation' => ModerationHandler::class],
|
||||
]);
|
||||
|
||||
$moderation_information = new ModerationInformation($this->setupModerationEntityManager($status), $this->getUser());
|
||||
$moderation_information = new ModerationInformation($this->getEntityTypeManager(), $this->setupModerationBundleInfo('test_bundle', $workflow));
|
||||
|
||||
$this->assertEquals($status, $moderation_information->shouldModerateEntitiesOfBundle($entity_type, 'test_bundle'));
|
||||
$this->assertEquals($expected, $moderation_information->shouldModerateEntitiesOfBundle($entity_type, 'test_bundle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for several tests.
|
||||
*/
|
||||
public function providerBoolean() {
|
||||
public function providerWorkflow() {
|
||||
return [
|
||||
[FALSE],
|
||||
[TRUE],
|
||||
[NULL, FALSE],
|
||||
['workflow', TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
namespace Drupal\Tests\content_moderation\Unit;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\content_moderation\ModerationStateInterface;
|
||||
use Drupal\content_moderation\ModerationStateTransitionInterface;
|
||||
use Drupal\content_moderation\StateTransitionValidation;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Drupal\workflows\WorkflowTypeInterface;
|
||||
use Drupal\workflows\WorkflowTypeManager;
|
||||
use Prophecy\Argument;
|
||||
|
||||
/**
|
||||
|
@ -17,216 +18,6 @@ use Prophecy\Argument;
|
|||
*/
|
||||
class StateTransitionValidationTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* Builds a mock storage object for Transitions.
|
||||
*
|
||||
* @return EntityStorageInterface
|
||||
* The mocked storage object for Transitions.
|
||||
*/
|
||||
protected function setupTransitionStorage() {
|
||||
$entity_storage = $this->prophesize(EntityStorageInterface::class);
|
||||
|
||||
$list = $this->setupTransitionEntityList();
|
||||
$entity_storage->loadMultiple()->willReturn($list);
|
||||
$entity_storage->loadMultiple(Argument::type('array'))->will(function ($args) use ($list) {
|
||||
$keys = $args[0];
|
||||
if (empty($keys)) {
|
||||
return $list;
|
||||
}
|
||||
|
||||
$return = array_map(function($key) use ($list) {
|
||||
return $list[$key];
|
||||
}, $keys);
|
||||
|
||||
return $return;
|
||||
});
|
||||
return $entity_storage->reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an array of mocked Transition objects.
|
||||
*
|
||||
* @return ModerationStateTransitionInterface[]
|
||||
* An array of mocked Transition objects.
|
||||
*/
|
||||
protected function setupTransitionEntityList() {
|
||||
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
|
||||
$transition->id()->willReturn('draft__needs_review');
|
||||
$transition->getFromState()->willReturn('draft');
|
||||
$transition->getToState()->willReturn('needs_review');
|
||||
$list[$transition->reveal()->id()] = $transition->reveal();
|
||||
|
||||
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
|
||||
$transition->id()->willReturn('needs_review__staging');
|
||||
$transition->getFromState()->willReturn('needs_review');
|
||||
$transition->getToState()->willReturn('staging');
|
||||
$list[$transition->reveal()->id()] = $transition->reveal();
|
||||
|
||||
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
|
||||
$transition->id()->willReturn('staging__published');
|
||||
$transition->getFromState()->willReturn('staging');
|
||||
$transition->getToState()->willReturn('published');
|
||||
$list[$transition->reveal()->id()] = $transition->reveal();
|
||||
|
||||
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
|
||||
$transition->id()->willReturn('needs_review__draft');
|
||||
$transition->getFromState()->willReturn('needs_review');
|
||||
$transition->getToState()->willReturn('draft');
|
||||
$list[$transition->reveal()->id()] = $transition->reveal();
|
||||
|
||||
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
|
||||
$transition->id()->willReturn('draft__draft');
|
||||
$transition->getFromState()->willReturn('draft');
|
||||
$transition->getToState()->willReturn('draft');
|
||||
$list[$transition->reveal()->id()] = $transition->reveal();
|
||||
|
||||
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
|
||||
$transition->id()->willReturn('needs_review__needs_review');
|
||||
$transition->getFromState()->willReturn('needs_review');
|
||||
$transition->getToState()->willReturn('needs_review');
|
||||
$list[$transition->reveal()->id()] = $transition->reveal();
|
||||
|
||||
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
|
||||
$transition->id()->willReturn('published__published');
|
||||
$transition->getFromState()->willReturn('published');
|
||||
$transition->getToState()->willReturn('published');
|
||||
$list[$transition->reveal()->id()] = $transition->reveal();
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a mock storage object for States.
|
||||
*
|
||||
* @return EntityStorageInterface
|
||||
* The mocked storage object for States.
|
||||
*/
|
||||
protected function setupStateStorage() {
|
||||
$entity_storage = $this->prophesize(EntityStorageInterface::class);
|
||||
|
||||
$state = $this->prophesize(ModerationStateInterface::class);
|
||||
$state->id()->willReturn('draft');
|
||||
$state->label()->willReturn('Draft');
|
||||
$state->isPublishedState()->willReturn(FALSE);
|
||||
$state->isDefaultRevisionState()->willReturn(FALSE);
|
||||
$states['draft'] = $state->reveal();
|
||||
|
||||
$state = $this->prophesize(ModerationStateInterface::class);
|
||||
$state->id()->willReturn('needs_review');
|
||||
$state->label()->willReturn('Needs Review');
|
||||
$state->isPublishedState()->willReturn(FALSE);
|
||||
$state->isDefaultRevisionState()->willReturn(FALSE);
|
||||
$states['needs_review'] = $state->reveal();
|
||||
|
||||
$state = $this->prophesize(ModerationStateInterface::class);
|
||||
$state->id()->willReturn('staging');
|
||||
$state->label()->willReturn('Staging');
|
||||
$state->isPublishedState()->willReturn(FALSE);
|
||||
$state->isDefaultRevisionState()->willReturn(FALSE);
|
||||
$states['staging'] = $state->reveal();
|
||||
|
||||
$state = $this->prophesize(ModerationStateInterface::class);
|
||||
$state->id()->willReturn('published');
|
||||
$state->label()->willReturn('Published');
|
||||
$state->isPublishedState()->willReturn(TRUE);
|
||||
$state->isDefaultRevisionState()->willReturn(TRUE);
|
||||
$states['published'] = $state->reveal();
|
||||
|
||||
$state = $this->prophesize(ModerationStateInterface::class);
|
||||
$state->id()->willReturn('archived');
|
||||
$state->label()->willReturn('Archived');
|
||||
$state->isPublishedState()->willReturn(TRUE);
|
||||
$state->isDefaultRevisionState()->willReturn(TRUE);
|
||||
$states['archived'] = $state->reveal();
|
||||
|
||||
$entity_storage->loadMultiple()->willReturn($states);
|
||||
|
||||
foreach ($states as $id => $state) {
|
||||
$entity_storage->load($id)->willReturn($state);
|
||||
}
|
||||
|
||||
return $entity_storage->reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a mocked Entity Type Manager.
|
||||
*
|
||||
* @return EntityTypeManagerInterface
|
||||
* The mocked Entity Type Manager.
|
||||
*/
|
||||
protected function setupEntityTypeManager(EntityStorageInterface $storage) {
|
||||
$entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
|
||||
$entityTypeManager->getStorage('moderation_state')->willReturn($storage);
|
||||
$entityTypeManager->getStorage('moderation_state_transition')->willReturn($this->setupTransitionStorage());
|
||||
|
||||
return $entityTypeManager->reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a mocked query factory that does nothing.
|
||||
*
|
||||
* @return QueryFactory
|
||||
* The mocked query factory that does nothing.
|
||||
*/
|
||||
protected function setupQueryFactory() {
|
||||
$factory = $this->prophesize(QueryFactory::class);
|
||||
|
||||
return $factory->reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::isTransitionAllowed
|
||||
* @covers ::calculatePossibleTransitions
|
||||
*
|
||||
* @dataProvider providerIsTransitionAllowedWithValidTransition
|
||||
*/
|
||||
public function testIsTransitionAllowedWithValidTransition($from_id, $to_id) {
|
||||
$storage = $this->setupStateStorage();
|
||||
$state_transition_validation = new StateTransitionValidation($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
|
||||
$this->assertTrue($state_transition_validation->isTransitionAllowed($storage->load($from_id), $storage->load($to_id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for self::testIsTransitionAllowedWithValidTransition().
|
||||
*/
|
||||
public function providerIsTransitionAllowedWithValidTransition() {
|
||||
return [
|
||||
['draft', 'draft'],
|
||||
['draft', 'needs_review'],
|
||||
['needs_review', 'needs_review'],
|
||||
['needs_review', 'staging'],
|
||||
['staging', 'published'],
|
||||
['needs_review', 'draft'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::isTransitionAllowed
|
||||
* @covers ::calculatePossibleTransitions
|
||||
*
|
||||
* @dataProvider providerIsTransitionAllowedWithInValidTransition
|
||||
*/
|
||||
public function testIsTransitionAllowedWithInValidTransition($from_id, $to_id) {
|
||||
$storage = $this->setupStateStorage();
|
||||
$state_transition_validation = new StateTransitionValidation($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
|
||||
$this->assertFalse($state_transition_validation->isTransitionAllowed($storage->load($from_id), $storage->load($to_id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for self::testIsTransitionAllowedWithInValidTransition().
|
||||
*/
|
||||
public function providerIsTransitionAllowedWithInValidTransition() {
|
||||
return [
|
||||
['published', 'needs_review'],
|
||||
['published', 'staging'],
|
||||
['staging', 'needs_review'],
|
||||
['staging', 'staging'],
|
||||
['needs_review', 'published'],
|
||||
['published', 'archived'],
|
||||
['archived', 'published'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies user-aware transition validation.
|
||||
*
|
||||
|
@ -239,7 +30,7 @@ class StateTransitionValidationTest extends \PHPUnit_Framework_TestCase {
|
|||
* @param bool $allowed
|
||||
* Whether or not to grant a user this permission.
|
||||
* @param bool $result
|
||||
* Whether userMayTransition() is expected to return TRUE or FALSE.
|
||||
* Whether getValidTransitions() is expected to have the.
|
||||
*
|
||||
* @dataProvider userTransitionsProvider
|
||||
*/
|
||||
|
@ -250,10 +41,45 @@ class StateTransitionValidationTest extends \PHPUnit_Framework_TestCase {
|
|||
$user->hasPermission($permission)->willReturn($allowed);
|
||||
$user->hasPermission(Argument::type('string'))->willReturn(FALSE);
|
||||
|
||||
$storage = $this->setupStateStorage();
|
||||
$validator = new Validator($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
|
||||
$entity = $this->prophesize(ContentEntityInterface::class);
|
||||
$entity = $entity->reveal();
|
||||
$entity->moderation_state = new \stdClass();
|
||||
$entity->moderation_state->value = $from_id;
|
||||
|
||||
$this->assertEquals($result, $validator->userMayTransition($storage->load($from_id), $storage->load($to_id), $user->reveal()));
|
||||
$validator = new StateTransitionValidation($this->setUpModerationInformation($entity));
|
||||
$has_transition = FALSE;
|
||||
foreach ($validator->getValidTransitions($entity, $user->reveal()) as $transition) {
|
||||
if ($transition->to()->id() === $to_id) {
|
||||
$has_transition = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertSame($result, $has_transition);
|
||||
}
|
||||
|
||||
protected function setUpModerationInformation(ContentEntityInterface $entity) {
|
||||
// Create a container so that the plugin manager and workflow type can be
|
||||
// mocked.
|
||||
$container = new ContainerBuilder();
|
||||
$workflow_type = $this->prophesize(WorkflowTypeInterface::class);
|
||||
$workflow_type->decorateState(Argument::any())->willReturnArgument(0);
|
||||
$workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
|
||||
$workflow_manager = $this->prophesize(WorkflowTypeManager::class);
|
||||
$workflow_manager->createInstance('content_moderation', Argument::any())->willReturn($workflow_type->reveal());
|
||||
$container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$workflow = new Workflow(['id' => 'process', 'type' => 'content_moderation'], 'workflow');
|
||||
$workflow
|
||||
->addState('draft', 'draft')
|
||||
->addState('needs_review', 'needs_review')
|
||||
->addState('published', 'published')
|
||||
->addTransition('draft', 'draft', ['draft'], 'draft')
|
||||
->addTransition('review', 'review', ['draft'], 'needs_review')
|
||||
->addTransition('publish', 'publish', ['needs_review', 'published'], 'published');
|
||||
$moderation_info = $this->prophesize(ModerationInformationInterface::class);
|
||||
$moderation_info->getWorkflowForEntity($entity)->willReturn($workflow);
|
||||
return $moderation_info->reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,37 +87,15 @@ class StateTransitionValidationTest extends \PHPUnit_Framework_TestCase {
|
|||
*/
|
||||
public function userTransitionsProvider() {
|
||||
// The user has the right permission, so let it through.
|
||||
$ret[] = ['draft', 'draft', 'use draft__draft transition', TRUE, TRUE];
|
||||
$ret[] = ['draft', 'draft', 'use process transition draft', TRUE, TRUE];
|
||||
|
||||
// The user doesn't have the right permission, block it.
|
||||
$ret[] = ['draft', 'draft', 'use draft__draft transition', FALSE, FALSE];
|
||||
$ret[] = ['draft', 'draft', 'use process transition draft', FALSE, FALSE];
|
||||
|
||||
// The user has some other permission that doesn't matter.
|
||||
$ret[] = ['draft', 'draft', 'use draft__needs_review transition', TRUE, FALSE];
|
||||
|
||||
// The user has permission, but the transition isn't allowed anyway.
|
||||
$ret[] = ['published', 'needs_review', 'use published__needs_review transition', TRUE, FALSE];
|
||||
$ret[] = ['draft', 'draft', 'use process transition review', TRUE, FALSE];
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Testable subclass for selected tests.
|
||||
*
|
||||
* EntityQuery is beyond untestable, so we have to subclass and override the
|
||||
* method that uses it.
|
||||
*/
|
||||
class Validator extends StateTransitionValidation {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getTransitionFromStates(ModerationStateInterface $from, ModerationStateInterface $to) {
|
||||
if ($from->id() === 'draft' && $to->id() === 'draft') {
|
||||
return $this->transitionStorage()->loadMultiple(['draft__draft'])[0];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Reference in a new issue