summaryrefslogtreecommitdiff
path: root/includes/actions.inc
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2008-06-29 11:37:34 +0000
committerDries Buytaert <dries@buytaert.net>2008-06-29 11:37:34 +0000
commit77f4c3974962118e4cfa8fc25a887d1a84ff8e5e (patch)
treef43b771aeb0cd3fea57b56422736c8af64255b21 /includes/actions.inc
parent3c81924d7edf55efdda261dd401245e43e87096c (diff)
downloadbrdo-77f4c3974962118e4cfa8fc25a887d1a84ff8e5e.tar.gz
brdo-77f4c3974962118e4cfa8fc25a887d1a84ff8e5e.tar.bz2
- Patch #266406 by cwgordon7 and catch: initial tests for (configuring complex) actions.
Diffstat (limited to 'includes/actions.inc')
-rw-r--r--includes/actions.inc414
1 files changed, 58 insertions, 356 deletions
diff --git a/includes/actions.inc b/includes/actions.inc
index 27f7bdf27..fc829a8e1 100644
--- a/includes/actions.inc
+++ b/includes/actions.inc
@@ -1,363 +1,65 @@
<?php
-// $Id$
-/**
- * @file
- * This is the actions engine for executing stored actions.
- */
-
-/**
- * Perform a given list of actions by executing their callback functions.
- *
- * Given the IDs of actions to perform, find out what the callbacks
- * for the actions are by querying the database. Then call each callback
- * using the function call $function($object, $context, $a1, $2)
- * where $function is the name of a function written in compliance with
- * the action specification; that is, foo($object, $context).
- *
- * @param $action_ids
- * The ID of the action to perform. Can be a single action ID or an array
- * of IDs. IDs of instances will be numeric; IDs of singletons will be
- * function names.
- * @param $object
- * Parameter that will be passed along to the callback. Typically the
- * object that the action will act on; a node, user or comment object.
- * @param $context
- * Parameter that will be passed along to the callback. $context is a
- * keyed array containing extra information about what is currently
- * happening at the time of the call. Typically $context['hook'] and
- * $context['op'] will tell which hook-op combination resulted in this
- * call to actions_do().
- * @param $a1
- * Parameter that will be passed along to the callback.
- * @param $a2
- * Parameter that will be passed along to the callback.
- *
- * @return
- * An associative array containing the result of the function that
- * performs the action, keyed on action ID.
- */
-function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) {
- static $stack;
- $stack++;
- if ($stack > variable_get('actions_max_stack', 35)) {
- watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
- return;
- }
- $actions = array();
- $available_actions = actions_list();
- $result = array();
- if (is_array($action_ids)) {
- $where = array();
- $where_values = array();
- foreach ($action_ids as $action_id) {
- if (is_numeric($action_id)) {
- $where[] = 'OR aid = %d';
- $where_values[] = $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 ($where) {
- $where_clause = implode(' ', $where);
- // Strip off leading 'OR '.
- $where_clause = '(' . strstr($where_clause, " ") . ')';
- $result_db = db_query('SELECT * FROM {actions} WHERE ' . $where_clause, $where_values);
- while ($action = db_fetch_object($result_db)) {
- $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) {
- if (is_numeric($action_id)) { // Configurable actions need parameters.
- $function = $params['callback'];
- $context = array_merge($context, $params);
- $result[$action_id] = $function($object, $context, $a1, $a2);
- }
- // Singleton action; $action_id is the function name.
- else {
- $result[$action_id] = $action_id($object, $context, $a1, $a2);
- }
- }
- }
- // Optimized execution of single action.
- else {
- // If it's a configurable action, retrieve stored parameters.
- if (is_numeric($action_ids)) {
- $action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $action_ids));
- $function = $action->callback;
- $context = array_merge($context, unserialize($action->parameters));
- $result[$action_ids] = $function($object, $context, $a1, $a2);
- }
- // Singleton action; $action_ids is the function name.
- else {
- $result[$action_ids] = $action_ids($object, $context, $a1, $a2);
- }
- }
- return $result;
-}
-
-
-/**
- * Discover all action functions by invoking hook_action_info().
- *
- * mymodule_action_info() {
- * return array(
- * 'mymodule_functiondescription_action' => array(
- * 'type' => 'node',
- * 'description' => t('Save node'),
- * 'configurable' => FALSE,
- * 'hooks' => array(
- * 'nodeapi' => array('delete', 'insert', 'update', 'view'),
- * 'comment' => array('delete', 'insert', 'update', 'view'),
- * )
- * )
- * );
- * }
- *
- * The description is used in presenting possible actions to the user for
- * configuration. The type is used to present these actions in a logical
- * grouping and to denote context. Some types are 'node', 'user', 'comment',
- * and 'system'. If an action is configurable it will provide form,
- * validation and submission functions. The hooks the action supports
- * are declared in the 'hooks' array.
- *
- * @param $reset
- * Reset the action info static cache.
- *
- * @return
- * An associative array keyed on function name. The value of each key is
- * an array containing information about the action, such as type of
- * action and description of the action, e.g.,
- *
- * @code
- * $actions['node_publish_action'] = array(
- * 'type' => 'node',
- * 'description' => t('Publish post'),
- * 'configurable' => FALSE,
- * 'hooks' => array(
- * 'nodeapi' => array('presave', 'insert', 'update', 'view'),
- * 'comment' => array('delete', 'insert', 'update', 'view'),
- * ),
- * );
- * @endcode
- */
-function actions_list($reset = FALSE) {
- static $actions;
- if (!isset($actions) || $reset) {
- $actions = module_invoke_all('action_info');
- drupal_alter('action_info', $actions);
- }
-
- // See module_implements for explanations of this cast.
- return (array)$actions;
-}
-
-/**
- * Retrieve all action instances from the database.
- *
- * Compare with actions_list() which gathers actions by
- * invoking hook_action_info(). The two are synchronized
- * by visiting /admin/build/actions (when actions.module is
- * enabled) which runs actions_synchronize().
- *
- * @return
- * Associative array keyed by action ID. Each value is
- * an associative array with keys 'callback', 'description',
- * 'type' and 'configurable'.
- */
-function actions_get_all_actions() {
- $actions = array();
- $result = db_query("SELECT * FROM {actions}");
- while ($action = db_fetch_object($result)) {
- $actions[$action->aid] = array(
- 'callback' => $action->callback,
- 'description' => $action->description,
- 'type' => $action->type,
- 'configurable' => (bool) $action->parameters,
+class ActionsConfigurationTestCase extends DrupalWebTestCase {
+ /**
+ * Implementation of getInfo().
+ */
+ function getInfo() {
+ return array(
+ 'name' => t('Actions configuration'),
+ 'description' => t('Tests complex actions configuration by adding, editing, and deleting a complex action.'),
+ 'group' => t('System'),
);
}
- return $actions;
-}
-
-/**
- * Create an associative array keyed by md5 hashes of function names.
- *
- * 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 as keys and associative
- * arrays with keys 'description', 'type', etc. as values. Generally
- * the output of actions_list() or actions_get_all_actions() is given
- * as input to this function.
- *
- * @return
- * An associative array keyed on md5 hash of function name. The value of
- * each key is an associative array of function, description, and type
- * for the action.
- */
-function actions_actions_map($actions) {
- $actions_map = array();
- foreach ($actions as $callback => $array) {
- $key = md5($callback);
- $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
- $actions_map[$key]['description'] = $array['description'];
- $actions_map[$key]['type'] = $array['type'];
- $actions_map[$key]['configurable'] = $array['configurable'];
- }
- return $actions_map;
-}
-
-/**
- * Given an md5 hash of a function name, return the function name.
- *
- * Faster than actions_actions_map() when you only need the function name.
- *
- * @param $hash
- * MD5 hash of a function name
- *
- * @return
- * Function name
- */
-function actions_function_lookup($hash) {
- $actions_list = actions_list();
- foreach ($actions_list as $function => $array) {
- if (md5($function) == $hash) {
- return $function;
- }
- }
- // Must be an instance; must check database.
- $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters != ''", $hash));
- return $aid;
-}
-
-/**
- * Synchronize actions that are provided by modules.
- *
- * They are synchronized with actions that are stored in the actions table.
- * This is necessary so that actions that do not require configuration can
- * receive action IDs. This is not necessarily the best approach,
- * but it is the most straightforward.
- */
-function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
- if (!$actions_in_code) {
- $actions_in_code = actions_list();
- }
- $actions_in_db = array();
- $result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
- while ($action = db_fetch_object($result)) {
- $actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description);
- }
-
- // 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 (array_key_exists($callback, $actions_in_db)) {
- unset($actions_in_db[$callback]);
- }
- else {
- // This is a new singleton that we don't have an aid for; assign one.
- db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']);
- watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description'])));
- }
- }
- }
-
- // Any actions that we have left in $actions_in_db are orphaned.
- if ($actions_in_db) {
- $orphaned = array();
- $placeholder = array();
-
- foreach ($actions_in_db as $callback => $array) {
- $orphaned[] = $callback;
- $placeholder[] = "'%s'";
- }
-
- $orphans = implode(', ', $orphaned);
-
- if ($delete_orphans) {
- $placeholders = implode(', ', $placeholder);
- $results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned);
- while ($action = db_fetch_object($results)) {
- actions_delete($action->aid);
- watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description)));
- }
- }
- else {
- $link = l(t('Remove orphaned actions'), 'admin/build/actions/orphan');
- $count = count($actions_in_db);
- watchdog('actions', format_plural($count, 'One orphaned action (%orphans) exists in the actions table. !link', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), 'warning'));
- }
+ /**
+ * Test the configuration of advanced actions through the administration
+ * interface.
+ */
+ function testActionConfiguration() {
+ // Create a user with permission to view the actions administration pages.
+ $user = $this->drupalCreateUser(array('administer actions'));
+ $this->drupalLogin($user);
+
+ // Make a POST request to admin/settings/actions/manage.
+ $edit = array();
+ $edit['action'] = md5('system_goto_action');
+ $this->drupalPost('admin/settings/actions/manage', $edit, t('Create'));
+
+ // Make a POST request to the individual action configuration page.
+ $edit = array();
+ $action_description = $this->randomName();
+ $edit['actions_description'] = $action_description;
+ $edit['url'] = 'admin';
+ $this->drupalPost('admin/settings/actions/configure/' . md5('system_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."));
+ $this->assertText($action_description, t("Make sure the action description 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'));
+ $edit = array();
+ $new_action_description = $this->randomName();
+ $edit['actions_description'] = $new_action_description;
+ $edit['url'] = 'admin';
+ $this->drupalPost('admin/settings/actions/configure/1', $edit, t('Save'));
+
+ // Make sure that the action updated properly.
+ $this->assertText(t('The action has been successfully saved.'), t("Make sure we get a confirmation that we've successfully updated the complex action."));
+ $this->assertNoText($action_description, t("Make sure the old action description does NOT appear on the configuration page after we've updated the complex action."));
+ $this->assertText($new_action_description, t("Make sure the action description appears on the configuration page after we've updated the complex action."));
+
+ // Make sure that deletions work properly.
+ $this->clickLink(t('delete'));
+ $edit = array();
+ $this->drupalPost('admin/settings/actions/delete/1', $edit, t('Delete'));
+
+ // Make sure that the action was actually deleted.
+ $this->assertRaw(t('Action %action was deleted', array('%action' => $new_action_description)), t('Make sure that we get a delete confirmation message.'));
+ $this->drupalGet('admin/settings/actions/manage');
+ $this->assertNoText($new_action_description, t("Make sure the action description does not appear on the overview page after we've deleted the action."));
+ $exists = db_result(db_query("SELECT aid FROM {actions} WHERE callback = 'drupal_goto_action'"));
+ $this->assertFalse($exists, t('Make sure the action is gone from the database after being deleted.'));
}
}
-
-/**
- * Save an action and its associated user-supplied parameter values to the database.
- *
- * @param $function
- * The name of the function to be called when this action is performed.
- * @param $params
- * An associative array with parameter names as keys and parameter values
- * as values.
- * @param $desc
- * A user-supplied description 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 actions_save($function, $type, $params, $desc, $aid = NULL) {
- $serialized = serialize($params);
- if ($aid) {
- db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized, $desc, $aid);
- watchdog('actions', 'Action %action saved.', array('%action' => $desc));
- }
- else {
- // aid is the callback for singleton actions so we need to keep a
- // separate table for numeric aids.
- db_query('INSERT INTO {actions_aid} VALUES (default)');
- $aid = db_last_insert_id('actions_aid', 'aid');
- db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
- watchdog('actions', 'Action %action created.', array('%action' => $desc));
- }
-
- return $aid;
-}
-
-/**
- * Retrieve a single action from the database.
- *
- * @param $aid
- * integer The ID of the action to retrieve.
- *
- * @return
- * The appropriate action row from the database as an object.
- */
-function actions_load($aid) {
- return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aid));
-}
-
-/**
- * Delete a single action from the database.
- *
- * @param $aid
- * integer The ID of the action to delete.
- */
-function actions_delete($aid) {
- db_query("DELETE FROM {actions} WHERE aid = %d", $aid);
- module_invoke_all('actions_delete', $aid);
-}