Update core 8.3.0
This commit is contained in:
parent
da7a7918f8
commit
cd7a898e66
6144 changed files with 132297 additions and 87747 deletions
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,221 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Tests;
|
||||
|
||||
/**
|
||||
* Test content_moderation functionality with localization and translation.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ModerationLocaleTest extends ModerationStateTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'content_moderation',
|
||||
'locale',
|
||||
'content_translation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests article translations can be moderated separately.
|
||||
*/
|
||||
public function testTranslateModeratedContent() {
|
||||
$this->drupalLogin($this->rootUser);
|
||||
|
||||
// Enable moderation on Article node type.
|
||||
$this->createContentTypeFromUi(
|
||||
'Article',
|
||||
'article',
|
||||
TRUE,
|
||||
['draft', 'published', 'archived'],
|
||||
'draft'
|
||||
);
|
||||
|
||||
// Add French language.
|
||||
$edit = [
|
||||
'predefined_langcode' => 'fr',
|
||||
];
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
// Enable content translation on articles.
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$edit = [
|
||||
'entity_types[node]' => TRUE,
|
||||
'settings[node][article][translatable]' => TRUE,
|
||||
'settings[node][article][settings][language][language_alterable]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
|
||||
// Adding languages requires a container rebuild in the test running
|
||||
// environment so that multilingual services are used.
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Create a published article in English.
|
||||
$edit = [
|
||||
'title[0][value]' => 'Published English node',
|
||||
'langcode[0][value]' => 'en',
|
||||
];
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save and Publish'));
|
||||
$this->assertText(t('Article Published English node has been created.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('Published English node');
|
||||
|
||||
// Add a French translation.
|
||||
$this->drupalGet('node/' . $english_node->id() . '/translations');
|
||||
$this->clickLink(t('Add'));
|
||||
$edit = [
|
||||
'title[0][value]' => 'French node Draft',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and Create New Draft (this translation)'));
|
||||
// Here the error has occurred "The website encountered an unexpected error.
|
||||
// Please try again later."
|
||||
// If the translation has got lost.
|
||||
$this->assertText(t('Article French node Draft has been updated.'));
|
||||
|
||||
// Create an article in English.
|
||||
$edit = [
|
||||
'title[0][value]' => 'English node',
|
||||
'langcode[0][value]' => 'en',
|
||||
];
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save and Create New Draft'));
|
||||
$this->assertText(t('Article English node has been created.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('English node');
|
||||
|
||||
// Add a French translation.
|
||||
$this->drupalGet('node/' . $english_node->id() . '/translations');
|
||||
$this->clickLink(t('Add'));
|
||||
$edit = [
|
||||
'title[0][value]' => 'French node',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and Create New Draft (this translation)'));
|
||||
$this->assertText(t('Article French node has been updated.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('English node', TRUE);
|
||||
|
||||
// Publish the English article and check that the translation stays
|
||||
// unpublished.
|
||||
$this->drupalPostForm('node/' . $english_node->id() . '/edit', [], t('Save and Publish (this translation)'));
|
||||
$this->assertText(t('Article English node has been updated.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('English node', TRUE);
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertEqual('French node', $french_node->label());
|
||||
|
||||
$this->assertEqual($english_node->moderation_state->target_id, 'published');
|
||||
$this->assertTrue($english_node->isPublished());
|
||||
$this->assertEqual($french_node->moderation_state->target_id, 'draft');
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
|
||||
// Create another article with its translation. This time we will publish
|
||||
// the translation first.
|
||||
$edit = [
|
||||
'title[0][value]' => 'Another node',
|
||||
];
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save and Create New Draft'));
|
||||
$this->assertText(t('Article Another node has been created.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('Another node');
|
||||
|
||||
// Add a French translation.
|
||||
$this->drupalGet('node/' . $english_node->id() . '/translations');
|
||||
$this->clickLink(t('Add'));
|
||||
$edit = [
|
||||
'title[0][value]' => 'Translated node',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and Create New Draft (this translation)'));
|
||||
$this->assertText(t('Article Translated node has been updated.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
|
||||
|
||||
// Publish the translation and check that the source language version stays
|
||||
// unpublished.
|
||||
$this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', [], t('Save and Publish (this translation)'));
|
||||
$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->assertTrue($french_node->isPublished());
|
||||
$this->assertEqual($english_node->moderation_state->target_id, 'draft');
|
||||
$this->assertFalse($english_node->isPublished());
|
||||
|
||||
// Now check that we can create a new draft of the translation.
|
||||
$edit = [
|
||||
'title[0][value]' => 'New draft of translated node',
|
||||
];
|
||||
$this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', $edit, t('Save and Create New Draft (this translation)'));
|
||||
$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->assertTrue($french_node->isPublished());
|
||||
$this->assertEqual($french_node->getTitle(), 'Translated node', 'The default revision of the published translation remains the same.');
|
||||
|
||||
// Publish the draft.
|
||||
$edit = [
|
||||
'new_state' => 'published',
|
||||
];
|
||||
$this->drupalPostForm('fr/node/' . $english_node->id() . '/latest', $edit, t('Apply'));
|
||||
$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->assertTrue($french_node->isPublished());
|
||||
$this->assertEqual($french_node->getTitle(), 'New draft of translated node', 'The draft has replaced the published revision.');
|
||||
|
||||
// Publish the English article before testing the archive transition.
|
||||
$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');
|
||||
|
||||
// Archive the node and its translation.
|
||||
$this->drupalPostForm('node/' . $english_node->id() . '/edit', [], t('Save and Archive (this translation)'));
|
||||
$this->assertText(t('Article Another node has been updated.'));
|
||||
$this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', [], t('Save and Archive (this translation)'));
|
||||
$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->assertFalse($english_node->isPublished());
|
||||
$this->assertEqual($french_node->moderation_state->target_id, 'archived');
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
|
||||
// Create another article with its translation. This time publishing english
|
||||
// after creating a forward french revision.
|
||||
$edit = [
|
||||
'title[0][value]' => 'An english node',
|
||||
];
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save and Create New Draft'));
|
||||
$this->assertText(t('Article An english node has been created.'));
|
||||
$english_node = $this->drupalGetNodeByTitle('An english node');
|
||||
$this->assertFalse($english_node->isPublished());
|
||||
|
||||
// Add a French translation.
|
||||
$this->drupalGet('node/' . $english_node->id() . '/translations');
|
||||
$this->clickLink(t('Add'));
|
||||
$edit = [
|
||||
'title[0][value]' => 'A french node',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and Publish (this translation)'));
|
||||
$english_node = $this->drupalGetNodeByTitle('An english node', TRUE);
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$this->assertFalse($english_node->isPublished());
|
||||
|
||||
// Create a forward revision
|
||||
$this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', [], t('Save and Create New Draft (this translation)'));
|
||||
$english_node = $this->drupalGetNodeByTitle('An english node', TRUE);
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$this->assertFalse($english_node->isPublished());
|
||||
|
||||
// Publish the english node and the default french node not the latest
|
||||
// french node should be used.
|
||||
$this->drupalPostForm('/node/' . $english_node->id() . '/edit', [], t('Save and Publish (this translation)'));
|
||||
$english_node = $this->drupalGetNodeByTitle('An english node', TRUE);
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$this->assertTrue($english_node->isPublished());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Tests;
|
||||
|
||||
/**
|
||||
* Tests permission access control around nodes.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class NodeAccessTest extends ModerationStateTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->createContentTypeFromUi(
|
||||
'Moderated content',
|
||||
'moderated_content',
|
||||
TRUE,
|
||||
['draft', 'published'],
|
||||
'draft'
|
||||
);
|
||||
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a non-admin user can still access the appropriate pages.
|
||||
*/
|
||||
public function testPageAccess() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Create a node to test with.
|
||||
$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) {
|
||||
$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';
|
||||
|
||||
// Publish the node.
|
||||
$this->drupalPostForm($edit_path, [], t('Save and Publish'));
|
||||
|
||||
// Ensure access works correctly for anonymous users.
|
||||
$this->drupalLogout();
|
||||
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertResponse(403);
|
||||
|
||||
$this->drupalGet($latest_path);
|
||||
$this->assertResponse(403);
|
||||
$this->drupalGet($view_path);
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Create a forward revision for the 'Latest revision' tab.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPostForm($edit_path, [
|
||||
'title[0][value]' => 'moderated content revised',
|
||||
], t('Save and Create New Draft'));
|
||||
|
||||
// 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',
|
||||
'view latest version',
|
||||
'view any unpublished content',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertResponse(403);
|
||||
|
||||
$this->drupalGet($latest_path);
|
||||
$this->assertResponse(200);
|
||||
$this->drupalGet($view_path);
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Now make another user, who should not be able to see forward revisions.
|
||||
$user = $this->createUser([
|
||||
'use published_draft transition',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertResponse(403);
|
||||
|
||||
$this->drupalGet($latest_path);
|
||||
$this->assertResponse(403);
|
||||
$this->drupalGet($view_path);
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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'],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in a new issue