Unverified Commit f50214d7 authored by larowlan's avatar larowlan Committed by larowlan

Issue #3150430 by snehalgaikwad, larowlan: Fix coding standards

parent b7199c7d
......@@ -3,29 +3,38 @@ Workbench Moderation
About this module
-----------------
Workbench Moderation (WBM) provides basic moderation support for revisionable content entities. That is, it allows
site administrators to define States that content can be in, Transitions between those States, and rules around who
is able to Transition content from one State to another, when.
Workbench Moderation (WBM) provides basic moderation support for revisionable
content entities. That is, it allows site administrators to define States that
content can be in, Transitions between those States, and rules around who is
able to Transition content from one State to another, when.
In concept, any revision-aware content entity is supportable. In core, that includes Nodes and Block Content. However,
there may be a small amount of work needed to support additional content entities due to inconsistencies in the
Entity API.core. To add custom WBM integration for your entity type, annotate your entity to specify a moderation
handler class that extends \Drupal\workbench_moderation\Entity\Handler\ModerationHandler.
In concept, any revision-aware content entity is supportable. In core, that
includes Nodes and Block Content. However, there may be a small amount of work
needed to support additional content entities due to inconsistencies in the
Entity API.core. To add custom WBM integration for your entity type, annotate
your entity to specify a moderation handler class that extends
\Drupal\workbench_moderation\Entity\Handler\ModerationHandler.
Installation
------------
WBM has no special installation instructions. The default configuration that ships with the module should cover most
typical use cases, with no need to configure additional States or Transitions. You are welcome to do so, however, and
to edit or remote any pre-defined State or Transition.
WBM has no special installation instructions. The default configuration that
ships with the module should cover most typical use cases, with no need to
configure additional States or Transitions. You are welcome to do so, however,
and to edit or remote any pre-defined State or Transition.
Note that when an entity is under moderation by this module, explicitly setting its published state (on nodes) or its
"make default revision" value (on all entities) will have no effect, because WBM will always overwrite those values based
on your configured State rules. That is most notable with regards to core's Publish and Unpublish actions for Bulk
Operations on nodes, as seen on /admin/content. Those actions will simply have no effect on a moderated node.
Note that when an entity is under moderation by this module, explicitly setting
its published state (on nodes) or its "make default revision" value (on all
entities) will have no effect, because WBM will always overwrite those values
based on your configured State rules. That is most notable with regards to
core's Publish and Unpublish actions for Bulk Operations on nodes, as seen on
/admin/content. Those actions will simply have no effect on a moderated node.
To avoid confusion, removing the actions from that view (or any similar views) is recommended.
To avoid confusion, removing the actions from that view (or any similar views)
is recommended.
1) Visit /admin/structure/views/view/content .
2) Configure: 'Content: Node operations bulk form' > change the Available actions to 'Only selected actions' > Deselect 'publish content' and 'unpublish content'.
2) Configure: 'Content: Node operations bulk form' > change the Available
actions to 'Only selected actions' > Deselect 'publish content'
and 'unpublish content'.
3) Save view.
......@@ -3,11 +3,11 @@ ul.entity-moderation-form {
display: -webkit-flex; /* Safari */
display: flex;
-webkit-flex-wrap: wrap; /* Safari */
flex-wrap: wrap;
flex-wrap: wrap;
-webkit-justify-content: space-around; /* Safari */
justify-content: space-around;
justify-content: space-around;
-webkit-align-items: flex-end; /* Safari */
align-items: flex-end;
align-items: flex-end;
border-bottom: 1px solid gray;
}
......
......@@ -3,16 +3,20 @@
namespace Drupal\workbench_moderation\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\workbench_moderation\ModerationInformationInterface;
use Symfony\Component\Routing\Route;
/**
* Define class for revision checks.
*/
class LatestRevisionCheck implements AccessInterface {
/**
* Moderation information.
*
* @var \Drupal\workbench_moderation\ModerationInformationInterface
*/
protected $moderationInfo;
......@@ -33,15 +37,15 @@ class LatestRevisionCheck implements AccessInterface {
* This checker assumes the presence of an '_entity_access' requirement key
* in the same form as used by EntityAccessCheck.
*
* @see EntityAccessCheck.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The parametrized route
* The parametrized route.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*
* @see EntityAccessCheck
*/
public function access(Route $route, RouteMatchInterface $route_match) {
......@@ -59,9 +63,9 @@ class LatestRevisionCheck implements AccessInterface {
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The parametrized route
* The parametrized route.
*
* @return ContentEntityInterface
* @return \Drupal\Core\Entity\ContentEntityInterface
* returns the Entity in question.
*
* @throws \Exception
......@@ -79,4 +83,5 @@ class LatestRevisionCheck implements AccessInterface {
}
throw new \Exception(sprintf('%s is not a valid entity route. The LatestRevisionCheck access checker may only be used with a route that has a single entity parameter.', $route_match->getRouteName()));
}
}
<?php
/**
* @file
* Contains Drupal\workbench_moderation\Entity\Handler\BlockContentCustomizations.
*/
namespace Drupal\workbench_moderation\Entity\Handler;
use Drupal\Core\Form\FormStateInterface;
/**
* Customizations for block content entities.
*/
......
......@@ -21,7 +21,7 @@ class ModerationHandler implements ModerationHandlerInterface, EntityHandlerInte
use StringTranslationTrait;
/**
* @inheritDoc
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static();
......@@ -51,13 +51,12 @@ class ModerationHandler implements ModerationHandlerInterface, EntityHandlerInte
* {@inheritdoc}
*/
public function onBundleModerationConfigurationFormSubmit(ConfigEntityInterface $bundle) {
// The Revisions portion of Entity API is not uniformly applied or consistent.
// Until that's fixed in core, 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 NodeType
// for an example.
// The Revisions portion of Entity API is not uniformly applied or
// consistent. Until that's fixed in core, 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 NodeType for an example.
if (method_exists($bundle, 'setNewRevision')) {
$bundle->setNewRevision(TRUE);
}
......@@ -70,25 +69,19 @@ class ModerationHandler implements ModerationHandlerInterface, EntityHandlerInte
$bundle->set('revision', TRUE);
}
$bundle->save();
return;
}
/**
* {@inheritdoc}
*/
public function enforceRevisionsEntityFormAlter(array &$form, FormStateInterface $form_state, $form_id) {
return;
}
/**
* {@inheritdoc}
*/
public function enforceRevisionsBundleFormAlter(array &$form, FormStateInterface $form_state, $form_id) {
return;
}
}
<?php
/**
* @file
* Contains Drupal\workbench_moderation\Entity\Handler\EntityCustomizationInterface.
*/
namespace Drupal\workbench_moderation\Entity\Handler;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Form\FormStateInterface;
......@@ -21,6 +16,8 @@ use Drupal\Core\Form\FormStateInterface;
interface ModerationHandlerInterface {
/**
* Work to be done before saving en entity.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity to modify.
* @param bool $default_revision
......@@ -40,9 +37,8 @@ interface ModerationHandlerInterface {
* 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\ConfigEntityTypeInterface $bundle
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle
* The bundle definition that is being saved.
* @return mixed
*/
public function onBundleModerationConfigurationFormSubmit(ConfigEntityInterface $bundle);
......
<?php
/**
* @file
* Contains Drupal\workbench_moderation\Entity\Handler\NodeCustomizations.
*/
namespace Drupal\workbench_moderation\Entity\Handler;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\Node;
/**
* Customizations for node entities.
......
......@@ -79,9 +79,9 @@ class ModerationStateTransition extends ConfigEntityBase implements ModerationSt
protected $weight;
/**
* Moderation state config prefix
* Moderation state config prefix.
*
* @var string.
* @var string
*/
protected $moderationStateConfigPrefix;
......
......@@ -20,11 +20,15 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class EntityOperations {
/**
* Moderation interface.
*
* @var \Drupal\workbench_moderation\ModerationInformationInterface
*/
protected $moderationInfo;
/**
* An entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
......@@ -37,11 +41,15 @@ class EntityOperations {
protected $eventDispatcher;
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* Revision tracker.
*
* @var \Drupal\workbench_moderation\RevisionTrackerInterface
*/
protected $tracker;
......@@ -81,19 +89,18 @@ class EntityOperations {
/**
* Hook bridge.
*
* @see hook_entity_storage_load().
*
* @param EntityInterface[] $entities
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entity objects that have just been loaded.
* @param string $entity_type_id
* The type of entity being loaded, such as "node" or "user".
*
* @see hook_entity_storage_load()
*/
public function entityStorageLoad(array $entities, $entity_type_id) {
// Ensure that all moderatable entities always have a moderation_state field
// with data, in all translations. That avoids us needing to have a thousand
// NULL checks elsewhere in the code.
// Quickly exclude any non-moderatable entities.
$to_check = array_filter($entities, [$this->moderationInfo, 'isModeratableEntity']);
if (!$to_check) {
......@@ -165,8 +172,8 @@ class EntityOperations {
$latest_revision = $this->moderationInfo->getLatestRevision($entity->getEntityTypeId(), $entity->id());
$state_before = !empty($latest_revision) ? $latest_revision->moderation_state->target_id : NULL;
// @todo: Revert to this simpler version when https://www.drupal.org/node/2700747 is fixed.
// $state_before = isset($entity->original) ? $entity->original->moderation_state->target_id : NULL;
// $state_before = isset($entity->original) ?
// $entity->original->moderation_state->target_id : NULL;
$state_after = $entity->moderation_state->target_id;
// Allow other modules to respond to the transition. Note that this
......@@ -181,17 +188,17 @@ class EntityOperations {
/**
* Hook bridge.
*
* @see hook_entity_insert().
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that was just saved.
*
* @see hook_entity_insert()
*/
public function entityInsert(EntityInterface $entity) {
if (!$this->moderationInfo->isModeratableEntity($entity)) {
return;
}
/** ContentEntityInterface $entity */
/* ContentEntityInterface $entity */
// Update our own record keeping.
$this->tracker->setLatestRevision($entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $entity->getRevisionId());
......@@ -200,17 +207,17 @@ class EntityOperations {
/**
* Hook bridge.
*
* @see hook_entity_update().
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that was just saved.
*
* @see hook_entity_update()
*/
public function entityUpdate(EntityInterface $entity) {
if (!$this->moderationInfo->isModeratableEntity($entity)) {
return;
}
/** ContentEntityInterface $entity */
/* ContentEntityInterface $entity */
// Update our own record keeping.
$this->tracker->setLatestRevision($entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $entity->getRevisionId());
......
......@@ -3,7 +3,6 @@
namespace Drupal\workbench_moderation;
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
......@@ -38,12 +37,15 @@ class EntityTypeInfo {
protected $moderationInfo;
/**
* The entitytype manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* A keyed array of custom moderation handlers for given entity types.
*
* Any entity not specified will use a common default.
*
* @var array
......@@ -74,10 +76,10 @@ class EntityTypeInfo {
*
* This is an alter hook bridge.
*
* @param EntityTypeInterface[] $entity_types
* @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types
* The master entity type list to alter.
*
* @see hook_entity_type_alter().
* @see hook_entity_type_alter()
*/
public function entityTypeAlter(array &$entity_types) {
foreach ($this->moderationInfo->selectRevisionableEntityTypes($entity_types) as $type_name => $type) {
......@@ -120,7 +122,7 @@ class EntityTypeInfo {
}
/**
* Modifies an entity type definition to include moderation configuration support.
* Modifies entity type definition to include configuration support.
*
* That "configuration support" includes a configuration form, a hypermedia
* link, and a route provider to tie it all together. There's also a
......@@ -161,7 +163,7 @@ class EntityTypeInfo {
* @return array
* An array of operation definitions.
*
* @see hook_entity_operation().
* @see hook_entity_operation()
*/
public function entityOperation(EntityInterface $entity) {
$operations = [];
......@@ -200,16 +202,16 @@ class EntityTypeInfo {
* - edit: (optional) String containing markup (normally a link) used as the
* element's 'edit' operation in the administration interface. Only for
* 'form' context.
* - delete: (optional) String containing markup (normally a link) used as the
* element's 'delete' operation in the administration interface. Only for
* 'form' context.
* - delete: (optional) String containing markup (normally a link) used as
* the element's 'delete' operation in the administration interface. Only
* for 'form' context.
*/
public function entityExtraFieldInfo() {
$return = [];
foreach ($this->getModeratedBundles() as $bundle) {
$return[$bundle['entity']][$bundle['bundle']]['display']['workbench_moderation_control'] = [
'label' => $this->t('Moderation control'),
'description' => $this->t('Status listing and form for the entitiy\'s moderation state.'),
'description' => $this->t("Status listing and form for the entitiy's moderation state."),
'weight' => -20,
'visible' => TRUE,
];
......@@ -226,12 +228,13 @@ class EntityTypeInfo {
*
* @return \Generator
* A generator, yielding a 2 element associative array:
* - entity: The machine name of an entity, such as "node" or "block_content".
* - entity: The machine name of an entity,
* such as "node" or "block_content".
* - bundle: The machine name of a bundle, such as "page" or "article".
*/
protected function getModeratedBundles() {
$revisionable_types = $this->moderationInfo->selectRevisionableEntityTypes($this->entityTypeManager->getDefinitions());
/** @var ConfigEntityTypeInterface $type */
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $type */
foreach ($revisionable_types as $type_name => $type) {
$result = $this->entityTypeManager
->getStorage($type_name)
......@@ -300,7 +303,7 @@ class EntityTypeInfo {
*
* @see hook_entity_bundle_field_info_alter();
*/
public function entityBundleFieldInfoAlter(&$fields, EntityTypeInterface $entity_type, $bundle) {
public function entityBundleFieldInfoAlter(array &$fields, EntityTypeInterface $entity_type, $bundle) {
if ($this->moderationInfo->isModeratableBundle($entity_type, $bundle) && !empty($fields['moderation_state'])) {
$fields['moderation_state']->addConstraint('ModerationState', []);
}
......@@ -325,13 +328,13 @@ class EntityTypeInfo {
$this->entityTypeManager->getHandler($bundle->getEntityType()->getBundleOf(), 'moderation')->enforceRevisionsBundleFormAlter($form, $form_state, $form_id);
}
else if ($this->moderationInfo->isModeratedEntityForm($form_state->getFormObject())) {
elseif ($this->moderationInfo->isModeratedEntityForm($form_state->getFormObject())) {
/* @var ContentEntityInterface $entity */
$entity = $form_state->getFormObject()->getEntity();
$this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->enforceRevisionsEntityFormAlter($form, $form_state, $form_id);
// Submit handler to redirect to the
// Submit handler to redirect to the.
$form['actions']['submit']['#submit'][] = '\Drupal\workbench_moderation\EntityTypeInfo::bundleFormRedirect';
}
}
......@@ -343,7 +346,9 @@ class EntityTypeInfo {
* the next page.
*
* @param array $form
* Form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*/
public static function bundleFormRedirect(array &$form, FormStateInterface $form_state) {
/* @var ContentEntityInterface $entity */
......@@ -355,4 +360,5 @@ class EntityTypeInfo {
$form_state->setRedirect("entity.$entity_type_id.latest_version", [$entity_type_id => $entity->id()]);
}
}
}
......@@ -2,6 +2,9 @@
namespace Drupal\workbench_moderation\Event;
/**
* Defines a class for moderation events.
*/
final class WorkbenchModerationEvents {
/**
......
......@@ -6,6 +6,8 @@ use Drupal\Core\Entity\ContentEntityInterface;
use Symfony\Component\EventDispatcher\Event;
/**
* Defines a class for transition events.
*
* @see \Drupal\workbench_moderation\ModerationStateEvents
*/
class WorkbenchModerationTransitionEvent extends Event {
......@@ -18,11 +20,15 @@ class WorkbenchModerationTransitionEvent extends Event {
protected $entity;
/**
* The state before the transition.
*
* @var string
*/
protected $stateBefore;
/**
* The state after the transition.
*
* @var string
*/
protected $stateAfter;
......@@ -47,20 +53,27 @@ class WorkbenchModerationTransitionEvent extends Event {
* Returns the changed entity.
*
* @return \Drupal\Core\Entity\ContentEntityInterface
* returns entity.
*/
public function getEntity() {
return $this->entity;
}
/**
* Returns state before the transition.
*
* @return string
* Return state before.
*/
public function getStateBefore() {
return $this->stateBefore;
}
/**
* Returns state after the transition.
*
* @return string
* Return state after.
*/
public function getStateAfter() {
return $this->stateAfter;
......
......@@ -2,7 +2,6 @@
namespace Drupal\workbench_moderation\Form;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Entity\EntityForm;
......@@ -17,19 +16,21 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class BundleModerationConfigurationForm extends EntityForm {
/**
* An entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @inheritDoc
* {@inheritdoc}
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* @inheritDoc
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity_type.manager'));
......@@ -73,15 +74,15 @@ class BundleModerationConfigurationForm extends EntityForm {
}
$states = $this->entityTypeManager->getStorage('moderation_state')->loadMultiple();
$label = function(ModerationState $state) {
$label = function (ModerationState $state) {
return $state->label();
};
$options_published = array_map($label, array_filter($states, function(ModerationState $state) {
$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) {
$options_unpublished = array_map($label, array_filter($states, function (ModerationState $state) {
return !$state->isPublishedState();
}));
......@@ -145,12 +146,16 @@ class BundleModerationConfigurationForm extends EntityForm {
* It was in the form_alter version but we should see if we can just fold
* it into the method above.
*
* @param $entity_type
* @param string $entity_type
* Entity type.
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle
* Entity bundle.
* @param array $form
* Form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*/
public function formBuilderCallback($entity_type, ConfigEntityInterface $bundle, &$form, FormStateInterface $form_state) {
public function formBuilderCallback($entity_type, ConfigEntityInterface $bundle, array &$form, FormStateInterface $form_state) {
// @todo write a test for this.
$bundle->setThirdPartySetting('workbench_moderation', 'enabled', $form_state->getValue('enable_moderation_state'));
$bundle->setThirdPartySetting('workbench_moderation', 'allowed_moderation_states', array_keys(array_filter($form_state->getValue('allowed_moderation_states_published') + $form_state->getValue('allowed_moderation_states_unpublished'))));
......@@ -177,15 +182,16 @@ class BundleModerationConfigurationForm extends EntityForm {
// 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 ConfigEntityTypeInterface $bundle */
if ($form_state->getValue('enable_moderation_state')) {
/* @var $bundle ConfigEntityTypeInterface */
$bundle = $form_state->getFormObject()->getEntity();
$this->entityTypeManager->getHandler($bundle->getEntityType()->getBundleOf(), 'moderation')->onBundleModerationConfigurationFormSubmit($bundle);
}
parent::submitForm( $form, $form_state);
parent::submitForm($form, $form_state);