Commit b0cf1be9 authored by alexpott's avatar alexpott

Issue #1846172 by tim.plunkett, damiankloip: Replace the actions API.

parent f164dcc5
......@@ -154,6 +154,9 @@ services:
plugin.manager.archiver:
class: Drupal\Core\Archiver\ArchiverManager
arguments: ['@container.namespaces']
plugin.manager.action:
class: Drupal\Core\Action\ActionManager
arguments: ['@container.namespaces']
request:
class: Symfony\Component\HttpFoundation\Request
event_dispatcher:
......
......@@ -2929,16 +2929,12 @@ function drupal_classloader_register($name, $path) {
*
* Example:
* @code
* function actions_do(...) {
* // $stack tracks the number of recursive calls.
* static $stack;
* $stack++;
* if ($stack > variable_get('action_max_stack', 35)) {
* ...
* return;
* function system_get_module_info($property) {
* static $info;
* if (!isset($info)) {
* $info = new ModuleInfo('system_info', 'cache');
* }
* ...
* $stack--;
* return $info[$property];
* }
* @endcode
*
......
<?php
/**
* @file
* Contains \Drupal\Core\Action\ActionBag.
*/
namespace Drupal\Core\Action;
use Drupal\Component\Plugin\PluginBag;
use Drupal\Component\Plugin\PluginManagerInterface;
/**
* Provides a container for lazily loading Action plugins.
*/
class ActionBag extends PluginBag {
/**
* The manager used to instantiate the plugins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $manager;
/**
* Constructs a new ActionBag object.
*
* @param \Drupal\Component\Plugin\PluginManagerInterface $manager
* The manager to be used for instantiating plugins.
* @param array $instance_ids
* The ids of the plugin instances with which we are dealing.
* @param array $configuration
* An array of configuration.
*/
public function __construct(PluginManagerInterface $manager, array $instance_ids, array $configuration) {
$this->manager = $manager;
$this->instanceIDs = drupal_map_assoc($instance_ids);
$this->configuration = $configuration;
}
/**
* {@inheritdoc}
*/
protected function initializePlugin($instance_id) {
if (isset($this->pluginInstances[$instance_id])) {
return;
}
$this->pluginInstances[$instance_id] = $this->manager->createInstance($instance_id, $this->configuration);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Action\ActionBase.
*/
namespace Drupal\Core\Action;
use Drupal\Core\Action\ActionInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginBase;
/**
* Provides a base implementation for an Action plugin.
*/
abstract class ActionBase extends ContainerFactoryPluginBase implements ActionInterface {
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
foreach ($entities as $entity) {
$this->execute($entity);
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Action\ActionInterface.
*/
namespace Drupal\Core\Action;
use Drupal\Core\Executable\ExecutableInterface;
/**
* Provides an interface for an Action plugin.
*
* @see \Drupal\Core\Annotation\Action
* @see \Drupal\Core\Action\ActionManager
*/
interface ActionInterface extends ExecutableInterface {
/**
* Executes the plugin for an array of objects.
*
* @param array $objects
* An array of entities.
*/
public function executeMultiple(array $objects);
}
<?php
/**
* @file
* Contains \Drupal\Core\Action\ActionManager.
*/
namespace Drupal\Core\Action;
use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Core\Plugin\Discovery\AlterDecorator;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Core\Plugin\Factory\ContainerFactory;
/**
* Provides an Action plugin manager.
*
* @see \Drupal\Core\Annotation\Operation
* @see \Drupal\Core\Action\OperationInterface
*/
class ActionManager extends PluginManagerBase {
/**
* Constructs a ActionManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
*/
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('Action', $namespaces, array(), 'Drupal\Core\Annotation\Action');
$this->discovery = new AlterDecorator($this->discovery, 'action_info');
$this->factory = new ContainerFactory($this);
}
/**
* Gets the plugin definitions for this entity type.
*
* @param string $type
* The entity type name.
*
* @return array
* An array of plugin definitions for this entity type.
*/
public function getDefinitionsByType($type) {
return array_filter($this->getDefinitions(), function ($definition) use ($type) {
return $definition['type'] === $type;
});
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Action\ConfigurableActionBase.
*/
namespace Drupal\Core\Action;
use Drupal\Core\Action\ConfigurableActionInterface;
use Drupal\Core\Action\ActionBase;
/**
* Provides a base implementation for a configurable Action plugin.
*/
abstract class ConfigurableActionBase extends ActionBase implements ConfigurableActionInterface {
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->configuration += $this->getDefaultConfiguration();
}
/**
* Returns default configuration for this action.
*
* @return array
*/
protected function getDefaultConfiguration() {
return array();
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return $this->configuration;
}
/**
* {@inheritdoc}
*/
public function validate(array &$form, array &$form_state) {
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Action\ConfigurableActionInterface.
*/
namespace Drupal\Core\Action;
use Drupal\Core\Action\ActionInterface;
/**
* Provides an interface for an Action plugin.
*
* @see \Drupal\Core\Annotation\Operation
* @see \Drupal\Core\Action\OperationManager
*/
interface ConfigurableActionInterface extends ActionInterface {
/**
* Returns this plugin's configuration.
*
* @return array
* An array of this action plugin's configuration.
*/
public function getConfiguration();
/**
* Form constructor.
*
* @param array $form
* An associative array containing the structure of the form.
* @param array $form_state
* An associative array containing the current state of the form.
*
* @return array
* The form structure.
*/
public function form(array $form, array &$form_state);
/**
* Form validation handler.
*
* @param array $form
* An associative array containing the structure of the form.
* @param array $form_state
* An associative array containing the current state of the form.
*/
public function validate(array &$form, array &$form_state);
/**
* Form submission handler.
*
* @param array $form
* An associative array containing the structure of the form.
* @param array $form_state
* An associative array containing the current state of the form.
*/
public function submit(array &$form, array &$form_state);
}
<?php
/**
* @file
* Contains \Drupal\Core\Annotation\Action.
*/
namespace Drupal\Core\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines an Action annotation object.
*
* @see \Drupal\Core\Action\ActionInterface
* @see \Drupal\Core\Action\ActionManager
*
* @Annotation
*/
class Action extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the action plugin.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* The path for a confirmation form for this action.
*
* @todo Change this to accept a route.
* @todo Provide a more generic way to allow an action to be confirmed first.
*
* @var string (optional)
*/
public $confirm_form_path = '';
/**
* The entity type the action can apply to.
*
* @todo Replace with \Drupal\Core\Plugin\Context\Context.
*
* @var string
*/
public $type = '';
}
......@@ -185,6 +185,7 @@ public function getFormController($entity_type, $operation) {
$class = $this->getControllerClass($entity_type, 'form', $operation);
if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
$this->controllers['form'][$operation][$entity_type] = $class::createInstance($this->container, $entity_type, $this->getDefinition($entity_type));
$this->controllers['form'][$operation][$entity_type]->setOperation($operation);
}
else {
$this->controllers['form'][$operation][$entity_type] = new $class($operation);
......
<?php
/**
* @file
* Admin page callbacks for the Action module.
*/
/**
* Post-deletion operations for deleting action orphans.
*
* @param $orphaned
* An array of orphaned actions.
*/
function action_admin_delete_orphans_post($orphaned) {
foreach ($orphaned as $callback) {
drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
}
}
......@@ -5,91 +5,11 @@
* Hooks provided by the Actions module.
*/
/**
* Declares information about actions.
*
* Any module can define actions, and then call actions_do() to make those
* actions happen in response to events.
*
* An action consists of two or three parts:
* - an action definition (returned by this hook)
* - a function which performs the action (which by convention is named
* MODULE_description-of-function_action)
* - an optional form definition function that defines a configuration form
* (which has the name of the action function with '_form' appended to it.)
*
* The action function takes two to four arguments, which come from the input
* arguments to actions_do().
*
* @return
* An associative array of action descriptions. The keys of the array
* are the names of the action functions, and each corresponding value
* is an associative array with the following key-value pairs:
* - 'type': The type of object this action acts upon. Core actions have types
* 'node', 'user', 'comment', and 'system'.
* - 'label': The human-readable name of the action, which should be passed
* through the t() function for translation.
* - 'configurable': If FALSE, then the action doesn't require any extra
* configuration. If TRUE, then your module must define a form function with
* the same name as the action function with '_form' appended (e.g., the
* form for 'node_assign_owner_action' is 'node_assign_owner_action_form'.)
* This function takes $context as its only parameter, and is paired with
* the usual _submit function, and possibly a _validate function.
* - 'triggers': An array of the events (that is, hooks) that can trigger this
* action. For example: array('node_insert', 'user_update'). You can also
* declare support for any trigger by returning array('any') for this value.
* - 'behavior': (optional) A machine-readable array of behaviors of this
* action, used to signal additionally required actions that may need to be
* triggered. Modules that are processing actions should take special care
* for the "presave" hook, in which case a dependent "save" action should
* NOT be invoked.
*
* @ingroup actions
*/
function hook_action_info() {
return array(
'comment_unpublish_action' => array(
'type' => 'comment',
'label' => t('Unpublish comment'),
'configurable' => FALSE,
'behavior' => array('changes_property'),
'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
),
'comment_unpublish_by_keyword_action' => array(
'type' => 'comment',
'label' => t('Unpublish comment containing keyword(s)'),
'configurable' => TRUE,
'behavior' => array('changes_property'),
'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
),
'comment_save_action' => array(
'type' => 'comment',
'label' => t('Save comment'),
'configurable' => FALSE,
'triggers' => array('comment_insert', 'comment_update'),
),
);
}
/**
* Alters the actions declared by another module.
*
* Called by action_list() to allow modules to alter the return values from
* implementations of hook_action_info().
*
* @ingroup actions
*/
function hook_action_info_alter(&$actions) {
$actions['node_unpublish_action']['label'] = t('Unpublish and remove from public view.');
}
/**
* Executes code after an action is deleted.
*
* @param $aid
* The action ID.
*
* @ingroup actions
*/
function hook_action_delete($aid) {
db_delete('actions_assignments')
......
<?php
/**
* @file
* Install, update and uninstall functions for the Actions module.
*/
/**
* Implements hook_schema().
*/
function action_schema() {
// 'action' is a reserved SQL keyword.
$schema['actions'] = array(
'description' => 'Stores action information.',
'fields' => array(
'aid' => array(
'description' => 'Primary Key: Unique action ID.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '0',
),
'type' => array(
'description' => 'The object that that action acts on (node, user, comment, system or custom types.)',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'callback' => array(
'description' => 'The callback function that executes when the action runs.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'parameters' => array(
'description' => 'Parameters to be passed to the callback function.',
'type' => 'blob',
'not null' => TRUE,
'size' => 'big',
),
'label' => array(
'description' => 'Label of the action.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '0',
),
),
'primary key' => array('aid'),
);
return $schema;
}
This diff is collapsed.
action_admin:
pattern: '/admin/config/system/actions'
defaults:
_content: '\Drupal\action\Controller\ActionController::adminManage'
_content: '\Drupal\Core\Entity\Controller\EntityListController::listing'
entity_type: 'action'
requirements:
_permission: 'administer actions'
action_admin_orphans_remove:
pattern: '/admin/config/system/actions/orphan'
action_admin_add:
pattern: '/admin/config/system/actions/add/{action_id}'
defaults:
_content: '\Drupal\action\Controller\ActionController::adminRemoveOrphans'
_entity_form: 'action.add'
requirements:
_permission: 'administer actions'
action_admin_configure:
pattern: '/admin/config/system/actions/configure/{action}'
defaults:
_form: '\Drupal\action\Form\ActionAdminConfigureForm'
_entity_form: 'action.edit'
requirements:
_permission: 'administer actions'
action_delete:
pattern: 'admin/config/system/actions/delete/{action}'
pattern: 'admin/config/system/actions/configure/{action}/delete'
defaults:
_form: '\Drupal\action\Form\DeleteForm'
requirements:
......
<?php
/**
* @file
* Contains \Drupal\action\ActionAddFormController.
*/
namespace Drupal\action;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Action\ActionManager;
use Drupal\Core\Entity\EntityControllerInterface;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form controller for action add forms.
*/
class ActionAddFormController extends ActionFormControllerBase implements EntityControllerInterface {
/**
* The action manager.
*
* @var \Drupal\Core\Action\ActionManager
*/
protected $actionManager;
/**
* Constructs a new ActionAddFormController.
*
* @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller
* The action storage controller.
* @param \Drupal\Core\Action\ActionManager $action_manager
* The action plugin manager.
*/
public function __construct(EntityStorageControllerInterface $storage_controller, ActionManager $action_manager) {
parent::__construct($storage_controller);
$this->actionManager = $action_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
return new static(
$container->get('plugin.manager.entity')->getStorageController($entity_type),
$container->get('plugin.manager.action')
);
}
/**
* {@inheritdoc}
*
* @param string $action_id
* The hashed version of the action ID.
*/
public function buildForm(array $form, array &$form_state, $action_id = NULL) {
// In \Drupal\action\Form\ActionAdminManageForm::buildForm() the action
// are hashed. Here we have to decrypt it to find the desired action ID.
foreach ($this->actionManager->getDefinitions() as $id => $definition) {
$key = Crypt::hashBase64($id);
if ($key === $action_id) {
$this->entity->setPlugin($id);
// Derive the label and type from the action definition.
$this->entity->set('label', $definition['label']);
$this->entity->set('type', $definition['type']);
break;
}
}
return parent::buildForm($form, $form_state);
}
}
<?php
/**
* @file
* Contains Drupal\action\ActionEditFormController.
*/
namespace Drupal\action;
/**
* Provides a form controller for action edit forms.
*/
class ActionEditFormController extends ActionFormControllerBase {
}
<?php
/**
* @file
* Contains Drupal\action\ActionEditFormController.
*/
namespace Drupal\action;
use Drupal\Core\Entity\EntityControllerInterface;
use Drupal\Core\Entity\EntityFormController;
use Drupal\Core\Action\ConfigurableActionInterface;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a base form controller for action forms.
*/
abstract class ActionFormControllerBase extends EntityFormController implements EntityControllerInterface {
/**
* The action plugin being configured.
*