diff --git a/core/core.services.yml b/core/core.services.yml
index a54bb4a50a4..3136bac1f17 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -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:
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index fbe25e80f44..fa93dc8bf6c 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -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
*
diff --git a/core/lib/Drupal/Core/Action/ActionBag.php b/core/lib/Drupal/Core/Action/ActionBag.php
new file mode 100644
index 00000000000..a8210fefa8a
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/ActionBag.php
@@ -0,0 +1,52 @@
+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);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/ActionBase.php b/core/lib/Drupal/Core/Action/ActionBase.php
new file mode 100644
index 00000000000..8b6c571f278
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/ActionBase.php
@@ -0,0 +1,27 @@
+execute($entity);
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/ActionInterface.php b/core/lib/Drupal/Core/Action/ActionInterface.php
new file mode 100644
index 00000000000..06204164927
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/ActionInterface.php
@@ -0,0 +1,28 @@
+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;
+ });
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/ConfigurableActionBase.php b/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
new file mode 100644
index 00000000000..04ca4fe1314
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
@@ -0,0 +1,49 @@
+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) {
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/ConfigurableActionInterface.php b/core/lib/Drupal/Core/Action/ConfigurableActionInterface.php
new file mode 100644
index 00000000000..c0a66a9df17
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/ConfigurableActionInterface.php
@@ -0,0 +1,61 @@
+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);
diff --git a/core/modules/action/action.admin.inc b/core/modules/action/action.admin.inc
deleted file mode 100644
index 89202c300a6..00000000000
--- a/core/modules/action/action.admin.inc
+++ /dev/null
@@ -1,18 +0,0 @@
- $callback)));
- }
-}
diff --git a/core/modules/action/action.api.php b/core/modules/action/action.api.php
index 8e92c629f25..bdd7c0f9228 100644
--- a/core/modules/action/action.api.php
+++ b/core/modules/action/action.api.php
@@ -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')
diff --git a/core/modules/action/action.install b/core/modules/action/action.install
deleted file mode 100644
index 4e500fdd6f3..00000000000
--- a/core/modules/action/action.install
+++ /dev/null
@@ -1,54 +0,0 @@
- '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;
-}
diff --git a/core/modules/action/action.module b/core/modules/action/action.module
index 0cd4397bb3f..e9f6dc57c3c 100644
--- a/core/modules/action/action.module
+++ b/core/modules/action/action.module
@@ -5,26 +5,6 @@
* This is the Actions module for executing stored actions.
*/
-use Drupal\Component\Utility\Crypt;
-
-/**
- * @defgroup actions Actions
- * @{
- * Functions that perform an action on a certain system object.
- *
- * Action functions are declared by modules by implementing hook_action_info().
- * Modules can cause action functions to run by calling actions_do().
- *
- * Each action function takes two to four arguments:
- * - $entity: The object that the action acts on, such as a node, comment, or
- * user.
- * - $context: Array of additional information about what triggered the action.
- * - $a1, $a2: Optional additional information, which can be passed into
- * actions_do() and will be passed along to the action function.
- *
- * @} End of "defgroup actions".
- */
-
/**
* Implements hook_help().
*/
@@ -69,12 +49,17 @@ function action_menu() {
'description' => 'Manage the actions defined for your site.',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
+ $items['admin/config/system/actions/add'] = array(
+ 'title' => 'Create an advanced action',
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ 'route_name' => 'action_admin_add',
+ );
$items['admin/config/system/actions/configure'] = array(
'title' => 'Configure an advanced action',
'type' => MENU_VISIBLE_IN_BREADCRUMB,
'route_name' => 'action_admin_configure',
);
- $items['admin/config/system/actions/delete/%action'] = array(
+ $items['admin/config/system/actions/configure/%/delete'] = array(
'title' => 'Delete action',
'description' => 'Delete an action.',
'route_name' => 'action_delete',
@@ -83,615 +68,10 @@ function action_menu() {
}
/**
- * Implements hook_rebuild().
+ * Implements hook_entity_info().
*/
-function action_rebuild() {
- // Synchronize any actions that were added or removed.
- action_synchronize();
-}
-
-/**
- * Performs a given list of actions by executing their callback functions.
- *
- * Given the IDs of actions to perform, this function finds out what the
- * callback functions for the actions are by querying the database. Then
- * it calls each callback using the function call $function($object, $context,
- * $a1, $a2), passing the input arguments of this function (see below) to the
- * action function.
- *
- * @param $action_ids
- * The IDs of the actions to perform. Can be a single action ID or an array
- * of IDs. IDs of configurable actions must be given as numeric action IDs;
- * IDs of non-configurable actions may be given as action function names.
- * @param $object
- * The object that the action will act on: a node, user, or comment object.
- * @param $context
- * Associative array containing extra information about what triggered
- * the action call, with $context['hook'] giving the name of the hook
- * that resulted in this call to actions_do(). Additional parameters
- * will be used as the data for token replacement.
- * @param $a1
- * Passed along to the callback.
- * @param $a2
- * Passed along to the callback.
- *
- * @return
- * An associative array containing the results of the functions that
- * perform the actions, keyed on action ID.
- *
- * @ingroup actions
- */
-function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) {
- // $stack tracks the number of recursive calls.
- static $stack;
- $stack++;
- $recursion_limit = config('action.settings')->get('recursion_limit');
- if ($stack > $recursion_limit) {
- watchdog('action', 'Stack overflow: recursion limit for actions_do() has been reached. Stack is limited by %limit calls.', array('%limit' => $recursion_limit), WATCHDOG_ERROR);
- return;
- }
- $actions = array();
- $available_actions = action_list();
- $result = array();
- if (is_array($action_ids)) {
- $conditions = array();
- foreach ($action_ids as $action_id) {
- if (is_numeric($action_id)) {
- $conditions[] = $action_id;
- }
- elseif (isset($available_actions[$action_id])) {
- $actions[$action_id] = $available_actions[$action_id];
- }
- }
-
- // When we have action instances we must go to the database to retrieve
- // instance data.
- if (!empty($conditions)) {
- $query = db_select('actions');
- $query->addField('actions', 'aid');
- $query->addField('actions', 'type');
- $query->addField('actions', 'callback');
- $query->addField('actions', 'parameters');
- $query->condition('aid', $conditions, 'IN');
- foreach ($query->execute() as $action) {
- $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
- $actions[$action->aid]['callback'] = $action->callback;
- $actions[$action->aid]['type'] = $action->type;
- }
- }
-
- // Fire actions, in no particular order.
- foreach ($actions as $action_id => $params) {
- // Configurable actions need parameters.
- if (is_numeric($action_id)) {
- $function = $params['callback'];
- if (function_exists($function)) {
- $context = array_merge($context, $params);
- $result[$action_id] = $function($object, $context, $a1, $a2);
- }
- else {
- $result[$action_id] = FALSE;
- }
- }
- // Singleton action; $action_id is the function name.
- else {
- $result[$action_id] = $action_id($object, $context, $a1, $a2);
- }
- }
- }
- // Optimized execution of a single action.
- else {
- // If it's a configurable action, retrieve stored parameters.
- if (is_numeric($action_ids)) {
- $action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject();
- $function = $action->callback;
- if (function_exists($function)) {
- $context = array_merge($context, unserialize($action->parameters));
- $result[$action_ids] = $function($object, $context, $a1, $a2);
- }
- else {
- $result[$action_ids] = FALSE;
- }
- }
- // Singleton action; $action_ids is the function name.
- else {
- if (function_exists($action_ids)) {
- $result[$action_ids] = $action_ids($object, $context, $a1, $a2);
- }
- else {
- // Set to avoid undefined index error messages later.
- $result[$action_ids] = FALSE;
- }
- }
- }
- $stack--;
- return $result;
-}
-
-/**
- * Discovers all available actions by invoking hook_action_info().
- *
- * This function contrasts with action_get_all_actions(); see the
- * documentation of action_get_all_actions() for an explanation.
- *
- * @param $reset
- * Reset the action info static cache.
- *
- * @return
- * An associative array keyed on action function name, with the same format
- * as the return value of hook_action_info(), containing all
- * modules' hook_action_info() return values as modified by any
- * hook_action_info_alter() implementations.
- *
- * @see hook_action_info()
- */
-function action_list($reset = FALSE) {
- $actions = &drupal_static(__FUNCTION__);
- if (!isset($actions) || $reset) {
- $actions = module_invoke_all('action_info');
- drupal_alter('action_info', $actions);
- }
-
- // See module_implements() for an explanation of this cast.
- return (array) $actions;
-}
-
-/**
- * Retrieves all action instances from the database.
- *
- * This function differs from the action_list() function, which gathers
- * actions by invoking hook_action_info(). The actions returned by this
- * function and the actions returned by action_list() are partially
- * synchronized. Non-configurable actions from hook_action_info()
- * implementations are put into the database when action_synchronize() is
- * called, which happens when admin/config/system/actions is visited.
- * Configurable actions are not added to the database until they are configured
- * in the user interface, in which case a database row is created for each
- * configuration of each action.
- *
- * @return
- * Associative array keyed by numeric action ID. Each value is an associative
- * array with keys 'callback', 'label', 'type' and 'configurable'.
- */
-function action_get_all_actions() {
- $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC);
- foreach ($actions as &$action) {
- $action['configurable'] = (bool) $action['parameters'];
- unset($action['parameters']);
- unset($action['aid']);
- }
- return $actions;
-}
-
-/**
- * Creates an associative array keyed by hashes of function names or IDs.
- *
- * Hashes are used to prevent actual function names from going out into HTML
- * forms and coming back.
- *
- * @param $actions
- * An associative array with function names or action IDs as keys
- * and associative arrays with keys 'label', 'type', etc. as values.
- * This is usually the output of action_list() or action_get_all_actions().
- *
- * @return
- * An associative array whose keys are hashes of the input array keys, and
- * whose corresponding values are associative arrays with components
- * 'callback', 'label', 'type', and 'configurable' from the input array.
- */
-function action_actions_map($actions) {
- $actions_map = array();
- foreach ($actions as $callback => $array) {
- $key = Crypt::hashBase64($callback);
- $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
- $actions_map[$key]['label'] = $array['label'];
- $actions_map[$key]['type'] = $array['type'];
- $actions_map[$key]['configurable'] = $array['configurable'];
- }
- return $actions_map;
-}
-
-/**
- * Returns an action array key (function or ID), given its hash.
- *
- * Faster than action_actions_map() when you only need the function name or ID.
- *
- * @param $hash
- * Hash of a function name or action ID array key. The array key
- * is a key into the return value of action_list() (array key is the action
- * function name) or action_get_all_actions() (array key is the action ID).
- *
- * @return
- * The corresponding array key, or FALSE if no match is found.
- */
-function action_function_lookup($hash) {
- // Check for a function name match.
- $actions_list = action_list();
- foreach ($actions_list as $function => $array) {
- if (Crypt::hashBase64($function) == $hash) {
- return $function;
- }
- }
- $aid = FALSE;
- // Must be a configurable action; check database.
- $result = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchAll(PDO::FETCH_ASSOC);
- foreach ($result as $row) {
- if (Crypt::hashBase64($row['aid']) == $hash) {
- $aid = $row['aid'];
- break;
- }
- }
- return $aid;
-}
-
-/**
- * Synchronizes actions that are provided by modules in hook_action_info().
- *
- * Actions provided by modules in hook_action_info() implementations are
- * synchronized with actions that are stored in the actions database table.
- * This is necessary so that actions that do not require configuration can
- * receive action IDs.
- *
- * @param $delete_orphans
- * If TRUE, any actions that exist in the database but are no longer
- * found in the code (for example, because the module that provides them has
- * been disabled) will be deleted.
- */
-function action_synchronize($delete_orphans = FALSE) {
- $actions_in_code = action_list(TRUE);
- $actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC);
-
- // Go through all the actions provided by modules.
- foreach ($actions_in_code as $callback => $array) {
- // Ignore configurable actions since their instances get put in when the
- // user adds the action.
- if (!$array['configurable']) {
- // If we already have an action ID for this action, no need to assign aid.
- if (isset($actions_in_db[$callback])) {
- unset($actions_in_db[$callback]);
- }
- else {
- // This is a new singleton that we don't have an aid for; assign one.
- db_insert('actions')
- ->fields(array(
- 'aid' => $callback,
- 'type' => $array['type'],
- 'callback' => $callback,
- 'parameters' => '',
- 'label' => $array['label'],
- ))
- ->execute();
- watchdog('action', "Action '%action' added.", array('%action' => $array['label']));
- }
- }
- }
-
- // Any actions that we have left in $actions_in_db are orphaned.
- if ($actions_in_db) {
- $orphaned = array_keys($actions_in_db);
-
- if ($delete_orphans) {
- $actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll();
- foreach ($actions as $action) {
- action_delete($action->aid);
- watchdog('action', "Removed orphaned action '%action' from database.", array('%action' => $action->label));
- }
- }
- else {
- $link = l(t('Remove orphaned actions'), 'admin/config/system/actions/orphan');
- $count = count($actions_in_db);
- $orphans = implode(', ', $orphaned);
- watchdog('action', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), WATCHDOG_INFO);
- }
- }
-}
-
-/**
- * Saves an action and its user-supplied parameter values to the database.
- *
- * @param $function
- * The name of the function to be called when this action is performed.
- * @param $type
- * The type of action, to describe grouping and/or context, e.g., 'node',
- * 'user', 'comment', or 'system'.
- * @param $params
- * An associative array with parameter names as keys and parameter values as
- * values.
- * @param $label
- * A user-supplied label of this particular action, e.g., 'Send e-mail
- * to Jim'.
- * @param $aid
- * The ID of this action. If omitted, a new action is created.
- *
- * @return
- * The ID of the action.
- */
-function action_save($function, $type, $params, $label, $aid = NULL) {
- // aid is the callback for singleton actions so we need to keep a separate
- // table for numeric aids.
- if (!$aid) {
- $aid = db_next_id();
- }
-
- db_merge('actions')
- ->key(array('aid' => $aid))
- ->fields(array(
- 'callback' => $function,
- 'type' => $type,
- 'parameters' => serialize($params),
- 'label' => $label,
- ))
- ->execute();
-
- watchdog('action', 'Action %action saved.', array('%action' => $label));
- return $aid;
-}
-
-/**
- * Retrieves a single action from the database.
- *
- * @param $aid
- * The ID of the action to retrieve.
- *
- * @return
- * The appropriate action row from the database as an object.
- */
-function action_load($aid) {
- return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject();
-}
-
-/**
- * Deletes a single action from the database.
- *
- * @param $aid
- * The ID of the action to delete.
- */
-function action_delete($aid) {
- db_delete('actions')
- ->condition('aid', $aid)
- ->execute();
- module_invoke_all('action_delete', $aid);
-}
-
-/**
- * Implements hook_action_info().
- *
- * @ingroup actions
- */
-function action_action_info() {
- return array(
- 'action_message_action' => array(
- 'type' => 'system',
- 'label' => t('Display a message to the user'),
- 'configurable' => TRUE,
- 'triggers' => array('any'),
- ),
- 'action_send_email_action' => array(
- 'type' => 'system',
- 'label' => t('Send e-mail'),
- 'configurable' => TRUE,
- 'triggers' => array('any'),
- ),
- 'action_goto_action' => array(
- 'type' => 'system',
- 'label' => t('Redirect to URL'),
- 'configurable' => TRUE,
- 'triggers' => array('any'),
- ),
- );
-}
-
-/**
- * Return a form definition so the Send email action can be configured.
- *
- * @param $context
- * Default values (if we are editing an existing action instance).
- *
- * @return
- * Form definition.
- *
- * @see action_send_email_action_validate()
- * @see action_send_email_action_submit()
- */
-function action_send_email_action_form($context) {
- // Set default values for form.
- if (!isset($context['recipient'])) {
- $context['recipient'] = '';
- }
- if (!isset($context['subject'])) {
- $context['subject'] = '';
- }
- if (!isset($context['message'])) {
- $context['message'] = '';
- }
-
- $form['recipient'] = array(
- '#type' => 'textfield',
- '#title' => t('Recipient'),
- '#default_value' => $context['recipient'],
- '#maxlength' => '254',
- '#description' => t('The e-mail address to which the message should be sent OR enter [node:author:mail], [comment:author:mail], etc. if you would like to send an e-mail to the author of the original post.'),
- );
- $form['subject'] = array(
- '#type' => 'textfield',
- '#title' => t('Subject'),
- '#default_value' => $context['subject'],
- '#maxlength' => '254',
- '#description' => t('The subject of the message.'),
- );
- $form['message'] = array(
- '#type' => 'textarea',
- '#title' => t('Message'),
- '#default_value' => $context['message'],
- '#cols' => '80',
- '#rows' => '20',
- '#description' => t('The message that should be sent. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
- );
- return $form;
-}
-
-/**
- * Validates action_send_email_action() form submissions.
- */
-function action_send_email_action_validate($form, $form_state) {
- $form_values = $form_state['values'];
- // Validate the configuration form.
- if (!valid_email_address($form_values['recipient']) && strpos($form_values['recipient'], ':mail') === FALSE) {
- // We want the literal %author placeholder to be emphasized in the error message.
- form_set_error('recipient', t('Enter a valid email address or use a token e-mail address such as %author.', array('%author' => '[node:author:mail]')));
- }
-}
-
-/**
- * Processes action_send_email_action() form submissions.
- */
-function action_send_email_action_submit($form, $form_state) {
- $form_values = $form_state['values'];
- // Process the HTML form to store configuration. The keyed array that
- // we return will be serialized to the database.
- $params = array(
- 'recipient' => $form_values['recipient'],
- 'subject' => $form_values['subject'],
- 'message' => $form_values['message'],
- );
- return $params;
-}
-
-/**
- * Sends an e-mail message.
- *
- * @param object $entity
- * An optional node entity, which will be added as $context['node'] if
- * provided.
- * @param array $context
- * Array with the following elements:
- * - 'recipient': E-mail message recipient. This will be passed through
- * \Drupal\Core\Utility\Token::replace().
- * - 'subject': The subject of the message. This will be passed through
- * \Drupal\Core\Utility\Token::replace().
- * - 'message': The message to send. This will be passed through
- * \Drupal\Core\Utility\Token::replace().
- * - Other elements will be used as the data for token replacement.
- *
- * @ingroup actions
- */
-function action_send_email_action($entity, $context) {
- if (empty($context['node'])) {
- $context['node'] = $entity;
- }
-
- $recipient = Drupal::token()->replace($context['recipient'], $context);
-
- // If the recipient is a registered user with a language preference, use
- // the recipient's preferred language. Otherwise, use the system default
- // language.
- $recipient_account = user_load_by_mail($recipient);
- if ($recipient_account) {
- $langcode = user_preferred_langcode($recipient_account);
- }
- else {
- $langcode = language_default()->langcode;
- }
- $params = array('context' => $context);
-
- if (drupal_mail('system', 'action_send_email', $recipient, $langcode, $params)) {
- watchdog('action', 'Sent email to %recipient', array('%recipient' => $recipient));
- }
- else {
- watchdog('error', 'Unable to send email to %recipient', array('%recipient' => $recipient));
- }
-}
-
-/**
- * Constructs the settings form for action_message_action().
- *
- * @see action_message_action_submit()
- */
-function action_message_action_form($context) {
- $form['message'] = array(
- '#type' => 'textarea',
- '#title' => t('Message'),
- '#default_value' => isset($context['message']) ? $context['message'] : '',
- '#required' => TRUE,
- '#rows' => '8',
- '#description' => t('The message to be displayed to the current user. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
- );
- return $form;
-}
-
-/**
- * Processes action_message_action form submissions.
- */
-function action_message_action_submit($form, $form_state) {
- return array('message' => $form_state['values']['message']);
-}
-
-/**
- * Sends a message to the current user's screen.
- *
- * @param object $entity
- * An optional node entity, which will be added as $context['node'] if
- * provided.
- * @param array $context
- * Array with the following elements:
- * - 'message': The message to send. This will be passed through
- * \Drupal\Core\Utility\Token::replace().
- * - Other elements will be used as the data for token replacement in
- * the message.
- *
- * @ingroup actions
- */
-function action_message_action(&$entity, $context = array()) {
- if (empty($context['node'])) {
- $context['node'] = $entity;
- }
-
- $context['message'] = Drupal::token()->replace(filter_xss_admin($context['message']), $context);
- drupal_set_message($context['message']);
-}
-
-/**
- * Constructs the settings form for action_goto_action().
- *
- * @see action_goto_action_submit()
- */
-function action_goto_action_form($context) {
- $form['url'] = array(
- '#type' => 'textfield',
- '#title' => t('URL'),
- '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like @url.', array('@url' => 'http://drupal.org')),
- '#default_value' => isset($context['url']) ? $context['url'] : '',
- '#required' => TRUE,
- );
- return $form;
-}
-
-/**
- * Processes action_goto_action form submissions.
- */
-function action_goto_action_submit($form, $form_state) {
- return array(
- 'url' => $form_state['values']['url']
- );
-}
-
-/**
- * Redirects to a different URL.
- *
- * Action functions are declared by modules by implementing hook_action_info().
- * Modules can cause action functions to run by calling actions_do().
- *
- * @param object $entity
- * An optional node entity, which will be added as $context['node'] if
- * provided.
- * @param array $context
- * Array with the following elements:
- * - 'url': URL to redirect to. This will be passed through
- * \Drupal\Core\Utility\Token::replace().
- * - Other elements will be used as the data for token replacement.
- *
- * @ingroup actions.
- */
-function action_goto_action($entity, $context) {
- drupal_goto(Drupal::token()->replace($context['url'], $context));
+function action_entity_info(&$entity_info) {
+ $entity_info['action']['controllers']['form']['add'] = 'Drupal\action\ActionAddFormController';
+ $entity_info['action']['controllers']['form']['edit'] = 'Drupal\action\ActionEditFormController';
+ $entity_info['action']['controllers']['list'] = 'Drupal\action\ActionListController';
}
diff --git a/core/modules/action/action.routing.yml b/core/modules/action/action.routing.yml
index a7ed7b900bc..b8a0980ef8d 100644
--- a/core/modules/action/action.routing.yml
+++ b/core/modules/action/action.routing.yml
@@ -1,26 +1,27 @@
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:
diff --git a/core/modules/action/lib/Drupal/action/ActionAddFormController.php b/core/modules/action/lib/Drupal/action/ActionAddFormController.php
new file mode 100644
index 00000000000..85db8fed826
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/ActionAddFormController.php
@@ -0,0 +1,75 @@
+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);
+ }
+
+}
diff --git a/core/modules/action/lib/Drupal/action/ActionEditFormController.php b/core/modules/action/lib/Drupal/action/ActionEditFormController.php
new file mode 100644
index 00000000000..ba758f508e0
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/ActionEditFormController.php
@@ -0,0 +1,15 @@
+storageController = $storage_controller;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+ return new static(
+ $container->get('plugin.manager.entity')->getStorageController($entity_type)
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, array &$form_state) {
+ $this->plugin = $this->entity->getPlugin();
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, array &$form_state) {
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#default_value' => $this->entity->label(),
+ '#maxlength' => '255',
+ '#description' => t('A unique label for this advanced action. This label will be displayed in the interface of modules that integrate with actions.'),
+ );
+
+ $form['id'] = array(
+ '#type' => 'machine_name',
+ '#title' => t('Machine name'),
+ '#default_value' => $this->entity->id(),
+ '#disabled' => !$this->entity->isNew(),
+ '#maxlength' => 64,
+ '#description' => t('A unique name for this action. It must only contain lowercase letters, numbers and underscores.'),
+ '#machine_name' => array(
+ 'exists' => array($this, 'exists'),
+ ),
+ );
+ $form['plugin'] = array(
+ '#type' => 'value',
+ '#value' => $this->entity->get('plugin'),
+ );
+ $form['type'] = array(
+ '#type' => 'value',
+ '#value' => $this->entity->getType(),
+ );
+
+ if ($this->plugin instanceof ConfigurableActionInterface) {
+ $form += $this->plugin->form($form, $form_state);
+ }
+
+ return parent::form($form, $form_state);
+ }
+
+ /**
+ * Determines if the action already exists.
+ *
+ * @param string $id
+ * The action ID
+ *
+ * @return bool
+ * TRUE if the action exists, FALSE otherwise.
+ */
+ public function exists($id) {
+ $actions = $this->storageController->load(array($id));
+ return isset($actions[$id]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function actions(array $form, array &$form_state) {
+ $actions = parent::actions($form, $form_state);
+ unset($actions['delete']);
+ return $actions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(array $form, array &$form_state) {
+ parent::validate($form, $form_state);
+
+ if ($this->plugin instanceof ConfigurableActionInterface) {
+ $this->plugin->validate($form, $form_state);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array $form, array &$form_state) {
+ parent::submit($form, $form_state);
+
+ if ($this->plugin instanceof ConfigurableActionInterface) {
+ $this->plugin->submit($form, $form_state);
+ }
+ return $this->entity;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(array $form, array &$form_state) {
+ $this->entity->save();
+ drupal_set_message(t('The action has been successfully saved.'));
+
+ $form_state['redirect'] = 'admin/config/system/actions';
+ }
+
+}
diff --git a/core/modules/action/lib/Drupal/action/ActionListController.php b/core/modules/action/lib/Drupal/action/ActionListController.php
new file mode 100644
index 00000000000..2af2f12fd6a
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/ActionListController.php
@@ -0,0 +1,137 @@
+actionManager = $action_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+ return new static(
+ $entity_type,
+ $container->get('plugin.manager.entity')->getStorageController($entity_type),
+ $container->get('plugin.manager.action')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function load() {
+ $entities = parent::load();
+ foreach ($entities as $entity) {
+ if ($entity->isConfigurable()) {
+ $this->hasConfigurableActions = TRUE;
+ continue;
+ }
+ }
+ return $entities;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildRow(EntityInterface $entity) {
+ $row['type'] = $entity->getType();
+ $row['label'] = String::checkPlain($entity->label());
+ if ($this->hasConfigurableActions) {
+ $row['operations']['data'] = $this->buildOperations($entity);
+ }
+ return $row;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildHeader() {
+ $header = array(
+ 'type' => t('Action type'),
+ 'label' => t('Label'),
+ 'operations' => t('Operations'),
+ );
+ return $header;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOperations(EntityInterface $entity) {
+ $operations = array();
+ if ($entity->isConfigurable()) {
+ $uri = $entity->uri();
+ $operations['edit'] = array(
+ 'title' => t('Configure'),
+ 'href' => $uri['path'],
+ 'options' => $uri['options'],
+ 'weight' => 10,
+ );
+ $operations['delete'] = array(
+ 'title' => t('Delete'),
+ 'href' => $uri['path'] . '/delete',
+ 'options' => $uri['options'],
+ 'weight' => 100,
+ );
+ }
+ return $operations;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render() {
+ $build['action_header']['#markup'] = '
' . t('Available actions:') . '
';
+ $build['action_table'] = parent::render();
+ if (!$this->hasConfigurableActions) {
+ unset($build['action_table']['#header']['operations']);
+ }
+ $build['action_admin_manage_form'] = drupal_get_form(new ActionAdminManageForm($this->actionManager));
+ return $build;
+ }
+
+}
diff --git a/core/modules/action/lib/Drupal/action/Controller/ActionController.php b/core/modules/action/lib/Drupal/action/Controller/ActionController.php
deleted file mode 100644
index 09423fb2bc8..00000000000
--- a/core/modules/action/lib/Drupal/action/Controller/ActionController.php
+++ /dev/null
@@ -1,139 +0,0 @@
-database = $database;
- }
-
- /**
- * Implements \Drupal\Core\ControllerInterface::create().
- */
- public static function create(ContainerInterface $container) {
- return new static($container->get('database'));
- }
-
- /**
- * Displays an overview of available and configured actions.
- *
- * @return
- * A render array containing a table of existing actions and the advanced
- * action creation form.
- */
- public function adminManage() {
- action_synchronize();
- $actions = action_list();
- $actions_map = action_actions_map($actions);
- $options = array();
- $unconfigurable = array();
-
- foreach ($actions_map as $key => $array) {
- if ($array['configurable']) {
- $options[$key] = $array['label'] . '...';
- }
- else {
- $unconfigurable[] = $array;
- }
- }
-
- $row = array();
- $instances_present = $this->database->query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchField();
- $header = array(
- array('data' => t('Action type'), 'field' => 'type'),
- array('data' => t('Label'), 'field' => 'label'),
- $instances_present ? t('Operations') : '',
- );
- $query = $this->database->select('actions')
- ->extend('Drupal\Core\Database\Query\PagerSelectExtender')
- ->extend('Drupal\Core\Database\Query\TableSortExtender');
- $result = $query
- ->fields('actions')
- ->limit(50)
- ->orderByHeader($header)
- ->execute();
-
- foreach ($result as $action) {
- $row = array();
- $row[] = $action->type;
- $row[] = check_plain($action->label);
- $links = array();
- if ($action->parameters) {
- $links['configure'] = array(
- 'title' => t('configure'),
- 'href' => "admin/config/system/actions/configure/$action->aid",
- );
- $links['delete'] = array(
- 'title' => t('delete'),
- 'href' => "admin/config/system/actions/delete/$action->aid",
- );
- }
- $row[] = array(
- 'data' => array(
- '#type' => 'operations',
- '#links' => $links,
- ),
- );
-
- $rows[] = $row;
- }
-
- if ($rows) {
- $pager = theme('pager');
- if (!empty($pager)) {
- $rows[] = array(array('data' => $pager, 'colspan' => '3'));
- }
- $build['action_header'] = array(
- '#markup' => '' . t('Available actions:') . '
'
- );
- $build['action_table'] = array(
- '#theme' => 'table',
- '#header' => $header,
- '#rows' => $rows,
- );
- }
-
- if ($actions_map) {
- $build['action_admin_manage_form'] = drupal_get_form(new ActionAdminManageForm(), $options);
- }
-
- return $build;
- }
-
- /**
- * Removes actions that are in the database but not supported by any enabled module.
- */
- public function adminRemoveOrphans() {
- action_synchronize(TRUE);
- return new RedirectResponse(url('admin/config/system/actions', array('absolute' => TRUE)));
- }
-
-}
diff --git a/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php b/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php
deleted file mode 100644
index 7d48af407db..00000000000
--- a/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php
+++ /dev/null
@@ -1,125 +0,0 @@
- $aid))->fetch();
- $edit['action_label'] = $data->label;
- $edit['action_type'] = $data->type;
- $function = $data->callback;
- $action = Crypt::hashBase64($data->callback);
- $params = unserialize($data->parameters);
- if ($params) {
- foreach ($params as $name => $val) {
- $edit[$name] = $val;
- }
- }
- }
- // Otherwise, we are creating a new action instance.
- else {
- $function = $actions_map[$action]['callback'];
- $edit['action_label'] = $actions_map[$action]['label'];
- $edit['action_type'] = $actions_map[$action]['type'];
- }
-
- $form['action_label'] = array(
- '#type' => 'textfield',
- '#title' => t('Label'),
- '#default_value' => $edit['action_label'],
- '#maxlength' => '255',
- '#description' => t('A unique label for this advanced action. This label will be displayed in the interface of modules that integrate with actions.'),
- '#weight' => -10,
- );
- $action_form = $function . '_form';
- $form = array_merge($form, $action_form($edit));
- $form['action_type'] = array(
- '#type' => 'value',
- '#value' => $edit['action_type'],
- );
- $form['action_action'] = array(
- '#type' => 'hidden',
- '#value' => $action,
- );
- // $aid is set when configuring an existing action instance.
- if (isset($aid)) {
- $form['action_aid'] = array(
- '#type' => 'hidden',
- '#value' => $aid,
- );
- }
- $form['action_configured'] = array(
- '#type' => 'hidden',
- '#value' => '1',
- );
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Save'),
- '#weight' => 13,
- );
-
- return $form;
- }
-
- /**
- * Implements \Drupal\Core\Form\FormInterface::validateForm().
- */
- public function validateForm(array &$form, array &$form_state) {
- $function = action_function_lookup($form_state['values']['action_action']) . '_validate';
- // Hand off validation to the action.
- if (function_exists($function)) {
- $function($form, $form_state);
- }
- }
-
- /**
- * Implements \Drupal\Core\Form\FormInterface::submitForm().
- */
- public function submitForm(array &$form, array &$form_state) {
- $function = action_function_lookup($form_state['values']['action_action']);
- $submit_function = $function . '_submit';
-
- // Action will return keyed array of values to store.
- $params = $submit_function($form, $form_state);
- $aid = isset($form_state['values']['action_aid']) ? $form_state['values']['action_aid'] : NULL;
-
- action_save($function, $form_state['values']['action_type'], $params, $form_state['values']['action_label'], $aid);
- drupal_set_message(t('The action has been successfully saved.'));
-
- $form_state['redirect'] = 'admin/config/system/actions';
- }
-
-}
diff --git a/core/modules/action/lib/Drupal/action/Form/ActionAdminManageForm.php b/core/modules/action/lib/Drupal/action/Form/ActionAdminManageForm.php
index 269ce651647..d0244b1c80a 100644
--- a/core/modules/action/lib/Drupal/action/Form/ActionAdminManageForm.php
+++ b/core/modules/action/lib/Drupal/action/Form/ActionAdminManageForm.php
@@ -7,27 +7,61 @@
namespace Drupal\action\Form;
+use Drupal\Core\Controller\ControllerInterface;
use Drupal\Core\Form\FormInterface;
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Action\ActionManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a configuration form for configurable actions.
*/
-class ActionAdminManageForm implements FormInterface {
+class ActionAdminManageForm implements FormInterface, ControllerInterface {
/**
- * Implements \Drupal\Core\Form\FormInterface::getFormID().
+ * The action plugin manager.
+ *
+ * @var \Drupal\Core\Action\ActionManager
+ */
+ protected $manager;
+
+ /**
+ * Constructs a new ActionAdminManageForm.
+ *
+ * @param \Drupal\Core\Action\ActionManager $manager
+ * The action plugin manager.
+ */
+ public function __construct(ActionManager $manager) {
+ $this->manager = $manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.action')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
*/
public function getFormID() {
return 'action_admin_manage';
}
/**
- * Implements \Drupal\Core\Form\FormInterface::buildForm().
- *
- * @param array $options
- * An array of configurable actions.
+ * {@inheritdoc}
*/
- public function buildForm(array $form, array &$form_state, array $options = array()) {
+ public function buildForm(array $form, array &$form_state) {
+ $actions = array();
+ foreach ($this->manager->getDefinitions() as $id => $definition) {
+ if (is_subclass_of($definition['class'], '\Drupal\Core\Action\ConfigurableActionInterface')) {
+ $key = Crypt::hashBase64($id);
+ $actions[$key] = $definition['label'] . '...';
+ }
+ }
$form['parent'] = array(
'#type' => 'details',
'#title' => t('Create an advanced action'),
@@ -37,7 +71,7 @@ class ActionAdminManageForm implements FormInterface {
'#type' => 'select',
'#title' => t('Action'),
'#title_display' => 'invisible',
- '#options' => $options,
+ '#options' => $actions,
'#empty_option' => t('Choose an advanced action'),
);
$form['parent']['actions'] = array(
@@ -51,17 +85,17 @@ class ActionAdminManageForm implements FormInterface {
}
/**
- * Implements \Drupal\Core\Form\FormInterface::validateForm().
+ * {@inheritdoc}
*/
public function validateForm(array &$form, array &$form_state) {
}
/**
- * Implements \Drupal\Core\Form\FormInterface::submitForm().
+ * {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
if ($form_state['values']['action']) {
- $form_state['redirect'] = 'admin/config/system/actions/configure/' . $form_state['values']['action'];
+ $form_state['redirect'] = 'admin/config/system/actions/add/' . $form_state['values']['action'];
}
}
diff --git a/core/modules/action/lib/Drupal/action/Form/DeleteForm.php b/core/modules/action/lib/Drupal/action/Form/DeleteForm.php
index 1d1885a3bdb..a3d2b3c3a5f 100644
--- a/core/modules/action/lib/Drupal/action/Form/DeleteForm.php
+++ b/core/modules/action/lib/Drupal/action/Form/DeleteForm.php
@@ -8,6 +8,7 @@
namespace Drupal\action\Form;
use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\system\ActionConfigEntityInterface;
/**
* Builds a form to delete an action.
@@ -17,7 +18,7 @@ class DeleteForm extends ConfirmFormBase {
/**
* The action to be deleted.
*
- * @var \stdClass
+ * @var \Drupal\system\ActionConfigEntityInterface
*/
protected $action;
@@ -25,7 +26,7 @@ class DeleteForm extends ConfirmFormBase {
* {@inheritdoc}
*/
protected function getQuestion() {
- return t('Are you sure you want to delete the action %action?', array('%action' => $this->action->label));
+ return t('Are you sure you want to delete the action %action?', array('%action' => $this->action->label()));
}
/**
@@ -40,7 +41,7 @@ class DeleteForm extends ConfirmFormBase {
* {@inheritdoc}
*/
protected function getCancelPath() {
- return 'admin/config/system/actions/manage';
+ return 'admin/config/system/actions';
}
/**
@@ -53,9 +54,8 @@ class DeleteForm extends ConfirmFormBase {
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, array &$form_state, $action = NULL) {
-
- $this->action = action_load($action);
+ public function buildForm(array $form, array &$form_state, ActionConfigEntityInterface $action = NULL) {
+ $this->action = $action;
return parent::buildForm($form, $form_state);
}
@@ -64,13 +64,12 @@ class DeleteForm extends ConfirmFormBase {
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
+ $this->action->delete();
- action_delete($this->action->aid);
+ watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $this->action->id(), '%action' => $this->action->label()));
+ drupal_set_message(t('Action %action was deleted', array('%action' => $this->action->label())));
- watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $this->action->aid, '%action' => $this->action->label));
- drupal_set_message(t('Action %action was deleted', array('%action' => $this->action->label)));
-
- $form_state['redirect'] = 'admin/config/system/actions/manage';
+ $form_state['redirect'] = 'admin/config/system/actions';
}
}
diff --git a/core/modules/action/lib/Drupal/action/Plugin/Action/EmailAction.php b/core/modules/action/lib/Drupal/action/Plugin/Action/EmailAction.php
new file mode 100644
index 00000000000..91432ab6006
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/Plugin/Action/EmailAction.php
@@ -0,0 +1,163 @@
+token = $token;
+ $this->storageController = $entity_manager->getStorageController('user');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+ return new static($configuration, $plugin_id, $plugin_definition,
+ $container->get('token'),
+ $container->get('plugin.manager.entity')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($entity = NULL) {
+ if (empty($this->configuration['node'])) {
+ $this->configuration['node'] = $entity;
+ }
+
+ $recipient = $this->token->replace($this->configuration['recipient'], $this->configuration);
+
+ // If the recipient is a registered user with a language preference, use
+ // the recipient's preferred language. Otherwise, use the system default
+ // language.
+ $recipient_accounts = $this->storageController->loadByProperties(array('mail' => $recipient));
+ $recipient_account = reset($recipient_accounts);
+ if ($recipient_account) {
+ $langcode = user_preferred_langcode($recipient_account);
+ }
+ else {
+ $langcode = language_default()->langcode;
+ }
+ $params = array('context' => $this->configuration);
+
+ if (drupal_mail('system', 'action_send_email', $recipient, $langcode, $params)) {
+ watchdog('action', 'Sent email to %recipient', array('%recipient' => $recipient));
+ }
+ else {
+ watchdog('error', 'Unable to send email to %recipient', array('%recipient' => $recipient));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultConfiguration() {
+ return array(
+ 'recipient' => '',
+ 'subject' => '',
+ 'message' => '',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, array &$form_state) {
+ $form['recipient'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Recipient'),
+ '#default_value' => $this->configuration['recipient'],
+ '#maxlength' => '254',
+ '#description' => t('The e-mail address to which the message should be sent OR enter [node:author:mail], [comment:author:mail], etc. if you would like to send an e-mail to the author of the original post.'),
+ );
+ $form['subject'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Subject'),
+ '#default_value' => $this->configuration['subject'],
+ '#maxlength' => '254',
+ '#description' => t('The subject of the message.'),
+ );
+ $form['message'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Message'),
+ '#default_value' => $this->configuration['message'],
+ '#cols' => '80',
+ '#rows' => '20',
+ '#description' => t('The message that should be sent. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
+ );
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(array &$form, array &$form_state) {
+ if (!valid_email_address($form_state['values']['recipient']) && strpos($form_state['values']['recipient'], ':mail') === FALSE) {
+ // We want the literal %author placeholder to be emphasized in the error message.
+ form_set_error('recipient', t('Enter a valid email address or use a token e-mail address such as %author.', array('%author' => '[node:author:mail]')));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array &$form, array &$form_state) {
+ $this->configuration['recipient'] = $form_state['values']['recipient'];
+ $this->configuration['subject'] = $form_state['values']['subject'];
+ $this->configuration['message'] = $form_state['values']['message'];
+ }
+
+}
diff --git a/core/modules/action/lib/Drupal/action/Plugin/Action/GotoAction.php b/core/modules/action/lib/Drupal/action/Plugin/Action/GotoAction.php
new file mode 100644
index 00000000000..aff20b07f11
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/Plugin/Action/GotoAction.php
@@ -0,0 +1,62 @@
+configuration['url']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultConfiguration() {
+ return array(
+ 'url' => '',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, array &$form_state) {
+ $form['url'] = array(
+ '#type' => 'textfield',
+ '#title' => t('URL'),
+ '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like @url.', array('@url' => 'http://drupal.org')),
+ '#default_value' => $this->configuration['url'],
+ '#required' => TRUE,
+ );
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array &$form, array &$form_state) {
+ $this->configuration['url'] = $form_state['values']['url'];
+ }
+
+}
diff --git a/core/modules/action/lib/Drupal/action/Plugin/Action/MessageAction.php b/core/modules/action/lib/Drupal/action/Plugin/Action/MessageAction.php
new file mode 100644
index 00000000000..3bc59b96e44
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/Plugin/Action/MessageAction.php
@@ -0,0 +1,92 @@
+token = $token;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+ return new static($configuration, $plugin_id, $plugin_definition, $container->get('token'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($entity = NULL) {
+ if (empty($this->configuration['node'])) {
+ $this->configuration['node'] = $entity;
+ }
+ $message = $this->token->replace(Xss::filterAdmin($this->configuration['message']), $this->configuration);
+ drupal_set_message($message);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultConfiguration() {
+ return array(
+ 'message' => '',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, array &$form_state) {
+ $form['message'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Message'),
+ '#default_value' => $this->configuration['message'],
+ '#required' => TRUE,
+ '#rows' => '8',
+ '#description' => t('The message to be displayed to the current user. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
+ );
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array &$form, array &$form_state) {
+ $this->configuration['message'] = $form_state['values']['message'];
+ unset($this->configuration['node']);
+ }
+
+}
diff --git a/core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php b/core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php
index 83f93b97c32..3d9f92e7e81 100644
--- a/core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php
+++ b/core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php
@@ -71,11 +71,10 @@ class BulkForm extends BulkFormBase {
*/
protected function getBulkOptions($filtered = TRUE) {
// Get all available actions.
- $actions = action_get_all_actions();
$entity_type = $this->getEntityType();
$options = array();
// Filter the action list.
- foreach ($actions as $id => $action) {
+ foreach ($this->actions as $id => $action) {
if ($filtered) {
$in_selected = in_array($id, $this->options['selected_actions']);
// If the field is configured to include only the selected actions,
@@ -90,8 +89,8 @@ class BulkForm extends BulkFormBase {
}
}
// Only allow actions that are valid for this entity type.
- if (($action['type'] == $entity_type) && empty($action['configurable'])) {
- $options[$id] = $action['label'];
+ if (($action->getType() == $entity_type)) {
+ $options[$id] = $action->label();
}
}
@@ -102,26 +101,13 @@ class BulkForm extends BulkFormBase {
* Implements \Drupal\system\Plugin\views\field\BulkFormBase::views_form_submit().
*/
public function views_form_submit(&$form, &$form_state) {
+ parent::views_form_submit($form, $form_state);
if ($form_state['step'] == 'views_form_views_form') {
- $action = $form_state['values']['action'];
- $action = action_load($action);
- $count = 0;
-
- // Filter only selected checkboxes.
- $selected = array_filter($form_state['values'][$this->options['id']]);
-
- if (!empty($selected)) {
- foreach (array_keys($selected) as $row_index) {
- $entity = $this->get_entity($this->view->result[$row_index]);
- actions_do($action->aid, $entity);
- $entity->save();
- $count++;
- }
- }
-
+ $count = count(array_filter($form_state['values'][$this->options['id']]));
+ $action = $this->actions[$form_state['values']['action']];
if ($count) {
drupal_set_message(format_plural($count, '%action was applied to @count item.', '%action was applied to @count items.', array(
- '%action' => $action->label,
+ '%action' => $action->label(),
)));
}
}
diff --git a/core/modules/action/lib/Drupal/action/Tests/ConfigurationTest.php b/core/modules/action/lib/Drupal/action/Tests/ConfigurationTest.php
index 8df7efaf616..3d62bbe152a 100644
--- a/core/modules/action/lib/Drupal/action/Tests/ConfigurationTest.php
+++ b/core/modules/action/lib/Drupal/action/Tests/ConfigurationTest.php
@@ -42,43 +42,56 @@ class ConfigurationTest extends WebTestBase {
$edit = array();
$edit['action'] = Crypt::hashBase64('action_goto_action');
$this->drupalPost('admin/config/system/actions', $edit, t('Create'));
+ $this->assertResponse(200);
// Make a POST request to the individual action configuration page.
$edit = array();
$action_label = $this->randomName();
- $edit['action_label'] = $action_label;
+ $edit['label'] = $action_label;
+ $edit['id'] = strtolower($action_label);
$edit['url'] = 'admin';
- $this->drupalPost('admin/config/system/actions/configure/' . Crypt::hashBase64('action_goto_action'), $edit, t('Save'));
+ $this->drupalPost('admin/config/system/actions/add/' . Crypt::hashBase64('action_goto_action'), $edit, t('Save'));
+ $this->assertResponse(200);
// Make sure that the new complex action was saved properly.
$this->assertText(t('The action has been successfully saved.'), "Make sure we get a confirmation that we've successfully saved the complex action.");
$this->assertText($action_label, "Make sure the action label appears on the configuration page after we've saved the complex action.");
// Make another POST request to the action edit page.
- $this->clickLink(t('configure'));
- preg_match('|admin/config/system/actions/configure/(\d+)|', $this->getUrl(), $matches);
+ $this->clickLink(t('Configure'));
+ preg_match('|admin/config/system/actions/configure/(.+)|', $this->getUrl(), $matches);
$aid = $matches[1];
$edit = array();
$new_action_label = $this->randomName();
- $edit['action_label'] = $new_action_label;
+ $edit['label'] = $new_action_label;
$edit['url'] = 'admin';
$this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertResponse(200);
// Make sure that the action updated properly.
$this->assertText(t('The action has been successfully saved.'), "Make sure we get a confirmation that we've successfully updated the complex action.");
$this->assertNoText($action_label, "Make sure the old action label does NOT appear on the configuration page after we've updated the complex action.");
$this->assertText($new_action_label, "Make sure the action label appears on the configuration page after we've updated the complex action.");
+ $this->clickLink(t('Configure'));
+ $element = $this->xpath('//input[@type="text" and @value="admin"]');
+ $this->assertTrue(!empty($element), 'Make sure the URL appears when re-editing the action.');
+
// Make sure that deletions work properly.
- $this->clickLink(t('delete'));
+ $this->drupalGet('admin/config/system/actions');
+ $this->clickLink(t('Delete'));
+ $this->assertResponse(200);
$edit = array();
- $this->drupalPost("admin/config/system/actions/delete/$aid", $edit, t('Delete'));
+ $this->drupalPost("admin/config/system/actions/configure/$aid/delete", $edit, t('Delete'));
+ $this->assertResponse(200);
// Make sure that the action was actually deleted.
$this->assertRaw(t('Action %action was deleted', array('%action' => $new_action_label)), 'Make sure that we get a delete confirmation message.');
$this->drupalGet('admin/config/system/actions');
+ $this->assertResponse(200);
$this->assertNoText($new_action_label, "Make sure the action label does not appear on the overview page after we've deleted the action.");
- $exists = db_query('SELECT aid FROM {actions} WHERE callback = :callback', array(':callback' => 'drupal_goto_action'))->fetchField();
- $this->assertFalse($exists, 'Make sure the action is gone from the database after being deleted.');
+
+ $action = entity_load('action', $aid);
+ $this->assertFalse($action, 'Make sure the action is gone after being deleted.');
}
}
diff --git a/core/modules/action/lib/Drupal/action/Tests/LoopTest.php b/core/modules/action/lib/Drupal/action/Tests/LoopTest.php
deleted file mode 100644
index 87fa08e566f..00000000000
--- a/core/modules/action/lib/Drupal/action/Tests/LoopTest.php
+++ /dev/null
@@ -1,82 +0,0 @@
- 'Actions executing in a potentially infinite loop',
- 'description' => 'Tests actions executing in a loop, and makes sure they abort properly.',
- 'group' => 'Action',
- );
- }
-
- /**
- * Sets up a loop with 3 - 12 recursions, and sees if it aborts properly.
- */
- function testActionLoop() {
- $user = $this->drupalCreateUser(array('administer actions'));
- $this->drupalLogin($user);
-
- $info = action_loop_test_action_info();
- $this->aid = action_save('action_loop_test_log', $info['action_loop_test_log']['type'], array(), $info['action_loop_test_log']['label']);
-
- // Delete any existing watchdog messages to clear the plethora of
- // "Action added" messages from when Drupal was installed.
- db_delete('watchdog')->execute();
- // To prevent this test from failing when xdebug is enabled, the maximum
- // recursion level should be kept low enough to prevent the xdebug
- // infinite recursion protection mechanism from aborting the request.
- // See http://drupal.org/node/587634.
- config('action.settings')
- ->set('recursion_limit', 7)
- ->save();
- $this->triggerActions();
- }
-
- /**
- * Loops watchdog messages up to actions_max_stack times.
- *
- * Creates an infinite loop by causing a watchdog message to be set,
- * which causes the actions to be triggered again, up to action_max_stack
- * times.
- */
- protected function triggerActions() {
- $this->drupalGet('', array('query' => array('trigger_action_on_watchdog' => $this->aid)));
- $expected = array();
- $expected[] = 'Triggering action loop';
- $recursion_limit = config('action.settings')->get('recursion_limit');
- for ($i = 1; $i <= $recursion_limit; $i++) {
- $expected[] = "Test log #$i";
- }
- $expected[] = 'Stack overflow: recursion limit for actions_do() has been reached. Stack is limited by %limit calls.';
-
- $result = db_query("SELECT message FROM {watchdog} WHERE type = 'action_loop_test' OR type = 'action' ORDER BY wid");
- $loop_started = FALSE;
- foreach ($result as $row) {
- $expected_message = array_shift($expected);
- $this->assertEqual($row->message, $expected_message, format_string('Expected message %expected, got %message.', array('%expected' => $expected_message, '%message' => $row->message)));
- }
- $this->assertTrue(empty($expected), 'All expected messages found.');
- }
-}
diff --git a/core/modules/action/tests/action_loop_test/action_loop_test.info.yml b/core/modules/action/tests/action_loop_test/action_loop_test.info.yml
deleted file mode 100644
index fefa4ee6123..00000000000
--- a/core/modules/action/tests/action_loop_test/action_loop_test.info.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-name: 'Action loop test'
-type: module
-description: 'Support module for action loop testing.'
-package: Testing
-version: VERSION
-core: 8.x
-hidden: true
-dependencies:
- - action
diff --git a/core/modules/action/tests/action_loop_test/action_loop_test.install b/core/modules/action/tests/action_loop_test/action_loop_test.install
deleted file mode 100644
index 7085aed22d3..00000000000
--- a/core/modules/action/tests/action_loop_test/action_loop_test.install
+++ /dev/null
@@ -1,8 +0,0 @@
- 'watchdog',
- );
- // Fire the actions on the associated object ($log_entry) and the context
- // variable.
- $aids = (array) $_GET['trigger_action_on_watchdog'];
- actions_do($aids, $log_entry, $context);
-}
-
-/**
- * Implements hook_custom_theme().
- *
- * We need to check wheter a loop should be triggered and we do this as early
- * possible, in the first hook that fires.
- */
-function action_loop_test_custom_theme() {
- if (!empty($_GET['trigger_action_on_watchdog'])) {
- watchdog_skip_semaphore('action_loop_test', 'Triggering action loop');
- }
-}
-
-/**
- * Implements hook_action_info().
- */
-function action_loop_test_action_info() {
- return array(
- 'action_loop_test_log' => array(
- 'label' => t('Write a message to the log.'),
- 'type' => 'system',
- 'configurable' => FALSE,
- 'triggers' => array('any'),
- ),
- );
-}
-
-/**
- * Write a message to the log.
- */
-function action_loop_test_log() {
- $count = &drupal_static(__FUNCTION__, 0);
- $count++;
- watchdog_skip_semaphore('action_loop_test', "Test log #$count");
-}
-
-/**
- * Replacement of the watchdog() function that eliminates the use of semaphores
- * so that we can test the abortion of an action loop.
- */
-function watchdog_skip_semaphore($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
- global $user, $base_root;
-
- // Prepare the fields to be logged
- $log_entry = array(
- 'type' => $type,
- 'message' => $message,
- 'variables' => $variables,
- 'severity' => $severity,
- 'link' => $link,
- 'user' => $user,
- 'uid' => isset($user->uid) ? $user->uid : 0,
- 'request_uri' => $base_root . request_uri(),
- 'referer' => $_SERVER['HTTP_REFERER'],
- 'ip' => Drupal::request()->getClientIP(),
- 'timestamp' => REQUEST_TIME,
- );
-
- // Call the logging hooks to log/process the message
- foreach (module_implements('watchdog') as $module) {
- module_invoke($module, 'watchdog', $log_entry);
- }
-}
diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc
index 51158a9b933..a8389de02c2 100644
--- a/core/modules/comment/comment.admin.inc
+++ b/core/modules/comment/comment.admin.inc
@@ -59,7 +59,7 @@ function comment_admin_overview($form, &$form_state, $arg) {
$form['options']['operation'] = array(
'#type' => 'select',
- '#title' => t('Operation'),
+ '#title' => t('Action'),
'#title_display' => 'invisible',
'#options' => $options,
'#default_value' => 'publish',
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 629249f3550..a241923e4a8 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -1805,161 +1805,6 @@ function comment_alphadecimal_to_int($c = '00') {
return base_convert(substr($c, 1), 36, 10);
}
-/**
- * Implements hook_action_info().
- */
-function comment_action_info() {
- return array(
- 'comment_publish_action' => array(
- 'label' => t('Publish comment'),
- 'type' => 'comment',
- 'configurable' => FALSE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
- ),
- 'comment_unpublish_action' => array(
- 'label' => t('Unpublish comment'),
- 'type' => 'comment',
- 'configurable' => FALSE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
- ),
- 'comment_unpublish_by_keyword_action' => array(
- 'label' => t('Unpublish comment containing keyword(s)'),
- 'type' => 'comment',
- 'configurable' => TRUE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
- ),
- 'comment_save_action' => array(
- 'label' => t('Save comment'),
- 'type' => 'comment',
- 'configurable' => FALSE,
- 'triggers' => array('comment_insert', 'comment_update'),
- ),
- );
-}
-
-/**
- * Publishes a comment.
- *
- * @param Drupal\comment\Comment $comment
- * (optional) A comment object to publish.
- * @param array $context
- * Array with components:
- * - 'cid': Comment ID. Required if $comment is not given.
- *
- * @ingroup actions
- */
-function comment_publish_action(Comment $comment = NULL, $context = array()) {
- if (isset($comment->subject->value)) {
- $subject = $comment->subject->value;
- $comment->status->value = COMMENT_PUBLISHED;
- }
- else {
- $cid = $context['cid'];
- $subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid' => $cid))->fetchField();
- db_update('comment')
- ->fields(array('status' => COMMENT_PUBLISHED))
- ->condition('cid', $cid)
- ->execute();
- }
- watchdog('action', 'Published comment %subject.', array('%subject' => $subject));
-}
-
-/**
- * Unpublishes a comment.
- *
- * @param Drupal\comment\Comment|null $comment
- * (optional) A comment object to unpublish.
- * @param array $context
- * Array with components:
- * - 'cid': Comment ID. Required if $comment is not given.
- *
- * @ingroup actions
- */
-function comment_unpublish_action(Comment $comment = NULL, $context = array()) {
- if (isset($comment->subject->value)) {
- $subject = $comment->subject->value;
- $comment->status->value = COMMENT_NOT_PUBLISHED;
- }
- else {
- $cid = $context['cid'];
- $subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid' => $cid))->fetchField();
- db_update('comment')
- ->fields(array('status' => COMMENT_NOT_PUBLISHED))
- ->condition('cid', $cid)
- ->execute();
- }
- watchdog('action', 'Unpublished comment %subject.', array('%subject' => $subject));
-}
-
-/**
- * Unpublishes a comment if it contains certain keywords.
- *
- * @param Drupal\comment\Comment $comment
- * Comment object to modify.
- * @param array $context
- * Array with components:
- * - 'keywords': Keywords to look for. If the comment contains at least one
- * of the keywords, it is unpublished.
- *
- * @ingroup actions
- * @see comment_unpublish_by_keyword_action_form()
- * @see comment_unpublish_by_keyword_action_submit()
- */
-function comment_unpublish_by_keyword_action(Comment $comment, $context) {
- $build = comment_view($comment);
- $text = drupal_render($build);
- foreach ($context['keywords'] as $keyword) {
- if (strpos($text, $keyword) !== FALSE) {
- $comment->status->value = COMMENT_NOT_PUBLISHED;
- watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject->value));
- break;
- }
- }
-}
-
-/**
- * Form constructor for the blacklisted keywords form.
- *
- * @ingroup forms
- * @see comment_unpublish_by_keyword_action()
- * @see comment_unpublish_by_keyword_action_submit()
- */
-function comment_unpublish_by_keyword_action_form($context) {
- $form['keywords'] = array(
- '#title' => t('Keywords'),
- '#type' => 'textarea',
- '#description' => t('The comment will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
- '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
- );
-
- return $form;
-}
-
-/**
- * Form submission handler for comment_unpublish_by_keyword_action_form().
- *
- * @see comment_unpublish_by_keyword_action()
- */
-function comment_unpublish_by_keyword_action_submit($form, $form_state) {
- return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
-}
-
-/**
- * Saves a comment.
- *
- * @param Drupal\comment\Comment $comment
- *
- * @ingroup actions
- */
-function comment_save_action(Comment $comment) {
- comment_save($comment);
- cache_invalidate_tags(array('content' => TRUE));
- watchdog('action', 'Saved comment %title', array('%title' => $comment->subject->value));
-}
-
/**
* Implements hook_ranking().
*/
diff --git a/core/modules/comment/config/action.action.comment_publish_action.yml b/core/modules/comment/config/action.action.comment_publish_action.yml
new file mode 100644
index 00000000000..e29edfa1d06
--- /dev/null
+++ b/core/modules/comment/config/action.action.comment_publish_action.yml
@@ -0,0 +1,6 @@
+id: comment_publish_action
+label: 'Publish comment'
+status: '1'
+langcode: en
+type: comment
+plugin: comment_publish_action
diff --git a/core/modules/comment/config/action.action.comment_save_action.yml b/core/modules/comment/config/action.action.comment_save_action.yml
new file mode 100644
index 00000000000..47f8c3927e0
--- /dev/null
+++ b/core/modules/comment/config/action.action.comment_save_action.yml
@@ -0,0 +1,6 @@
+id: comment_save_action
+label: 'Save comment'
+status: '1'
+langcode: en
+type: comment
+plugin: comment_save_action
diff --git a/core/modules/comment/config/action.action.comment_unpublish_action.yml b/core/modules/comment/config/action.action.comment_unpublish_action.yml
new file mode 100644
index 00000000000..0ac26fd952d
--- /dev/null
+++ b/core/modules/comment/config/action.action.comment_unpublish_action.yml
@@ -0,0 +1,6 @@
+id: comment_unpublish_action
+label: 'Unpublish comment'
+status: '1'
+langcode: en
+type: comment
+plugin: comment_unpublish_action
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Action/PublishComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Action/PublishComment.php
new file mode 100644
index 00000000000..93b9a99a3ea
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Action/PublishComment.php
@@ -0,0 +1,33 @@
+status->value = COMMENT_PUBLISHED;
+ $comment->save();
+ }
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Action/SaveComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Action/SaveComment.php
new file mode 100644
index 00000000000..6127c8d3ceb
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Action/SaveComment.php
@@ -0,0 +1,34 @@
+save();
+ Cache::invalidateTags(array('content' => TRUE));
+ }
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishByKeywordComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishByKeywordComment.php
new file mode 100644
index 00000000000..d123cfcabbe
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishByKeywordComment.php
@@ -0,0 +1,69 @@
+configuration['keywords'] as $keyword) {
+ if (strpos($text, $keyword) !== FALSE) {
+ $comment->status->value = COMMENT_NOT_PUBLISHED;
+ $comment->save();
+ break;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultConfiguration() {
+ return array(
+ 'keywords' => array(),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, array &$form_state) {
+ $form['keywords'] = array(
+ '#title' => t('Keywords'),
+ '#type' => 'textarea',
+ '#description' => t('The comment will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
+ '#default_value' => drupal_implode_tags($this->configuration['keywords']),
+ );
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array &$form, array &$form_state) {
+ $this->configuration['keywords'] = drupal_explode_tags($form_state['values']['keywords']);
+ }
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishComment.php
new file mode 100644
index 00000000000..4857d3efe06
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishComment.php
@@ -0,0 +1,33 @@
+status->value = COMMENT_NOT_PUBLISHED;
+ $comment->save();
+ }
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentActionsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentActionsTest.php
index a942d7cad92..94a1651b0c0 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentActionsTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentActionsTest.php
@@ -36,28 +36,15 @@ class CommentActionsTest extends CommentTestBase {
$subject = $this->randomName();
$comment = $this->postComment($this->node, $comment_text, $subject);
- // Unpublish a comment (direct form: doesn't actually save the comment).
- comment_unpublish_action($comment);
+ // Unpublish a comment.
+ $action = entity_load('action', 'comment_unpublish_action');
+ $action->execute(array($comment));
$this->assertEqual($comment->status->value, COMMENT_NOT_PUBLISHED, 'Comment was unpublished');
- $this->assertWatchdogMessage('Unpublished comment %subject.', array('%subject' => $subject), 'Found watchdog message');
- $this->clearWatchdog();
- // Unpublish a comment (indirect form: modify the comment in the database).
- comment_unpublish_action(NULL, array('cid' => $comment->id()));
- $this->assertEqual(comment_load($comment->id())->status->value, COMMENT_NOT_PUBLISHED, 'Comment was unpublished');
- $this->assertWatchdogMessage('Unpublished comment %subject.', array('%subject' => $subject), 'Found watchdog message');
-
- // Publish a comment (direct form: doesn't actually save the comment).
- comment_publish_action($comment);
+ // Publish a comment.
+ $action = entity_load('action', 'comment_publish_action');
+ $action->execute(array($comment));
$this->assertEqual($comment->status->value, COMMENT_PUBLISHED, 'Comment was published');
- $this->assertWatchdogMessage('Published comment %subject.', array('%subject' => $subject), 'Found watchdog message');
- $this->clearWatchdog();
-
- // Publish a comment (indirect form: modify the comment in the database).
- comment_publish_action(NULL, array('cid' => $comment->id()));
- $this->assertEqual(comment_load($comment->id())->status->value, COMMENT_PUBLISHED, 'Comment was published');
- $this->assertWatchdogMessage('Published comment %subject.', array('%subject' => $subject), 'Found watchdog message');
- $this->clearWatchdog();
}
/**
@@ -67,9 +54,16 @@ class CommentActionsTest extends CommentTestBase {
$this->drupalLogin($this->admin_user);
$keyword_1 = $this->randomName();
$keyword_2 = $this->randomName();
- $aid = action_save('comment_unpublish_by_keyword_action', 'comment', array('keywords' => array($keyword_1, $keyword_2)), $this->randomName());
-
- $this->assertTrue(action_load($aid), 'The action could be loaded.');
+ $action = entity_create('action', array(
+ 'id' => 'comment_unpublish_by_keyword_action',
+ 'label' => $this->randomName(),
+ 'type' => 'comment',
+ 'configuration' => array(
+ 'keywords' => array($keyword_1, $keyword_2),
+ ),
+ 'plugin' => 'comment_unpublish_by_keyword_action',
+ ));
+ $action->save();
$comment = $this->postComment($this->node, $keyword_2, $this->randomName());
@@ -78,29 +72,8 @@ class CommentActionsTest extends CommentTestBase {
$this->assertTrue($comment->status->value == COMMENT_PUBLISHED, 'The comment status was set to published.');
- actions_do($aid, $comment, array());
+ $action->execute(array($comment));
$this->assertTrue($comment->status->value == COMMENT_NOT_PUBLISHED, 'The comment status was set to not published.');
}
- /**
- * Verifies that a watchdog message has been entered.
- *
- * @param $watchdog_message
- * The watchdog message.
- * @param $variables
- * The array of variables passed to watchdog().
- * @param $message
- * The assertion message.
- */
- function assertWatchdogMessage($watchdog_message, $variables, $message) {
- $status = (bool) db_query_range("SELECT 1 FROM {watchdog} WHERE message = :message AND variables = :variables", 0, 1, array(':message' => $watchdog_message, ':variables' => serialize($variables)))->fetchField();
- return $this->assert($status, format_string('@message', array('@message'=> $message)));
- }
-
- /**
- * Clears watchdog.
- */
- function clearWatchdog() {
- db_truncate('watchdog')->execute();
- }
}
diff --git a/core/modules/node/config/action.action.node_delete_action.yml b/core/modules/node/config/action.action.node_delete_action.yml
new file mode 100644
index 00000000000..3adc607c4df
--- /dev/null
+++ b/core/modules/node/config/action.action.node_delete_action.yml
@@ -0,0 +1,6 @@
+id: node_delete_action
+label: 'Delete selected content'
+status: '1'
+langcode: en
+type: node
+plugin: node_delete_action
diff --git a/core/modules/node/config/action.action.node_make_sticky_action.yml b/core/modules/node/config/action.action.node_make_sticky_action.yml
new file mode 100644
index 00000000000..14e731ac999
--- /dev/null
+++ b/core/modules/node/config/action.action.node_make_sticky_action.yml
@@ -0,0 +1,6 @@
+id: node_make_sticky_action
+label: 'Make content sticky'
+status: '1'
+langcode: en
+type: node
+plugin: node_make_sticky_action
diff --git a/core/modules/node/config/action.action.node_make_unsticky_action.yml b/core/modules/node/config/action.action.node_make_unsticky_action.yml
new file mode 100644
index 00000000000..e8e3a2534e3
--- /dev/null
+++ b/core/modules/node/config/action.action.node_make_unsticky_action.yml
@@ -0,0 +1,6 @@
+id: node_make_unsticky_action
+label: 'Make content unsticky'
+status: '1'
+langcode: en
+type: node
+plugin: node_make_unsticky_action
diff --git a/core/modules/node/config/action.action.node_promote_action.yml b/core/modules/node/config/action.action.node_promote_action.yml
new file mode 100644
index 00000000000..3d56d923849
--- /dev/null
+++ b/core/modules/node/config/action.action.node_promote_action.yml
@@ -0,0 +1,6 @@
+id: node_promote_action
+label: 'Promote content to front page'
+status: '1'
+langcode: en
+type: node
+plugin: node_promote_action
diff --git a/core/modules/node/config/action.action.node_publish_action.yml b/core/modules/node/config/action.action.node_publish_action.yml
new file mode 100644
index 00000000000..220a9441e42
--- /dev/null
+++ b/core/modules/node/config/action.action.node_publish_action.yml
@@ -0,0 +1,6 @@
+id: node_publish_action
+label: 'Publish content'
+status: '1'
+langcode: en
+type: node
+plugin: node_publish_action
diff --git a/core/modules/node/config/action.action.node_save_action.yml b/core/modules/node/config/action.action.node_save_action.yml
new file mode 100644
index 00000000000..46472cc84c0
--- /dev/null
+++ b/core/modules/node/config/action.action.node_save_action.yml
@@ -0,0 +1,6 @@
+id: node_save_action
+label: 'Save content'
+status: '1'
+langcode: en
+type: node
+plugin: node_save_action
diff --git a/core/modules/node/config/action.action.node_unpromote_action.yml b/core/modules/node/config/action.action.node_unpromote_action.yml
new file mode 100644
index 00000000000..86d11a7b6fb
--- /dev/null
+++ b/core/modules/node/config/action.action.node_unpromote_action.yml
@@ -0,0 +1,6 @@
+id: node_unpromote_action
+label: 'Remove content from front page'
+status: '1'
+langcode: en
+type: node
+plugin: node_unpromote_action
diff --git a/core/modules/node/config/action.action.node_unpublish_action.yml b/core/modules/node/config/action.action.node_unpublish_action.yml
new file mode 100644
index 00000000000..58530698916
--- /dev/null
+++ b/core/modules/node/config/action.action.node_unpublish_action.yml
@@ -0,0 +1,6 @@
+id: node_unpublish_action
+label: 'Unpublish content'
+status: '1'
+langcode: en
+type: node
+plugin: node_unpublish_action
diff --git a/core/modules/node/lib/Drupal/node/Form/DeleteMultiple.php b/core/modules/node/lib/Drupal/node/Form/DeleteMultiple.php
new file mode 100644
index 00000000000..c11dbd83595
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Form/DeleteMultiple.php
@@ -0,0 +1,126 @@
+tempStoreFactory = $temp_store_factory;
+ $this->storageController = $manager->getStorageController('node');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('user.tempstore'),
+ $container->get('plugin.manager.entity')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormID() {
+ return 'node_multiple_delete_confirm';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getQuestion() {
+ return format_plural(count($this->nodes), 'Are you sure you want to delete this item?', 'Are you sure you want to delete these items?');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getCancelPath() {
+ return 'admin/content';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getConfirmText() {
+ return t('Delete');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, array &$form_state) {
+ $this->nodes = $this->tempStoreFactory->get('node_multiple_delete_confirm')->get($GLOBALS['user']->uid);
+ if (empty($this->nodes)) {
+ drupal_goto($this->getCancelPath());
+ }
+
+ $form['nodes'] = array(
+ '#theme' => 'item_list',
+ '#items' => array_map(function ($node) {
+ return String::checkPlain($node->label());
+ }, $this->nodes),
+ );
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, array &$form_state) {
+ if ($form_state['values']['confirm'] && !empty($this->nodes)) {
+ $this->storageController->delete($this->nodes);
+ $this->tempStoreFactory->get('node_multiple_delete_confirm')->delete($GLOBALS['user']->uid);
+ $count = count($this->nodes);
+ watchdog('content', 'Deleted @count posts.', array('@count' => $count));
+ drupal_set_message(format_plural($count, 'Deleted 1 post.', 'Deleted @count posts.'));
+ }
+ $form_state['redirect'] = 'admin/content';
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/AssignOwnerNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/AssignOwnerNode.php
new file mode 100644
index 00000000000..94d85decaae
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/AssignOwnerNode.php
@@ -0,0 +1,135 @@
+connection = $connection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+ return new static($configuration, $plugin_id, $plugin_definition,
+ $container->get('database')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($entity = NULL) {
+ $entity->uid = $this->configuration['owner_uid'];
+ $entity->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultConfiguration() {
+ return array(
+ 'owner_uid' => '',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, array &$form_state) {
+ $description = t('The username of the user to which you would like to assign ownership.');
+ $count = $this->connection->query("SELECT COUNT(*) FROM {users}")->fetchField();
+ $owner_name = '';
+ if (is_numeric($this->configuration['owner_uid'])) {
+ $owner_name = $this->connection->query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $this->configuration['owner_uid']))->fetchField();
+ }
+
+ // Use dropdown for fewer than 200 users; textbox for more than that.
+ if (intval($count) < 200) {
+ $options = array();
+ $result = $this->connection->query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name");
+ foreach ($result as $data) {
+ $options[$data->name] = $data->name;
+ }
+ $form['owner_name'] = array(
+ '#type' => 'select',
+ '#title' => t('Username'),
+ '#default_value' => $owner_name,
+ '#options' => $options,
+ '#description' => $description,
+ );
+ }
+ else {
+ $form['owner_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Username'),
+ '#default_value' => $owner_name,
+ '#autocomplete_path' => 'user/autocomplete',
+ '#size' => '6',
+ '#maxlength' => '60',
+ '#description' => $description,
+ );
+ }
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(array &$form, array &$form_state) {
+ $exists = (bool) $this->connection->queryRange('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField();
+ if (!$exists) {
+ form_set_error('owner_name', t('Enter a valid username.'));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array &$form, array &$form_state) {
+ $this->configuration['owner_uid'] = $this->connection->query('SELECT uid from {users} WHERE name = :name', array(':name' => $form_state['values']['owner_name']))->fetchField();
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/DeleteNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/DeleteNode.php
new file mode 100644
index 00000000000..f4a5eff0df2
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/DeleteNode.php
@@ -0,0 +1,74 @@
+tempStore = $temp_store_factory->get('node_multiple_delete_confirm');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+ return new static($configuration, $plugin_id, $plugin_definition, $container->get('user.tempstore'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function executeMultiple(array $entities) {
+ $this->tempStore->set($GLOBALS['user']->uid, $entities);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($object = NULL) {
+ $this->executeMultiple(array($object));
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/DemoteNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/DemoteNode.php
new file mode 100644
index 00000000000..7676a765790
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/DemoteNode.php
@@ -0,0 +1,33 @@
+promote = NODE_NOT_PROMOTED;
+ $entity->save();
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/PromoteNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/PromoteNode.php
new file mode 100644
index 00000000000..56f86584c0f
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/PromoteNode.php
@@ -0,0 +1,34 @@
+status = NODE_PUBLISHED;
+ $entity->promote = NODE_PROMOTED;
+ $entity->save();
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/PublishNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/PublishNode.php
new file mode 100644
index 00000000000..132e04503ad
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/PublishNode.php
@@ -0,0 +1,33 @@
+status = NODE_PUBLISHED;
+ $entity->save();
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/SaveNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/SaveNode.php
new file mode 100644
index 00000000000..d374afca531
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/SaveNode.php
@@ -0,0 +1,32 @@
+save();
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/StickyNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/StickyNode.php
new file mode 100644
index 00000000000..a1700670758
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/StickyNode.php
@@ -0,0 +1,34 @@
+status = NODE_PUBLISHED;
+ $entity->sticky = NODE_STICKY;
+ $entity->save();
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishByKeywordNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishByKeywordNode.php
new file mode 100644
index 00000000000..2d0f4ed20b5
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishByKeywordNode.php
@@ -0,0 +1,68 @@
+configuration['keywords'] as $keyword) {
+ $elements = node_view(clone $node);
+ if (strpos(drupal_render($elements), $keyword) !== FALSE || strpos($node->label(), $keyword) !== FALSE) {
+ $node->status = NODE_NOT_PUBLISHED;
+ $node->save();
+ break;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultConfiguration() {
+ return array(
+ 'keywords' => array(),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, array &$form_state) {
+ $form['keywords'] = array(
+ '#title' => t('Keywords'),
+ '#type' => 'textarea',
+ '#description' => t('The content will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
+ '#default_value' => drupal_implode_tags($this->configuration['keywords']),
+ );
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array &$form, array &$form_state) {
+ $this->configuration['keywords'] = drupal_explode_tags($form_state['values']['keywords']);
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishNode.php
new file mode 100644
index 00000000000..968404f9a48
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishNode.php
@@ -0,0 +1,33 @@
+status = NODE_NOT_PUBLISHED;
+ $entity->save();
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/UnstickyNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/UnstickyNode.php
new file mode 100644
index 00000000000..ea930585598
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/UnstickyNode.php
@@ -0,0 +1,33 @@
+sticky = NODE_NOT_STICKY;
+ $entity->save();
+ }
+
+}
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index 39b54f19765..219e92d0bd9 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -31,49 +31,6 @@ function node_configure_rebuild_confirm_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/reports/status';
}
-/**
- * Implements hook_node_operations().
- */
-function node_node_operations() {
- $operations = array(
- 'publish' => array(
- 'label' => t('Publish selected content'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED)),
- ),
- 'unpublish' => array(
- 'label' => t('Unpublish selected content'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('status' => NODE_NOT_PUBLISHED)),
- ),
- 'promote' => array(
- 'label' => t('Promote selected content to front page'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED, 'promote' => NODE_PROMOTED)),
- ),
- 'demote' => array(
- 'label' => t('Demote selected content from front page'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('promote' => NODE_NOT_PROMOTED)),
- ),
- 'sticky' => array(
- 'label' => t('Make selected content sticky'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED, 'sticky' => NODE_STICKY)),
- ),
- 'unsticky' => array(
- 'label' => t('Make selected content not sticky'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('sticky' => NODE_NOT_STICKY)),
- ),
- 'delete' => array(
- 'label' => t('Delete selected content'),
- 'callback' => NULL,
- ),
- );
- return $operations;
-}
-
/**
* Lists node administration filters that can be applied.
*
@@ -446,12 +403,13 @@ function node_admin_nodes() {
'#access' => $admin_access,
);
$options = array();
- foreach (module_invoke_all('node_operations') as $operation => $array) {
- $options[$operation] = $array['label'];
+ $actions = entity_load_multiple_by_properties('action', array('type' => 'node'));
+ foreach ($actions as $id => $action) {
+ $options[$id] = $action->label();
}
$form['options']['operation'] = array(
'#type' => 'select',
- '#title' => t('Operation'),
+ '#title' => t('Action'),
'#title_display' => 'invisible',
'#options' => $options,
'#default_value' => 'approve',
@@ -630,20 +588,13 @@ function node_admin_nodes() {
* @see node_multiple_delete_confirm_submit()
*/
function node_admin_nodes_submit($form, &$form_state) {
- $operations = module_invoke_all('node_operations');
- $operation = $operations[$form_state['values']['operation']];
- // Filter out unchecked nodes
- $nodes = array_filter($form_state['values']['nodes']);
- if ($function = $operation['callback']) {
- // Add in callback arguments if present.
- if (isset($operation['callback arguments'])) {
- $args = array_merge(array($nodes), $operation['callback arguments']);
+ if ($action = entity_load('action', $form_state['values']['operation'])) {
+ $nodes = entity_load_multiple('node', array_filter($form_state['values']['nodes']));
+ $action->execute($nodes);
+ $operation_definition = $action->getPluginDefinition();
+ if (!empty($operation_definition['confirm_form_path'])) {
+ $form_state['redirect'] = $operation_definition['confirm_form_path'];
}
- else {
- $args = array($nodes);
- }
- call_user_func_array($function, $args);
-
cache_invalidate_tags(array('content' => TRUE));
}
else {
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index e390d83f390..8bd1d9a4003 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -401,64 +401,6 @@ function hook_node_grants_alter(&$grants, $account, $op) {
}
}
-/**
- * Add mass node operations.
- *
- * This hook enables modules to inject custom operations into the mass
- * operations dropdown found at admin/content, by associating a callback
- * function with the operation, which is called when the form is submitted. The
- * callback function receives one initial argument, which is an array of the
- * checked nodes.
- *
- * @return
- * An array of operations. Each operation is an associative array that may
- * contain the following key-value pairs:
- * - label: (required) The label for the operation, displayed in the dropdown
- * menu.
- * - callback: (required) The function to call for the operation.
- * - callback arguments: (optional) An array of additional arguments to pass
- * to the callback function.
- */
-function hook_node_operations() {
- $operations = array(
- 'publish' => array(
- 'label' => t('Publish selected content'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED)),
- ),
- 'unpublish' => array(
- 'label' => t('Unpublish selected content'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('status' => NODE_NOT_PUBLISHED)),
- ),
- 'promote' => array(
- 'label' => t('Promote selected content to front page'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED, 'promote' => NODE_PROMOTED)),
- ),
- 'demote' => array(
- 'label' => t('Demote selected content from front page'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('promote' => NODE_NOT_PROMOTED)),
- ),
- 'sticky' => array(
- 'label' => t('Make selected content sticky'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED, 'sticky' => NODE_STICKY)),
- ),
- 'unsticky' => array(
- 'label' => t('Make selected content not sticky'),
- 'callback' => 'node_mass_update',
- 'callback arguments' => array('updates' => array('sticky' => NODE_NOT_STICKY)),
- ),
- 'delete' => array(
- 'label' => t('Delete selected content'),
- 'callback' => NULL,
- ),
- );
- return $operations;
-}
-
/**
* Act before node deletion.
*
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 920679870a7..30161a20168 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -3111,332 +3111,6 @@ function node_content_form(EntityInterface $node, $form_state) {
* @} End of "defgroup node_content".
*/
-/**
- * Implements hook_action_info().
- */
-function node_action_info() {
- return array(
- 'node_publish_action' => array(
- 'type' => 'node',
- 'label' => t('Publish content'),
- 'configurable' => FALSE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
- ),
- 'node_unpublish_action' => array(
- 'type' => 'node',
- 'label' => t('Unpublish content'),
- 'configurable' => FALSE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
- ),
- 'node_make_sticky_action' => array(
- 'type' => 'node',
- 'label' => t('Make content sticky'),
- 'configurable' => FALSE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
- ),
- 'node_make_unsticky_action' => array(
- 'type' => 'node',
- 'label' => t('Make content unsticky'),
- 'configurable' => FALSE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
- ),
- 'node_promote_action' => array(
- 'type' => 'node',
- 'label' => t('Promote content to front page'),
- 'configurable' => FALSE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
- ),
- 'node_unpromote_action' => array(
- 'type' => 'node',
- 'label' => t('Remove content from front page'),
- 'configurable' => FALSE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
- ),
- 'node_assign_owner_action' => array(
- 'type' => 'node',
- 'label' => t('Change the author of content'),
- 'configurable' => TRUE,
- 'behavior' => array('changes_property'),
- 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
- ),
- 'node_save_action' => array(
- 'type' => 'node',
- 'label' => t('Save content'),
- 'configurable' => FALSE,
- 'triggers' => array('comment_insert', 'comment_update', 'comment_delete'),
- ),
- 'node_unpublish_by_keyword_action' => array(
- 'type' => 'node',
- 'label' => t('Unpublish content containing keyword(s)'),
- 'configurable' => TRUE,
- 'triggers' => array('node_presave', 'node_insert', 'node_update'),
- ),
- );
-}
-
-/**
- * Sets the status of a node to 1 (published).
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity.
- * @param $context
- * (optional) Array of additional information about what triggered the action.
- * Not used for this action.
- *
- * @ingroup actions
- */
-function node_publish_action(EntityInterface $node, $context = array()) {
- $node->status = NODE_PUBLISHED;
- watchdog('action', 'Set @type %title to published.', array('@type' => node_get_type_label($node), '%title' => $node->label()));
-}
-
-/**
- * Sets the status of a node to 0 (unpublished).
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity.
- * @param $context
- * (optional) Array of additional information about what triggered the action.
- * Not used for this action.
- *
- * @ingroup actions
- */
-function node_unpublish_action(EntityInterface $node, $context = array()) {
- $node->status = NODE_NOT_PUBLISHED;
- watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_type_label($node), '%title' => $node->label()));
-}
-
-/**
- * Sets the sticky-at-top-of-list property of a node to 1.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity.
- * @param $context
- * (optional) Array of additional information about what triggered the action.
- * Not used for this action.
- *
- * @ingroup actions
- */
-function node_make_sticky_action(EntityInterface $node, $context = array()) {
- $node->sticky = NODE_STICKY;
- watchdog('action', 'Set @type %title to sticky.', array('@type' => node_get_type_label($node), '%title' => $node->label()));
-}
-
-/**
- * Sets the sticky-at-top-of-list property of a node to 0.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity.
- * @param $context
- * (optional) Array of additional information about what triggered the action.
- * Not used for this action.
- *
- * @ingroup actions
- */
-function node_make_unsticky_action(EntityInterface $node, $context = array()) {
- $node->sticky = NODE_NOT_STICKY;
- watchdog('action', 'Set @type %title to unsticky.', array('@type' => node_get_type_label($node), '%title' => $node->label()));
-}
-
-/**
- * Sets the promote property of a node to 1.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity.
- * @param $context
- * (optional) Array of additional information about what triggered the action.
- * Not used for this action.
- *
- * @ingroup actions
- */
-function node_promote_action(EntityInterface $node, $context = array()) {
- $node->promote = NODE_PROMOTED;
- watchdog('action', 'Promoted @type %title to front page.', array('@type' => node_get_type_label($node), '%title' => $node->label()));
-}
-
-/**
- * Sets the promote property of a node to 0.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity.
- * @param $context
- * (optional) Array of additional information about what triggered the action.
- * Not used for this action.
- *
- * @ingroup actions
- */
-function node_unpromote_action(EntityInterface $node, $context = array()) {
- $node->promote = NODE_NOT_PROMOTED;
- watchdog('action', 'Removed @type %title from front page.', array('@type' => node_get_type_label($node), '%title' => $node->label()));
-}
-
-/**
- * Saves a node.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * The node to be saved.
- *
- * @ingroup actions
- */
-function node_save_action(EntityInterface $node) {
- $node->save();
- watchdog('action', 'Saved @type %title', array('@type' => node_get_type_label($node), '%title' => $node->label()));
-}
-
-/**
- * Assigns ownership of a node to a user.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity to modify.
- * @param $context
- * Array of additional information about what triggered the action. Includes
- * the following elements:
- * - owner_uid: User ID to assign to the node.
- *
- * @see node_assign_owner_action_form()
- * @see node_assign_owner_action_validate()
- * @see node_assign_owner_action_submit()
- * @ingroup actions
- */
-function node_assign_owner_action(EntityInterface $node, $context) {
- $node->uid = $context['owner_uid'];
- $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField();
- watchdog('action', 'Changed owner of @type %title to uid %name.', array('@type' => node_get_type_label($node), '%title' => $node->label(), '%name' => $owner_name));
-}
-
-/**
- * Form constructor for the settings form for node_assign_owner_action().
- *
- * @param $context
- * Array of additional information about what triggered the action. Includes
- * the following elements:
- * - owner_uid: User ID to assign to the node.
- *
- * @see node_assign_owner_action_submit()
- * @see node_assign_owner_action_validate()
- * @ingroup forms
- */
-function node_assign_owner_action_form($context) {
- $description = t('The username of the user to which you would like to assign ownership.');
- $count = db_query("SELECT COUNT(*) FROM {users}")->fetchField();
- $owner_name = '';
- if (isset($context['owner_uid'])) {
- $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField();
- }
-
- // Use dropdown for fewer than 200 users; textbox for more than that.
- if (intval($count) < 200) {
- $options = array();
- $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name");
- foreach ($result as $data) {
- $options[$data->name] = $data->name;
- }
- $form['owner_name'] = array(
- '#type' => 'select',
- '#title' => t('Username'),
- '#default_value' => $owner_name,
- '#options' => $options,
- '#description' => $description,
- );
- }
- else {
- $form['owner_name'] = array(
- '#type' => 'textfield',
- '#title' => t('Username'),
- '#default_value' => $owner_name,
- '#autocomplete_path' => 'user/autocomplete',
- '#size' => '6',
- '#maxlength' => '60',
- '#description' => $description,
- );
- }
- return $form;
-}
-
-/**
- * Form validation handler for node_assign_owner_action_form().
- *
- * @see node_assign_owner_action_submit()
- */
-function node_assign_owner_action_validate($form, $form_state) {
- $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField();
- if (!$exists) {
- form_set_error('owner_name', t('Enter a valid username.'));
- }
-}
-
-/**
- * Form submission handler for node_assign_owner_action_form().
- *
- * @see node_assign_owner_action_validate()
- */
-function node_assign_owner_action_submit($form, $form_state) {
- // Username can change, so we need to store the ID, not the username.
- $uid = db_query('SELECT uid from {users} WHERE name = :name', array(':name' => $form_state['values']['owner_name']))->fetchField();
- return array('owner_uid' => $uid);
-}
-
-/**
- * Generates settings form for node_unpublish_by_keyword_action().
- *
- * @param array $context
- * Array of additional information about what triggered this action.
- *
- * @return array
- * A form array.
- *
- * @see node_unpublish_by_keyword_action_submit()
- */
-function node_unpublish_by_keyword_action_form($context) {
- $form['keywords'] = array(
- '#title' => t('Keywords'),
- '#type' => 'textarea',
- '#description' => t('The content will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
- '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
- );
- return $form;
-}
-
-/**
- * Form submission handler for node_unpublish_by_keyword_action().
- */
-function node_unpublish_by_keyword_action_submit($form, $form_state) {
- return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
-}
-
-/**
- * Unpublishes a node containing certain keywords.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity to modify.
- * @param $context
- * Array of additional information about what triggered the action. Includes
- * the following elements:
- * - keywords: Array of keywords. If any keyword is present in the rendered
- * node, the node's status flag is set to unpublished.
- *
- * @see node_unpublish_by_keyword_action_form()
- * @see node_unpublish_by_keyword_action_submit()
- *
- * @ingroup actions
- */
-function node_unpublish_by_keyword_action(EntityInterface $node, $context) {
- foreach ($context['keywords'] as $keyword) {
- $elements = node_view(clone $node);
- if (strpos(drupal_render($elements), $keyword) !== FALSE || strpos($node->label(), $keyword) !== FALSE) {
- $node->status = NODE_NOT_PUBLISHED;
- watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_type_label($node), '%title' => $node->label()));
- break;
- }
- }
-}
-
/**
* Implements hook_requirements().
*/
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
new file mode 100644
index 00000000000..02e887b79bd
--- /dev/null
+++ b/core/modules/node/node.routing.yml
@@ -0,0 +1,6 @@
+node_multiple_delete_confirm:
+ pattern: '/admin/content/node/delete'
+ defaults:
+ _form: '\Drupal\node\Form\DeleteMultiple'
+ requirements:
+ _permission: 'administer nodes'
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestTestForm.php b/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestTestForm.php
index 229211c3669..17ce75f11c9 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestTestForm.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestTestForm.php
@@ -54,7 +54,7 @@ class SimpletestTestForm implements FormInterface {
}
}
- // Operation buttons.
+ // Action buttons.
$form['tests']['op'] = array(
'#type' => 'submit',
'#value' => t('Run tests'),
diff --git a/core/modules/system/lib/Drupal/system/ActionConfigEntityInterface.php b/core/modules/system/lib/Drupal/system/ActionConfigEntityInterface.php
new file mode 100644
index 00000000000..32b41b0ffb9
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/ActionConfigEntityInterface.php
@@ -0,0 +1,38 @@
+getPlugin();
+ // If this plugin has any configuration, ensure that it is set.
+ if ($plugin instanceof ConfigurableActionInterface) {
+ $entity->set('configuration', $plugin->getConfiguration());
+ }
+ }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Action.php b/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Action.php
new file mode 100644
index 00000000000..1264777666e
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Action.php
@@ -0,0 +1,179 @@
+pluginBag = new ActionBag(\Drupal::service('plugin.manager.action'), array($this->plugin), $this->configuration);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPlugin() {
+ return $this->pluginBag->get($this->plugin);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setPlugin($plugin_id) {
+ $this->plugin = $plugin_id;
+ $this->pluginBag->addInstanceID($plugin_id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPluginDefinition() {
+ return $this->getPlugin()->getDefinition();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute(array $entities) {
+ return $this->getPlugin()->executeMultiple($entities);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isConfigurable() {
+ return $this->getPlugin() instanceof ConfigurableActionInterface;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getType() {
+ return $this->type;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function uri() {
+ return array(
+ 'path' => 'admin/config/system/actions/configure/' . $this->id(),
+ 'options' => array(
+ 'entity_type' => $this->entityType,
+ 'entity' => $this,
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function sort($a, $b) {
+ $a_type = $a->getType();
+ $b_type = $b->getType();
+ if ($a_type != $b_type) {
+ return strnatcasecmp($a_type, $b_type);
+ }
+ return parent::sort($a, $b);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getExportProperties() {
+ $properties = parent::getExportProperties();
+ $names = array(
+ 'type',
+ 'plugin',
+ 'configuration',
+ );
+ foreach ($names as $name) {
+ $properties[$name] = $this->get($name);
+ }
+ return $properties;
+ }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkFormBase.php b/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkFormBase.php
index fda3479613c..348085ca014 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkFormBase.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkFormBase.php
@@ -7,15 +7,48 @@
namespace Drupal\system\Plugin\views\field;
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Entity\EntityManager;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\Plugin\views\style\Table;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a generic bulk operation form element.
*/
abstract class BulkFormBase extends FieldPluginBase {
+ /**
+ * An array of actions that can be executed.
+ *
+ * @var array
+ */
+ protected $actions = array();
+
+ /**
+ * Constructs a new BulkForm object.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param array $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Entity\EntityManager $manager
+ * The entity manager.
+ */
+ public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManager $manager) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+ $this->actions = $manager->getStorageController('action')->load();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+ return new static($configuration, $plugin_id, $plugin_definition, $container->get('plugin.manager.entity'));
+ }
+
/**
* Overrides \Drupal\views\Plugin\views\Plugin\field\FieldPluginBase::render().
*/
@@ -104,7 +137,25 @@ abstract class BulkFormBase extends FieldPluginBase {
* @param array $form_state
* An associative array containing the current state of the form.
*/
- abstract public function views_form_submit(&$form, &$form_state);
+ public function views_form_submit(&$form, &$form_state) {
+ if ($form_state['step'] == 'views_form_views_form') {
+ // Filter only selected checkboxes.
+ $selected = array_filter($form_state['values'][$this->options['id']]);
+ $entities = array();
+ foreach (array_intersect_key($this->view->result, $selected) as $row) {
+ $entity = $this->get_entity($row);
+ $entities[$entity->id()] = $entity;
+ }
+
+ $action = $this->actions[$form_state['values']['action']];
+ $action->execute($entities);
+
+ $operation_definition = $action->getPluginDefinition();
+ if (!empty($operation_definition['confirm_form_path'])) {
+ $form_state['confirm_form_path'] = $operation_definition['confirm_form_path'];
+ }
+ }
+ }
/**
* Overrides \Drupal\views\Plugin\views\Plugin\field\FieldPluginBase::query().
diff --git a/core/modules/system/lib/Drupal/system/Tests/Action/ActionUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Action/ActionUnitTest.php
new file mode 100644
index 00000000000..1c08aca1d83
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Action/ActionUnitTest.php
@@ -0,0 +1,86 @@
+ 'Action Plugins',
+ 'description' => 'Tests Action plugins.',
+ 'group' => 'Action',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ $this->actionManager = $this->container->get('plugin.manager.action');
+ $this->installSchema('user', array('users', 'users_roles'));
+ $this->installSchema('system', array('sequences'));
+ }
+
+ /**
+ * Tests the functionality of test actions.
+ */
+ public function testOperations() {
+ // Test that actions can be discovered.
+ $definitions = $this->actionManager->getDefinitions();
+ $this->assertTrue(count($definitions) > 1, 'Action definitions are found.');
+ $this->assertTrue(!empty($definitions['action_test_no_type']), 'The test action is among the definitions found.');
+
+ $definition = $this->actionManager->getDefinition('action_test_no_type');
+ $this->assertTrue(!empty($definition), 'The test action definition is found.');
+
+ $definitions = $this->actionManager->getDefinitionsByType('user');
+ $this->assertTrue(empty($definitions['action_test_no_type']), 'An action with no type is not found.');
+
+ // Create an instance of the 'save entity' action.
+ $action = $this->actionManager->createInstance('action_test_save_entity');
+ $this->assertTrue($action instanceof ActionInterface, 'The action implements the correct interface.');
+
+ // Create a new unsaved user.
+ $name = $this->randomName();
+ $user_storage = $this->container->get('plugin.manager.entity')->getStorageController('user');
+ $account = $user_storage->create(array('name' => $name, 'bundle' => 'user'));
+ $loaded_accounts = $user_storage->load();
+ $this->assertEqual(count($loaded_accounts), 0);
+
+ // Execute the 'save entity' action.
+ $action->execute($account);
+ $loaded_accounts = $user_storage->load();
+ $this->assertEqual(count($loaded_accounts), 1);
+ $account = reset($loaded_accounts);
+ $this->assertEqual($name, $account->label());
+ }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ActionUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ActionUpgradePathTest.php
new file mode 100644
index 00000000000..aa9f26873ba
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ActionUpgradePathTest.php
@@ -0,0 +1,42 @@
+ 'Action upgrade test',
+ 'description' => 'Upgrade tests with action data.',
+ 'group' => 'Upgrade path',
+ );
+ }
+
+ public function setUp() {
+ // Path to the database dump files.
+ $this->databaseDumpFiles = array(
+ drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.bare.minimal.database.php.gz',
+ );
+ parent::setUp();
+ }
+
+ /**
+ * Tests to see if actions were upgrade.
+ */
+ public function testActionUpgrade() {
+ $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
+
+ $this->drupalGet('admin/people');
+ $elements = $this->xpath('//select[@name="operation"]/option');
+ $this->assertTrue(!empty($elements), 'The user actions were upgraded.');
+ }
+
+}
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index def6429221b..f345d2c444e 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -2196,6 +2196,32 @@ function system_update_8056() {
}
}
+/**
+ * Convert actions to configuration.
+ *
+ * @ingroup config_upgrade
+ */
+function system_update_8057() {
+ $actions = db_query("SELECT * FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC);
+ $action_plugins = Drupal::service('plugin.manager.action')->getDefinitions();
+ foreach ($actions as $action) {
+ if (isset($action_plugins[$action['callback']])) {
+ if (is_numeric($action['aid'])) {
+ $action['aid'] = $action['callback'] . '_' . $action['aid'];
+ }
+ $configuration = unserialize($action['parameters']) ?: array();
+ config('action.action.' . $action['aid'])
+ ->set('id', $action['aid'])
+ ->set('label', $action['label'])
+ ->set('status', '1')
+ ->set('type', $action['type'])
+ ->set('plugin', $action['callback'])
+ ->set('configuration', $configuration)
+ ->save();
+ }
+ }
+}
+
/**
* @} End of "defgroup updates-7.x-to-8.x".
* The next series of updates should start at 9000.
diff --git a/core/modules/system/tests/modules/action_test/action_test.info.yml b/core/modules/system/tests/modules/action_test/action_test.info.yml
new file mode 100644
index 00000000000..c639b277cbc
--- /dev/null
+++ b/core/modules/system/tests/modules/action_test/action_test.info.yml
@@ -0,0 +1,7 @@
+name: 'Action test'
+type: module
+description: 'Support module for action testing.'
+package: Testing
+version: VERSION
+core: 8.x
+hidden: true
diff --git a/core/modules/system/tests/modules/action_test/action_test.module b/core/modules/system/tests/modules/action_test/action_test.module
new file mode 100644
index 00000000000..b3d9bbc7f37
--- /dev/null
+++ b/core/modules/system/tests/modules/action_test/action_test.module
@@ -0,0 +1 @@
+save();
+ }
+
+}
diff --git a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php
index 26492f64a02..d2d992cb5cb 100644
--- a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php
+++ b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php
@@ -271,7 +271,7 @@ class TrackerTest extends WebTestBase {
// Unpublish the node and ensure that it's no longer displayed.
$edit = array(
- 'operation' => 'unpublish',
+ 'operation' => 'node_unpublish_action',
'nodes[' . $node->nid . ']' => $node->nid,
);
$this->drupalPost('admin/content', $edit, t('Update'));
diff --git a/core/modules/user/config/action.action.user_block_user_action.yml b/core/modules/user/config/action.action.user_block_user_action.yml
new file mode 100644
index 00000000000..2c4ed88a04e
--- /dev/null
+++ b/core/modules/user/config/action.action.user_block_user_action.yml
@@ -0,0 +1,6 @@
+id: user_block_user_action
+label: 'Block the selected user(s)'
+status: '1'
+langcode: en
+type: user
+plugin: user_block_user_action
diff --git a/core/modules/user/config/action.action.user_cancel_user_action.yml b/core/modules/user/config/action.action.user_cancel_user_action.yml
new file mode 100644
index 00000000000..b69d2d91e6a
--- /dev/null
+++ b/core/modules/user/config/action.action.user_cancel_user_action.yml
@@ -0,0 +1,6 @@
+id: user_cancel_user_action
+label: 'Cancel the selected user account(s)'
+status: '1'
+langcode: en
+type: user
+plugin: user_cancel_user_action
diff --git a/core/modules/user/config/action.action.user_unblock_user_action.yml b/core/modules/user/config/action.action.user_unblock_user_action.yml
new file mode 100644
index 00000000000..20a6fd57b95
--- /dev/null
+++ b/core/modules/user/config/action.action.user_unblock_user_action.yml
@@ -0,0 +1,6 @@
+id: user_unblock_user_action
+label: 'Unblock the selected user(s)'
+status: '1'
+langcode: en
+type: user
+plugin: user_unblock_user_action
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/AddRoleUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/AddRoleUser.php
new file mode 100644
index 00000000000..f1d1f379aed
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/AddRoleUser.php
@@ -0,0 +1,41 @@
+configuration['rid'];
+ // Skip adding the role to the user if they already have it.
+ if ($account !== FALSE && !isset($account->roles[$rid])) {
+ $roles = $account->roles + array($rid => $rid);
+ // For efficiency manually save the original account before applying
+ // any changes.
+ $account->original = clone $account;
+ $account->roles = $roles;
+ $account->save();
+ }
+ }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/BlockUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/BlockUser.php
new file mode 100644
index 00000000000..dcb733bd1ba
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/BlockUser.php
@@ -0,0 +1,39 @@
+status->value == 1) {
+ // For efficiency manually save the original account before applying any
+ // changes.
+ $account->original = clone $account;
+ $account->status = 0;
+ $account->save();
+ }
+ }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/CancelUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/CancelUser.php
new file mode 100644
index 00000000000..58e9e374342
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/CancelUser.php
@@ -0,0 +1,74 @@
+tempStoreFactory = $temp_store_factory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+ return new static($configuration, $plugin_id, $plugin_definition, $container->get('user.tempstore'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function executeMultiple(array $entities) {
+ $this->tempStoreFactory->get('user_user_operations_cancel')->set($GLOBALS['user']->uid, $entities);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($object = NULL) {
+ $this->executeMultiple(array($object));
+ }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/ChangeUserRoleBase.php b/core/modules/user/lib/Drupal/user/Plugin/Action/ChangeUserRoleBase.php
new file mode 100644
index 00000000000..9dfb056f976
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/ChangeUserRoleBase.php
@@ -0,0 +1,49 @@
+ '',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, array &$form_state) {
+ $roles = user_role_names(TRUE);
+ unset($roles[DRUPAL_AUTHENTICATED_RID]);
+ $form['rid'] = array(
+ '#type' => 'radios',
+ '#title' => t('Role'),
+ '#options' => $roles,
+ '#default_value' => $this->configuration['rid'],
+ '#required' => TRUE,
+ );
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array &$form, array &$form_state) {
+ $this->configuration['rid'] = $form_state['values']['rid'];
+ }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/RemoveRoleUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/RemoveRoleUser.php
new file mode 100644
index 00000000000..a2b2616cd3f
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/RemoveRoleUser.php
@@ -0,0 +1,41 @@
+configuration['rid'];
+ // Skip removing the role from the user if they already don't have it.
+ if ($account !== FALSE && isset($account->roles[$rid])) {
+ $roles = array_diff($account->roles, array($rid => $rid));
+ // For efficiency manually save the original account before applying
+ // any changes.
+ $account->original = clone $account;
+ $account->roles = $roles;
+ $account->save();
+ }
+ }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/UnblockUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/UnblockUser.php
new file mode 100644
index 00000000000..616405478cb
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/UnblockUser.php
@@ -0,0 +1,36 @@
+status->value == 0) {
+ $account->status = 1;
+ $account->save();
+ }
+ }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php b/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php
index 0769fff5ec1..ca17c505f36 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php
@@ -73,7 +73,7 @@ class UserAdminTest extends WebTestBase {
$account = user_load($user_c->uid);
$this->assertEqual($account->status, 1, 'User C not blocked');
$edit = array();
- $edit['operation'] = 'block';
+ $edit['operation'] = 'user_block_user_action';
$edit['accounts[' . $account->uid . ']'] = TRUE;
$this->drupalPost('admin/people', $edit, t('Update'));
$account = user_load($user_c->uid, TRUE);
@@ -81,7 +81,7 @@ class UserAdminTest extends WebTestBase {
// Test unblocking of a user from /admin/people page and sending of activation mail
$editunblock = array();
- $editunblock['operation'] = 'unblock';
+ $editunblock['operation'] = 'user_unblock_user_action';
$editunblock['accounts[' . $account->uid . ']'] = TRUE;
$this->drupalPost('admin/people', $editunblock, t('Update'));
$account = user_load($user_c->uid, TRUE);
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php
index 5b622e129f0..38614685f60 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php
@@ -88,7 +88,7 @@ class UserCancelTest extends WebTestBase {
$this->admin_user = $this->drupalCreateUser(array('administer users'));
$this->drupalLogin($this->admin_user);
$edit = array(
- 'operation' => 'cancel',
+ 'operation' => 'user_cancel_user_action',
'accounts[1]' => TRUE,
);
$this->drupalPost('admin/people', $edit, t('Update'));
@@ -408,7 +408,7 @@ class UserCancelTest extends WebTestBase {
// Cancel user accounts, including own one.
$edit = array();
- $edit['operation'] = 'cancel';
+ $edit['operation'] = 'user_cancel_user_action';
foreach ($users as $uid => $account) {
$edit['accounts[' . $uid . ']'] = TRUE;
}
diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc
index 0f199e6e80f..7c04815bd69 100644
--- a/core/modules/user/user.admin.inc
+++ b/core/modules/user/user.admin.inc
@@ -190,12 +190,13 @@ function user_admin_account() {
'#attributes' => array('class' => array('container-inline')),
);
$options = array();
- foreach (module_invoke_all('user_operations') as $operation => $array) {
- $options[$operation] = $array['label'];
+ $actions = entity_load_multiple_by_properties('action', array('type' => 'user'));
+ foreach ($actions as $id => $action) {
+ $options[$id] = $action->label();
}
$form['options']['operation'] = array(
'#type' => 'select',
- '#title' => t('Operation'),
+ '#title' => t('Action'),
'#title_display' => 'invisible',
'#options' => $options,
'#default_value' => 'unblock',
@@ -263,20 +264,13 @@ function user_admin_account() {
* Submit the user administration update form.
*/
function user_admin_account_submit($form, &$form_state) {
- $operations = module_invoke_all('user_operations', $form, $form_state);
- $operation = $operations[$form_state['values']['operation']];
- // Filter out unchecked accounts.
- $accounts = array_filter($form_state['values']['accounts']);
- if ($function = $operation['callback']) {
- // Add in callback arguments if present.
- if (isset($operation['callback arguments'])) {
- $args = array_merge(array($accounts), $operation['callback arguments']);
+ if ($action = entity_load('action', $form_state['values']['operation'])) {
+ $accounts = entity_load_multiple('user', array_filter($form_state['values']['accounts']));
+ $action->execute($accounts);
+ $operation_definition = $action->getPluginDefinition();
+ if (!empty($operation_definition['confirm_form_path'])) {
+ $form_state['redirect'] = $operation_definition['confirm_form_path'];
}
- else {
- $args = array($accounts);
- }
- call_user_func_array($function, $args);
-
drupal_set_message(t('The update has been performed.'));
}
}
diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php
index 51ed678add0..3879b59cf92 100644
--- a/core/modules/user/user.api.php
+++ b/core/modules/user/user.api.php
@@ -205,40 +205,6 @@ function hook_user_format_name_alter(&$name, $account) {
}
}
-/**
- * Add mass user operations.
- *
- * This hook enables modules to inject custom operations into the mass operations
- * dropdown found at admin/people, by associating a callback function with
- * the operation, which is called when the form is submitted. The callback function
- * receives one initial argument, which is an array of the checked users.
- *
- * @return
- * An array of operations. Each operation is an associative array that may
- * contain the following key-value pairs:
- * - "label": Required. The label for the operation, displayed in the dropdown menu.
- * - "callback": Required. The function to call for the operation.
- * - "callback arguments": Optional. An array of additional arguments to pass to
- * the callback function.
- *
- */
-function hook_user_operations() {
- $operations = array(
- 'unblock' => array(
- 'label' => t('Unblock the selected users'),
- 'callback' => 'user_user_operations_unblock',
- ),
- 'block' => array(
- 'label' => t('Block the selected users'),
- 'callback' => 'user_user_operations_block',
- ),
- 'cancel' => array(
- 'label' => t('Cancel the selected user accounts'),
- ),
- );
- return $operations;
-}
-
/**
* Act on a user account being inserted or updated.
*
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 593d35a259a..377da0970d5 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -9,6 +9,7 @@ use Drupal\file\Plugin\Core\Entity\File;
use Drupal\user\Plugin\Core\Entity\User;
use Drupal\user\UserInterface;
use Drupal\user\UserRole;
+use Drupal\user\RoleInterface;
use Drupal\Core\Template\Attribute;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\menu_link\Plugin\Core\Entity\MenuLink;
@@ -986,6 +987,13 @@ function user_menu() {
'access arguments' => array('administer users'),
'type' => MENU_LOCAL_ACTION,
);
+ $items['admin/people/cancel'] = array(
+ 'title' => 'Cancel user',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('user_multiple_cancel_confirm'),
+ 'access arguments' => array('administer users'),
+ 'file' => 'user.admin.inc',
+ );
// Administration pages.
$items['admin/config/people'] = array(
@@ -1832,6 +1840,55 @@ function user_role_names($membersonly = FALSE, $permission = NULL) {
}, user_roles($membersonly, $permission));
}
+/**
+ * Implements hook_user_role_insert().
+ */
+function user_user_role_insert(RoleInterface $role) {
+ // Ignore the authenticated and anonymous roles.
+ if (in_array($role->id(), array(DRUPAL_AUTHENTICATED_RID, DRUPAL_ANONYMOUS_RID))) {
+ return;
+ }
+
+ $action = entity_create('action', array(
+ 'id' => 'user_add_role_action.' . $role->id(),
+ 'type' => 'user',
+ 'label' => t('Add the @label role to the selected users', array('@label' => $role->label())),
+ 'configuration' => array(
+ 'rid' => $role->id(),
+ ),
+ 'plugin' => 'user_add_role_action',
+ ));
+ $action->save();
+ $action = entity_create('action', array(
+ 'id' => 'user_remove_role_action.' . $role->id(),
+ 'type' => 'user',
+ 'label' => t('Remove the @label role from the selected users', array('@label' => $role->label())),
+ 'configuration' => array(
+ 'rid' => $role->id(),
+ ),
+ 'plugin' => 'user_remove_role_action',
+ ));
+ $action->save();
+}
+
+/**
+ * Implements hook_user_role_delete().
+ */
+function user_user_role_delete(RoleInterface $role) {
+ // Ignore the authenticated and anonymous roles.
+ if (in_array($role->id(), array(DRUPAL_AUTHENTICATED_RID, DRUPAL_ANONYMOUS_RID))) {
+ return;
+ }
+
+ $actions = entity_load_multiple('action', array(
+ 'user_add_role_action.' . $role->id(),
+ 'user_remove_role_action.' . $role->id(),
+ ));
+ foreach ($actions as $action) {
+ $action->delete();
+ }
+}
+
/**
* Retrieve an array of roles matching specified conditions.
*
@@ -2005,149 +2062,14 @@ function user_role_revoke_permissions($rid, array $permissions = array()) {
drupal_static_reset('user_role_permissions');
}
-/**
- * Implements hook_user_operations().
- */
-function user_user_operations($form = array(), $form_state = array()) {
- $operations = array(
- 'unblock' => array(
- 'label' => t('Unblock the selected users'),
- 'callback' => 'user_user_operations_unblock',
- ),
- 'block' => array(
- 'label' => t('Block the selected users'),
- 'callback' => 'user_user_operations_block',
- ),
- 'cancel' => array(
- 'label' => t('Cancel the selected user accounts'),
- ),
- );
-
- if (user_access('administer permissions')) {
- $roles = user_role_names(TRUE);
- unset($roles[DRUPAL_AUTHENTICATED_RID]); // Can't edit authenticated role.
-
- $add_roles = array();
- foreach ($roles as $key => $value) {
- $add_roles['add_role-' . $key] = $value;
- }
-
- $remove_roles = array();
- foreach ($roles as $key => $value) {
- $remove_roles['remove_role-' . $key] = $value;
- }
-
- if (count($roles)) {
- $role_operations = array(
- t('Add a role to the selected users') => array(
- 'label' => $add_roles,
- ),
- t('Remove a role from the selected users') => array(
- 'label' => $remove_roles,
- ),
- );
-
- $operations += $role_operations;
- }
- }
-
- // If the form has been posted, we need to insert the proper data for
- // role editing if necessary.
- if (!empty($form_state['submitted'])) {
- $operation_rid = explode('-', $form_state['values']['operation']);
- $operation = $operation_rid[0];
- if ($operation == 'add_role' || $operation == 'remove_role') {
- $rid = $operation_rid[1];
- if (user_access('administer permissions')) {
- $operations[$form_state['values']['operation']] = array(
- 'callback' => 'user_multiple_role_edit',
- 'callback arguments' => array($operation, $rid),
- );
- }
- else {
- watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
- return;
- }
- }
- }
-
- return $operations;
-}
-
-/**
- * Callback function for admin mass unblocking users.
- */
-function user_user_operations_unblock($accounts) {
- $accounts = user_load_multiple($accounts);
- foreach ($accounts as $account) {
- // Skip unblocking user if they are already unblocked.
- if ($account !== FALSE && $account->status == 0) {
- $account->status = 1;
- $account->save();
- }
- }
-}
-
-/**
- * Callback function for admin mass blocking users.
- */
-function user_user_operations_block($accounts) {
- $accounts = user_load_multiple($accounts);
- foreach ($accounts as $account) {
- // Skip blocking user if they are already blocked.
- if ($account !== FALSE && $account->status == 1) {
- // For efficiency manually save the original account before applying any
- // changes.
- $account->original = clone $account;
- $account->status = 0;
- $account->save();
- }
- }
-}
-
-/**
- * Callback function for admin mass adding/deleting a user role.
- */
-function user_multiple_role_edit($accounts, $operation, $rid) {
- $role_name = entity_load('user_role', $rid)->label();
-
- switch ($operation) {
- case 'add_role':
- $accounts = user_load_multiple($accounts);
- foreach ($accounts as $account) {
- // Skip adding the role to the user if they already have it.
- if ($account !== FALSE && !in_array($rid, $account->roles)) {
- // For efficiency manually save the original account before applying
- // any changes.
- $account->original = clone $account;
- $account->roles[] = $rid;
- $account->save();
- }
- }
- break;
- case 'remove_role':
- $accounts = user_load_multiple($accounts);
- foreach ($accounts as $account) {
- // Skip removing the role from the user if they already don't have it.
- if ($account !== FALSE && in_array($rid, $account->roles)) {
- $roles = array_diff($account->roles, array($rid));
- // For efficiency manually save the original account before applying
- // any changes.
- $account->original = clone $account;
- $account->roles = $roles;
- $account->save();
- }
- }
- break;
- }
-}
-
function user_multiple_cancel_confirm($form, &$form_state) {
$edit = $form_state['input'];
+ // Retrieve the accounts to be canceled from the temp store.
+ $accounts = Drupal::service('user.tempstore')->get('user_user_operations_cancel')->get($GLOBALS['user']->uid);
$form['accounts'] = array('#prefix' => '', '#tree' => TRUE);
- $accounts = user_load_multiple(array_keys(array_filter($edit['accounts'])));
- foreach ($accounts as $uid => $account) {
+ foreach ($accounts as $account) {
+ $uid = $account->id();
// Prevent user 1 from being canceled.
if ($uid <= 1) {
continue;
@@ -2156,14 +2078,14 @@ function user_multiple_cancel_confirm($form, &$form_state) {
'#type' => 'hidden',
'#value' => $uid,
'#prefix' => '',
- '#suffix' => check_plain($account->name) . "\n",
+ '#suffix' => check_plain($account->name->value) . "\n",
);
}
// Output a notice that user 1 cannot be canceled.
if (isset($accounts[1])) {
$redirect = (count($accounts) == 1);
- $message = t('The user account %name cannot be cancelled.', array('%name' => $accounts[1]->name));
+ $message = t('The user account %name cannot be cancelled.', array('%name' => $accounts[1]->name->value));
drupal_set_message($message, $redirect ? 'error' : 'warning');
// If only user 1 was selected, redirect to the overview.
if ($redirect) {
@@ -2211,6 +2133,8 @@ function user_multiple_cancel_confirm($form, &$form_state) {
function user_multiple_cancel_confirm_submit($form, &$form_state) {
global $user;
+ // Clear out the accounts from the temp store.
+ Drupal::service('user.tempstore')->get('user_user_operations_cancel')->delete($user->uid);
if ($form_state['values']['confirm']) {
foreach ($form_state['values']['accounts'] as $uid => $value) {
// Prevent programmatic form submissions from cancelling user 1.
@@ -2509,44 +2433,6 @@ function user_node_load($nodes, $types) {
}
}
-/**
- * Implements hook_action_info().
- */
-function user_action_info() {
- return array(
- 'user_block_user_action' => array(
- 'label' => t('Block current user'),
- 'type' => 'user',
- 'configurable' => FALSE,
- 'triggers' => array('any'),
- ),
- );
-}
-
-/**
- * Blocks the current user.
- *
- * @ingroup actions
- */
-function user_block_user_action(&$entity, $context = array()) {
- // First priority: If there is a $entity->uid, block that user.
- // This is most likely a user object or the author if a node or comment.
- if (isset($entity->uid)) {
- $uid = $entity->uid;
- }
- elseif (isset($context['uid'])) {
- $uid = $context['uid'];
- }
- // If neither of those are valid, then block the current user.
- else {
- $uid = $GLOBALS['user']->uid;
- }
- $account = user_load($uid);
- $account->status = 0;
- $account->save();
- watchdog('action', 'Blocked user %name.', array('%name' => $account->name));
-}
-
/**
* Implements hook_form_FORM_ID_alter() for 'field_ui_field_instance_edit_form'.
*
diff --git a/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php b/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php
index 5768aa94d72..7e154ce600c 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php
@@ -8,7 +8,7 @@
namespace Drupal\views\Plugin;
use Drupal\Component\Plugin\PluginManagerBase;
-use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Core\Plugin\Factory\ContainerFactory;
use Drupal\Core\Plugin\Discovery\CacheDecorator;
use Drupal\views\Plugin\Discovery\ViewsHandlerDiscovery;
@@ -30,7 +30,7 @@ class ViewsHandlerManager extends PluginManagerBase {
$this->discovery = new ViewsHandlerDiscovery($type, $namespaces);
$this->discovery = new CacheDecorator($this->discovery, "views:$type", 'views_info');
- $this->factory = new DefaultFactory($this->discovery);
+ $this->factory = new ContainerFactory($this);
}
}