Commit cbd3299e authored by alexpott's avatar alexpott

Issue #2916740 by chr.fritsch, xjm, Manuel Garcia, tstoeckler, alexpott,...

Issue #2916740 by chr.fritsch, xjm, Manuel Garcia, tstoeckler, alexpott, seanB, Sam152, amateescu, tim.plunkett, dawehner, larowlan, Berdir: Add generic entity actions
parent c9ca954c
......@@ -374,3 +374,8 @@ block.settings.field_block:*:*:
mapping:
formatter:
type: field_formatter
# Schema for entity actions.
action.configuration.entity:*:*:
type: action_configuration_default
label: 'Entity action'
<?php
namespace Drupal\Core\Action\Plugin\Action\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a base action for each entity type with specific interfaces.
*/
abstract class EntityActionDeriverBase extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new EntityActionDeriverBase object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static($container->get('entity_type.manager'));
}
/**
* Indicates whether the deriver can be used for the provided entity type.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return bool
* TRUE if the entity type can be used, FALSE otherwise.
*/
abstract protected function isApplicable(EntityTypeInterface $entity_type);
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
if (empty($this->derivatives)) {
$definitions = [];
foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
$definition = $base_plugin_definition;
$definition['type'] = $entity_type_id;
$definition['label'] = sprintf('%s %s', $base_plugin_definition['action_label'], $entity_type->getSingularLabel());
$definitions[$entity_type_id] = $definition;
}
$this->derivatives = $definitions;
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
/**
* Gets a list of applicable entity types.
*
* The list consists of all entity types which match the conditions for the
* given deriver.
* For example, if the action applies to entities that are publishable,
* this method will find all entity types that are publishable.
*
* @return \Drupal\Core\Entity\EntityTypeInterface[]
* The applicable entity types, keyed by entity type ID.
*/
protected function getApplicableEntityTypes() {
$entity_types = $this->entityTypeManager->getDefinitions();
$entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
return $this->isApplicable($entity_type);
});
return $entity_types;
}
}
<?php
namespace Drupal\Core\Action\Plugin\Action\Derivative;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Provides an action deriver that finds entity types of EntityChangedInterface.
*
* @see \Drupal\Core\Action\Plugin\Action\SaveAction
*/
class EntityChangedActionDeriver extends EntityActionDeriverBase {
/**
* {@inheritdoc}
*/
protected function isApplicable(EntityTypeInterface $entity_type) {
return $entity_type->entityClassImplements(EntityChangedInterface::class);
}
}
<?php
namespace Drupal\Core\Action\Plugin\Action\Derivative;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Provides an action deriver that finds publishable entity types.
*
* @see \Drupal\Core\Action\Plugin\Action\PublishAction
* @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
*/
class EntityPublishedActionDeriver extends EntityActionDeriverBase {
/**
* {@inheritdoc}
*/
protected function isApplicable(EntityTypeInterface $entity_type) {
return $entity_type->entityClassImplements(EntityPublishedInterface::class);
}
}
<?php
namespace Drupal\Core\Action\Plugin\Action;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for entity-based actions.
*/
abstract class EntityActionBase extends ActionBase implements DependentPluginInterface, ContainerFactoryPluginInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a EntityActionBase object.
*
* @param mixed[] $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
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 calculateDependencies() {
$module_name = $this->entityTypeManager
->getDefinition($this->getPluginDefinition()['type'])
->getProvider();
return ['module' => [$module_name]];
}
}
<?php
namespace Drupal\Core\Action\Plugin\Action;
use Drupal\Core\Session\AccountInterface;
/**
* Publishes an entity.
*
* @Action(
* id = "entity:publish_action",
* action_label = @Translation("Publish"),
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver",
* )
*/
class PublishAction extends EntityActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->setPublished()->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
$key = $object->getEntityType()->getKey('published');
/** @var \Drupal\Core\Entity\EntityInterface $object */
$result = $object->access('update', $account, TRUE)
->andIf($object->$key->access('edit', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}
<?php
namespace Drupal\Core\Action\Plugin\Action;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an action that can save any entity.
*
* @Action(
* id = "entity:save_action",
* action_label = @Translation("Save"),
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver",
* )
*/
class SaveAction extends EntityActionBase {
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* Constructs a SaveAction object.
*
* @param mixed[] $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
$this->time = $time;
}
/**
* {@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'),
$container->get('datetime.time')
);
}
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->setChangedTime($this->time->getRequestTime())->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
// It's not necessary to check the changed field access here, because
// Drupal\Core\Field\ChangedFieldItemList would anyway return 'not allowed'.
// Also changing the changed field value is only a workaround to trigger an
// entity resave. Without a field change, this would not be possible.
/** @var \Drupal\Core\Entity\EntityInterface $object */
return $object->access('update', $account, $return_as_object);
}
}
<?php
namespace Drupal\Core\Action\Plugin\Action;
use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes an entity.
*
* @Action(
* id = "entity:unpublish_action",
* action_label = @Translation("Unpublish"),
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver",
* )
*/
class UnpublishAction extends EntityActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->setUnpublished()->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
$key = $object->getEntityType()->getKey('published');
/** @var \Drupal\Core\Entity\EntityInterface $object */
$result = $object->access('update', $account, TRUE)
->andIf($object->$key->access('edit', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}
......@@ -23,6 +23,12 @@ process:
imagecache_flush_action: 0
imagecache_generate_all_action: 0
imagecache_generate_action: 0
comment_publish_action: entity:publish_action:comment
comment_unpublish_action: entity:unpublish_action:comment
comment_save_action: entity:save_action:comment
node_publish_action: entity:publish_action:node
node_unpublish_action: entity:unpublish_action:node
node_save_action: entity:save_action:node
bypass: true
-
plugin: skip_on_empty
......
......@@ -20,6 +20,12 @@ process:
system_send_email_action: action_send_email_action
system_message_action: action_message_action
system_block_ip_action: 0
comment_publish_action: entity:publish_action:comment
comment_unpublish_action: entity:unpublish_action:comment
comment_save_action: entity:save_action:comment
node_publish_action: entity:publish_action:node
node_unpublish_action: entity:unpublish_action:node
node_save_action: entity:save_action:node
bypass: true
-
plugin: skip_on_empty
......
......@@ -6,5 +6,5 @@ dependencies:
id: comment_publish_action
label: 'Publish comment'
type: comment
plugin: comment_publish_action
plugin: entity:publish_action:comment
configuration: { }
......@@ -6,5 +6,5 @@ dependencies:
id: comment_save_action
label: 'Save comment'
type: comment
plugin: comment_save_action
plugin: entity:save_action:comment
configuration: { }
......@@ -6,5 +6,5 @@ dependencies:
id: comment_unpublish_action
label: 'Unpublish comment'
type: comment
plugin: comment_unpublish_action
plugin: entity:unpublish_action:comment
configuration: { }
......@@ -15,10 +15,14 @@ field.widget.settings.comment_default:
type: mapping
label: 'Comment display format settings'
# @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
# @see https://www.drupal.org/node/2919303
action.configuration.comment_publish_action:
type: action_configuration_default
label: 'Publish comment configuration'
# @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
# @see https://www.drupal.org/node/2919303
action.configuration.comment_save_action:
type: action_configuration_default
label: 'Save comment configuration'
......@@ -34,6 +38,8 @@ action.configuration.comment_unpublish_by_keyword_action:
type: string
label: 'Keyword'
# @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
# @see https://www.drupal.org/node/2919303
action.configuration.comment_unpublish_action:
type: action_configuration_default
label: 'Unpublish comment configuration'
......
......@@ -2,37 +2,32 @@
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Action\Plugin\Action\PublishAction;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* Publishes a comment.
*
* @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
* Use \Drupal\Core\Action\Plugin\Action\PublishAction instead.
*
* @see \Drupal\Core\Action\Plugin\Action\PublishAction
* @see https://www.drupal.org/node/2919303
*
* @Action(
* id = "comment_publish_action",
* label = @Translation("Publish comment"),
* type = "comment"
* )
*/
class PublishComment extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($comment = NULL) {
$comment->setPublished(TRUE);
$comment->save();
}
class PublishComment extends PublishAction {
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\comment\CommentInterface $object */
$result = $object->status->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
public function __construct($configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
@trigger_error(__NAMESPACE__ . '\PublishComment is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\PublishAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
}
}
......@@ -2,33 +2,33 @@
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Action\Plugin\Action\SaveAction;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* Saves a comment.
*
* @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
* Use \Drupal\Core\Action\Plugin\Action\SaveAction instead.
*
* @see \Drupal\Core\Action\Plugin\Action\SaveAction
* @see https://www.drupal.org/node/2919303
*
* @Action(
* id = "comment_save_action",
* label = @Translation("Save comment"),
* type = "comment"
* )
*/
class SaveComment extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($comment = NULL) {
$comment->save();
}
class SaveComment extends SaveAction {
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\comment\CommentInterface $object */
return $object->access('update', $account, $return_as_object);
public function __construct($configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $time);
@trigger_error(__NAMESPACE__ . '\SaveComment is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\SaveAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
}
}
......@@ -2,37 +2,32 @@
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Action\Plugin\Action\UnpublishAction;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* Unpublishes a comment.
*
* @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
* Use \Drupal\Core\Action\Plugin\Action\UnpublishAction instead.
*
* @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
* @see https://www.drupal.org/node/2919303
*
* @Action(
* id = "comment_unpublish_action",
* label = @Translation("Unpublish comment"),
* type = "comment"
* )
*/
class UnpublishComment extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($comment = NULL) {
$comment->setPublished(FALSE);
$comment->save();
}
class UnpublishComment extends UnpublishAction {
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\comment\CommentInterface $object */
$result = $object->status->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
public function __construct($configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
@trigger_error(__NAMESPACE__ . '\UnpublishComment is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\UnpublishAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
}
}
......@@ -32,7 +32,7 @@ public function testCommentPublishUnpublishActions() {
$action = Action::load('comment_unpublish_action');
$action->execute([$comment]);
$this->assertTrue($comment->isPublished() === FALSE, 'Comment was unpublished');
$this->assertArraySubset(['module' => ['comment']], $action->getDependencies());
// Publish a comment.
$action = Action::load('comment_publish_action');
$action->execute([$comment]);
......
......@@ -8,8 +8,8 @@
use Drupal\content_moderation\EntityOperations;
use Drupal\content_moderation\EntityTypeInfo;
use Drupal\content_moderation\ContentPreprocess;
use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublishNode;
use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish;
use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublish;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
......@@ -21,8 +21,8 @@
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\workflows\WorkflowInterface;
use Drupal\node\Plugin\Action\PublishNode;
use Drupal\node\Plugin\Action\UnpublishNode;
use Drupal\Core\Action\Plugin\Action\PublishAction;
use Drupal\Core\Action\Plugin\Action\UnpublishAction;
use Drupal\workflows\Entity\Workflow;
/**
......@@ -227,13 +227,15 @@ function content_moderation_action_info_alter(&$definitions) {
// The publish/unpublish actions are not valid on moderated entities. So swap
// their implementations out for alternates that will become a no-op on a
// moderated node. If another module has already swapped out those classes,
// moderated entity. If another module has already swapped out those classes,
// though, we'll be polite and do nothing.
if (isset($definitions['node_publish_action']['class']) && $definitions['node_publish_action']['class'] == PublishNode::class) {
$definitions['node_publish_action']['class'] = ModerationOptOutPublishNode::class;
foreach ($definitions as &$definition) {
if ($definition['id'] === 'entity:publish_action' && $definition['class'] == PublishAction::class) {
$definition['class'] = ModerationOptOutPublish::class;
}
if ($definition['id'] === 'entity:unpublish_action' && $definition['class'] == UnpublishAction::class) {
$definition['class'] = ModerationOptOutUnpublish::class;
}
if (isset($definitions['node_unpublish_action']['class']) && $definitions['node_unpublish_action']['class'] == UnpublishNode::class) {
$definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class;
}
}
......
<?php
namespace Drupal\content_moderation\Plugin\Action;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\Plugin\Action\PublishAction;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\content_moderation\ModerationInformationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Alternate action plugin that can opt-out of modifying moderated entities.
*
* @see \Drupal\Core\Action\Plugin\Action\PublishAction
*/
class ModerationOptOutPublish extends PublishAction implements ContainerFactoryPluginInterface {
/**
* Moderation information service.
*
* @var \Drupal\content_moderation\ModerationInformationInterface
*/
protected $moderationInfo;
/**
* Bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $bundleInfo;
/**
* ModerationOptOutPublish constructor.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
* The moderation information service.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
* Bundle info service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_info, EntityTypeBundleInfoInterface $bundle_info) {
parent::__construct