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(); $actions_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'); $result = $query->execute(); foreach ($result 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); $actions_result[$action_id] = $function($object, $context, $a1, $a2); } else { $actions_result[$action_id] = FALSE; } } // Singleton action; $action_id is the function name. else { $actions_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)); $actions_result[$action_ids] = $function($object, $context, $a1, $a2); } else { $actions_result[$action_ids] = FALSE; } } // Singleton action; $action_ids is the function name. else { if (function_exists($action_ids)) { $actions_result[$action_ids] = $action_ids($object, $context, $a1, $a2); } else { // Set to avoid undefined index error messages later. $actions_result[$action_ids] = FALSE; } } } $stack--; return $actions_result; } /** * Discovers all available actions by invoking hook_action_info(). * * This function contrasts with actions_get_all_actions(); see the * documentation of actions_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 actions_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 actions_list() function, which gathers * actions by invoking hook_action_info(). The actions returned by this * function and the actions returned by actions_list() are partially * synchronized. Non-configurable actions from hook_action_info() * implementations are put into the database when actions_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 actions_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 actions_list() or actions_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 actions_actions_map($actions) { $actions_map = array(); foreach ($actions as $callback => $array) { $key = drupal_hash_base64($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 actions_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 actions_list() (array key is the action * function name) or actions_get_all_actions() (array key is the action ID). * * @return * The corresponding array key, or FALSE if no match is found. */ function actions_function_lookup($hash) { // Check for a function name match. $actions_list = actions_list(); foreach ($actions_list as $function => $array) { if (drupal_hash_base64($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 (drupal_hash_base64($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 actions_synchronize($delete_orphans = FALSE) { $actions_in_code = actions_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('actions', "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) { actions_delete($action->aid); watchdog('actions', "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('actions', '@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 actions_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('actions', '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 actions_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 actions_delete($aid) { db_delete('actions') ->condition('aid', $aid) ->execute(); module_invoke_all('actions_delete', $aid); }