Commit 2edfc475 authored by webchick's avatar webchick

Issue #1008166 by sun, catch, rootatwc, Rob Loach, Jody Lynn: Actions should be a module.

parent f846c6c1
......@@ -3190,7 +3190,7 @@ function drupal_classloader_register($name, $path) {
* // $stack tracks the number of recursive calls.
* static $stack;
* $stack++;
* if ($stack > variable_get('actions_max_stack', 35)) {
* if ($stack > variable_get('action_max_stack', 35)) {
* ...
* return;
* }
......
......@@ -4871,7 +4871,6 @@ function _drupal_bootstrap_code() {
require_once DRUPAL_ROOT . '/core/includes/image.inc';
require_once DRUPAL_ROOT . '/core/includes/form.inc';
require_once DRUPAL_ROOT . '/core/includes/mail.inc';
require_once DRUPAL_ROOT . '/core/includes/actions.inc';
require_once DRUPAL_ROOT . '/core/includes/ajax.inc';
require_once DRUPAL_ROOT . '/core/includes/token.inc';
require_once DRUPAL_ROOT . '/core/includes/errors.inc';
......
<?php
/**
* @file
* Admin page callbacks for the Action module.
*/
/**
* Menu callback; Displays an overview of available and configured actions.
*/
function action_admin_manage() {
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 = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchField();
$header = array(
array('data' => t('Action type'), 'field' => 'type'),
array('data' => t('Label'), 'field' => 'label'),
array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
);
$query = db_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(
array('data' => $action->type),
array('data' => check_plain($action->label)),
array('data' => $action->parameters ? l(t('configure'), "admin/config/system/actions/configure/$action->aid") : ''),
array('data' => $action->parameters ? l(t('delete'), "admin/config/system/actions/delete/$action->aid") : '')
);
}
if ($row) {
$pager = theme('pager');
if (!empty($pager)) {
$row[] = array(array('data' => $pager, 'colspan' => '3'));
}
$build['action_header'] = array('#markup' => '<h3>' . t('Available actions:') . '</h3>');
$build['action_table'] = array('#markup' => theme('table', array('header' => $header, 'rows' => $row)));
}
if ($actions_map) {
$build['action_admin_manage_form'] = drupal_get_form('action_admin_manage_form', $options);
}
return $build;
}
/**
* Define the form for the actions overview page.
*
* @param $form_state
* An associative array containing the current state of the form; not used.
* @param $options
* An array of configurable actions.
* @return
* Form definition.
*
* @ingroup forms
* @see action_admin_manage_form_submit()
*/
function action_admin_manage_form($form, &$form_state, $options = array()) {
$form['parent'] = array(
'#type' => 'fieldset',
'#title' => t('Create an advanced action'),
'#attributes' => array('class' => array('container-inline')),
);
$form['parent']['action'] = array(
'#type' => 'select',
'#title' => t('Action'),
'#title_display' => 'invisible',
'#options' => $options,
'#empty_option' => t('Choose an advanced action'),
);
$form['parent']['actions'] = array('#type' => 'actions');
$form['parent']['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Create'),
);
return $form;
}
/**
* Form submission handler for action_admin_manage_form().
*/
function action_admin_manage_form_submit($form, &$form_state) {
if ($form_state['values']['action']) {
$form_state['redirect'] = 'admin/config/system/actions/configure/' . $form_state['values']['action'];
}
}
/**
* Form constructor for the configuration of a single action.
*
* We provide the "Description" field. The rest of the form is provided by the
* action. We then provide the Save button. Because we are combining unknown
* form elements with the action configuration form, we use an 'action_' prefix
* on our elements.
*
* @param $action
* Hash of an action ID or an integer. If it is a hash, we are
* creating a new instance. If it is an integer, we are editing an existing
* instance.
*
* @see action_admin_configure_validate()
* @see action_admin_configure_submit()
* @ingroup forms
*/
function action_admin_configure($form, &$form_state, $action = NULL) {
if ($action === NULL) {
drupal_goto('admin/config/system/actions');
}
$actions_map = action_actions_map(action_list());
$edit = array();
// Numeric action denotes saved instance of a configurable action.
if (is_numeric($action)) {
$aid = $action;
// Load stored parameter values from database.
$data = db_query("SELECT * FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetch();
$edit['action_label'] = $data->label;
$edit['action_type'] = $data->type;
$function = $data->callback;
$action = drupal_hash_base64($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;
}
/**
* Form validation handler for action_admin_configure().
*
* @see action_admin_configure_submit()
*/
function action_admin_configure_validate($form, &$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);
}
}
/**
* Form submission handler for action_admin_configure().
*
* @see action_admin_configure_validate()
*/
function action_admin_configure_submit($form, &$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/manage';
}
/**
* Creates the form for confirmation of deleting an action.
*
* @see action_admin_delete_form_submit()
* @ingroup forms
*/
function action_admin_delete_form($form, &$form_state, $action) {
$form['aid'] = array(
'#type' => 'hidden',
'#value' => $action->aid,
);
return confirm_form($form,
t('Are you sure you want to delete the action %action?', array('%action' => $action->label)),
'admin/config/system/actions/manage',
t('This cannot be undone.'),
t('Delete'),
t('Cancel')
);
}
/**
* Form submission handler for action_admin_delete_form().
*/
function action_admin_delete_form_submit($form, &$form_state) {
$aid = $form_state['values']['aid'];
$action = action_load($aid);
action_delete($aid);
watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $action->label));
drupal_set_message(t('Action %action was deleted', array('%action' => $action->label)));
$form_state['redirect'] = 'admin/config/system/actions/manage';
}
/**
* Post-deletion operations for deleting action orphans.
*
* @param $orphaned
* An array of orphaned actions.
*/
function action_admin_delete_orphans_post($orphaned) {
foreach ($orphaned as $callback) {
drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
}
}
/**
* Removes actions that are in the database but not supported by any enabled module.
*/
function action_admin_remove_orphans() {
action_synchronize(TRUE);
drupal_goto('admin/config/system/actions/manage');
}
<?php
/**
* @file
* 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')
->condition('aid', $aid)
->execute();
}
name = Actions
description = Perform tasks on specific events triggered within the system.
package = Core
version = VERSION
core = 8.x
configure = admin/config/system/actions
<?php
/**
* @file
* Install, update and uninstall functions for the Actions module.
*/
/**
* Implements hook_schema().
*/
function action_schema() {
// 'action' is a reserved SQL keyword.
$schema['actions'] = array(
'description' => 'Stores action information.',
'fields' => array(
'aid' => array(
'description' => 'Primary Key: Unique action ID.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '0',
),
'type' => array(
'description' => 'The object that that action acts on (node, user, comment, system or custom types.)',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'callback' => array(
'description' => 'The callback function that executes when the action runs.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'parameters' => array(
'description' => 'Parameters to be passed to the callback function.',
'type' => 'blob',
'not null' => TRUE,
'size' => 'big',
),
'label' => array(
'description' => 'Label of the action.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '0',
),
),
'primary key' => array('aid'),
);
return $schema;
}
......@@ -2,10 +2,10 @@
/**
* @file
* Definition of Drupal\system\Tests\Actions\ConfigurationTest.
* Definition of Drupal\action\Tests\ConfigurationTest.
*/
namespace Drupal\system\Tests\Actions;
namespace Drupal\action\Tests;
use Drupal\simpletest\WebTestBase;
......@@ -13,11 +13,19 @@
* Actions configuration.
*/
class ConfigurationTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('action');
public static function getInfo() {
return array(
'name' => 'Actions configuration',
'description' => 'Tests complex actions configuration by adding, editing, and deleting a complex action.',
'group' => 'Actions',
'group' => 'Action',
);
}
......@@ -32,15 +40,15 @@ function testActionConfiguration() {
// Make a POST request to admin/config/system/actions/manage.
$edit = array();
$edit['action'] = drupal_hash_base64('system_goto_action');
$edit['action'] = drupal_hash_base64('action_goto_action');
$this->drupalPost('admin/config/system/actions/manage', $edit, t('Create'));
// Make a POST request to the individual action configuration page.
$edit = array();
$action_label = $this->randomName();
$edit['actions_label'] = $action_label;
$edit['action_label'] = $action_label;
$edit['url'] = 'admin';
$this->drupalPost('admin/config/system/actions/configure/' . drupal_hash_base64('system_goto_action'), $edit, t('Save'));
$this->drupalPost('admin/config/system/actions/configure/' . drupal_hash_base64('action_goto_action'), $edit, t('Save'));
// Make sure that the new complex action was saved properly.
$this->assertText(t('The action has been successfully saved.'), t("Make sure we get a confirmation that we've successfully saved the complex action."));
......@@ -52,7 +60,7 @@ function testActionConfiguration() {
$aid = $matches[1];
$edit = array();
$new_action_label = $this->randomName();
$edit['actions_label'] = $new_action_label;
$edit['action_label'] = $new_action_label;
$edit['url'] = 'admin';
$this->drupalPost(NULL, $edit, t('Save'));
......
......@@ -2,10 +2,10 @@
/**
* @file
* Definition of Drupal\system\Tests\Actions\LoopTest.
* Definition of Drupal\action\Tests\LoopTest.
*/
namespace Drupal\system\Tests\Actions;
namespace Drupal\action\Tests;
use Drupal\simpletest\WebTestBase;
......@@ -19,7 +19,7 @@ class LoopTest extends WebTestBase {
*
* @var array
*/
public static $modules = array('dblog', 'actions_loop_test');
public static $modules = array('dblog', 'action_loop_test');
protected $aid;
......@@ -27,7 +27,7 @@ public static function getInfo() {
return array(
'name' => 'Actions executing in a potentially infinite loop',
'description' => 'Tests actions executing in a loop, and makes sure they abort properly.',
'group' => 'Actions',
'group' => 'Action',
);
}
......@@ -38,8 +38,8 @@ function testActionLoop() {
$user = $this->drupalCreateUser(array('administer actions'));
$this->drupalLogin($user);
$info = actions_loop_test_action_info();
$this->aid = actions_save('actions_loop_test_log', $info['actions_loop_test_log']['type'], array(), $info['actions_loop_test_log']['label']);
$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.
......@@ -48,25 +48,25 @@ function testActionLoop() {
// 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.
variable_set('actions_max_stack', 7);
variable_set('action_max_stack', 7);
$this->triggerActions();
}
/**
* Create an infinite loop by causing a watchdog message to be set,
* which causes the actions to be triggered again, up to actions_max_stack
* which causes the actions to be triggered again, up to action_max_stack
* times.
*/
protected function triggerActions() {
$this->drupalGet('<front>', array('query' => array('trigger_actions_on_watchdog' => $this->aid)));
$this->drupalGet('<front>', array('query' => array('trigger_action_on_watchdog' => $this->aid)));
$expected = array();
$expected[] = 'Triggering action loop';
for ($i = 1; $i <= variable_get('actions_max_stack', 35); $i++) {
for ($i = 1; $i <= variable_get('action_max_stack', 35); $i++) {
$expected[] = "Test log #$i";
}
$expected[] = 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.';
$result = db_query("SELECT message FROM {watchdog} WHERE type = 'actions_loop_test' OR type = 'actions' ORDER BY wid");
$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);
......
name = Actions loop test
name = Action loop test
description = Support module for action loop testing.
package = Testing
version = VERSION
core = 8.x
hidden = TRUE
dependencies[] = action
......@@ -3,9 +3,9 @@
/**
* Implements hook_install().
*/
function actions_loop_test_install() {
function action_loop_test_install() {
db_update('system')
->fields(array('weight' => 1))
->condition('name', 'actions_loop_test')
->condition('name', 'action_loop_test')
->execute();
}
......@@ -3,9 +3,9 @@
/**
* Implements hook_watchdog().
*/
function actions_loop_test_watchdog(array $log_entry) {
function action_loop_test_watchdog(array $log_entry) {
// If the triggering actions are not explicitly enabled, abort.
if (empty($_GET['trigger_actions_on_watchdog'])) {
if (empty($_GET['trigger_action_on_watchdog'])) {
return;
}
// We can pass in any applicable information in $context. There isn't much in
......@@ -15,25 +15,25 @@ function actions_loop_test_watchdog(array $log_entry) {
);
// Fire the actions on the associated object ($log_entry) and the context
// variable.
$aids = (array) $_GET['trigger_actions_on_watchdog'];
$aids = (array) $_GET['trigger_action_on_watchdog'];
actions_do($aids, $log_entry, $context);
}
/**
* Implements hook_init().
*/
function actions_loop_test_init() {
if (!empty($_GET['trigger_actions_on_watchdog'])) {
watchdog_skip_semaphore('actions_loop_test', 'Triggering action loop');
function action_loop_test_init() {
if (!empty($_GET['trigger_action_on_watchdog'])) {
watchdog_skip_semaphore('action_loop_test', 'Triggering action loop');
}
}
/**
* Implements hook_action_info().
*/
function actions_loop_test_action_info() {
function action_loop_test_action_info() {
return array(
'actions_loop_test_log' => array(
'action_loop_test_log' => array(
'label' => t('Write a message to the log.'),
'type' => 'system',
'configurable' => FALSE,
......@@ -45,10 +45,10 @@ function actions_loop_test_action_info() {
/**
* Write a message to the log.
*/
function actions_loop_test_log() {
function action_loop_test_log() {
$count = &drupal_static(__FUNCTION__, 0);
$count++;
watchdog_skip_semaphore('actions_loop_test', "Test log #$count");
watchdog_skip_semaphore('action_loop_test', "Test log #$count");
}
/**
......
......@@ -2834,281 +2834,6 @@ function system_add_date_formats_form_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/config/regional/date-time/formats';
}
/**
* Menu callback; Displays an overview of available and configured actions.
*/
function system_actions_manage() {
actions_synchronize();
$actions = actions_list();
$actions_map = actions_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 = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchField();
$header = array(
array('data' => t('Action type'), 'field' => 'type'),
array('data' => t('Label'), 'field' => 'label'),
array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
);
$query = db_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(
array('data' => $action->type),
array('data' => check_plain($action->label)),
array('data' => $ac