webmaster@1: variable_get('actions_max_stack', 35)) { webmaster@7: watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR); webmaster@1: return; webmaster@1: } webmaster@1: $actions = array(); webmaster@1: $available_actions = actions_list(); webmaster@1: $result = array(); webmaster@1: if (is_array($action_ids)) { webmaster@1: $where = array(); webmaster@1: $where_values = array(); webmaster@1: foreach ($action_ids as $action_id) { webmaster@1: if (is_numeric($action_id)) { webmaster@1: $where[] = 'OR aid = %d'; webmaster@1: $where_values[] = $action_id; webmaster@1: } webmaster@1: elseif (isset($available_actions[$action_id])) { webmaster@1: $actions[$action_id] = $available_actions[$action_id]; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // When we have action instances we must go to the database to webmaster@1: // retrieve instance data. webmaster@1: if ($where) { webmaster@1: $where_clause = implode(' ', $where); webmaster@1: // Strip off leading 'OR '. webmaster@1: $where_clause = '('. strstr($where_clause, " ") .')'; webmaster@1: $result_db = db_query('SELECT * FROM {actions} WHERE '. $where_clause, $where_values); webmaster@1: while ($action = db_fetch_object($result_db)) { webmaster@1: $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array(); webmaster@1: $actions[$action->aid]['callback'] = $action->callback; webmaster@1: $actions[$action->aid]['type'] = $action->type; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Fire actions, in no particular order. webmaster@1: foreach ($actions as $action_id => $params) { webmaster@1: if (is_numeric($action_id)) { // Configurable actions need parameters. webmaster@1: $function = $params['callback']; webmaster@1: $context = array_merge($context, $params); webmaster@1: $result[$action_id] = $function($object, $context, $a1, $a2); webmaster@1: } webmaster@1: // Singleton action; $action_id is the function name. webmaster@1: else { webmaster@1: $result[$action_id] = $action_id($object, $context, $a1, $a2); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: // Optimized execution of single action. webmaster@1: else { webmaster@1: // If it's a configurable action, retrieve stored parameters. webmaster@1: if (is_numeric($action_ids)) { webmaster@1: $action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $action_ids)); webmaster@1: $function = $action->callback; webmaster@1: $context = array_merge($context, unserialize($action->parameters)); webmaster@1: $result[$action_ids] = $function($object, $context, $a1, $a2); webmaster@1: } webmaster@1: // Singleton action; $action_ids is the function name. webmaster@1: else { webmaster@1: $result[$action_ids] = $action_ids($object, $context, $a1, $a2); webmaster@1: } webmaster@1: } webmaster@1: return $result; webmaster@1: } webmaster@1: webmaster@1: webmaster@1: /** webmaster@1: * Discover all action functions by invoking hook_action_info(). webmaster@1: * webmaster@1: * mymodule_action_info() { webmaster@1: * return array( webmaster@1: * 'mymodule_functiondescription_action' => array( webmaster@1: * 'type' => 'node', webmaster@1: * 'description' => t('Save node'), webmaster@1: * 'configurable' => FALSE, webmaster@1: * 'hooks' => array( webmaster@1: * 'nodeapi' => array('delete', 'insert', 'update', 'view'), webmaster@1: * 'comment' => array('delete', 'insert', 'update', 'view'), webmaster@1: * ) webmaster@1: * ) webmaster@1: * ); webmaster@1: * } webmaster@1: * webmaster@1: * The description is used in presenting possible actions to the user for webmaster@1: * configuration. The type is used to present these actions in a logical webmaster@1: * grouping and to denote context. Some types are 'node', 'user', 'comment', webmaster@1: * and 'system'. If an action is configurable it will provide form, webmaster@1: * validation and submission functions. The hooks the action supports webmaster@1: * are declared in the 'hooks' array. webmaster@1: * webmaster@1: * @param $reset webmaster@1: * Reset the action info static cache. webmaster@1: * webmaster@1: * @return webmaster@1: * An associative array keyed on function name. The value of each key is webmaster@1: * an array containing information about the action, such as type of webmaster@1: * action and description of the action, e.g., webmaster@1: * webmaster@1: * @code webmaster@1: * $actions['node_publish_action'] = array( webmaster@1: * 'type' => 'node', webmaster@1: * 'description' => t('Publish post'), webmaster@1: * 'configurable' => FALSE, webmaster@1: * 'hooks' => array( webmaster@1: * 'nodeapi' => array('presave', 'insert', 'update', 'view'), webmaster@1: * 'comment' => array('delete', 'insert', 'update', 'view'), webmaster@1: * ), webmaster@1: * ); webmaster@1: * @endcode webmaster@1: */ webmaster@1: function actions_list($reset = FALSE) { webmaster@1: static $actions; webmaster@1: if (!isset($actions) || $reset) { webmaster@1: $actions = module_invoke_all('action_info'); webmaster@1: drupal_alter('action_info', $actions); webmaster@1: } webmaster@1: webmaster@1: // See module_implements for explanations of this cast. webmaster@1: return (array)$actions; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Retrieve all action instances from the database. webmaster@1: * webmaster@1: * Compare with actions_list() which gathers actions by webmaster@1: * invoking hook_action_info(). The two are synchronized webmaster@1: * by visiting /admin/build/actions (when actions.module is webmaster@1: * enabled) which runs actions_synchronize(). webmaster@1: * webmaster@1: * @return webmaster@1: * Associative array keyed by action ID. Each value is webmaster@1: * an associative array with keys 'callback', 'description', webmaster@1: * 'type' and 'configurable'. webmaster@1: */ webmaster@1: function actions_get_all_actions() { webmaster@1: $actions = array(); webmaster@1: $result = db_query("SELECT * FROM {actions}"); webmaster@1: while ($action = db_fetch_object($result)) { webmaster@1: $actions[$action->aid] = array( webmaster@1: 'callback' => $action->callback, webmaster@1: 'description' => $action->description, webmaster@1: 'type' => $action->type, webmaster@1: 'configurable' => (bool) $action->parameters, webmaster@1: ); webmaster@1: } webmaster@1: return $actions; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Create an associative array keyed by md5 hashes of function names. webmaster@1: * webmaster@1: * Hashes are used to prevent actual function names from going out into webmaster@1: * HTML forms and coming back. webmaster@1: * webmaster@1: * @param $actions webmaster@1: * An associative array with function names as keys and associative webmaster@1: * arrays with keys 'description', 'type', etc. as values. Generally webmaster@1: * the output of actions_list() or actions_get_all_actions() is given webmaster@1: * as input to this function. webmaster@1: * webmaster@1: * @return webmaster@1: * An associative array keyed on md5 hash of function name. The value of webmaster@1: * each key is an associative array of function, description, and type webmaster@1: * for the action. webmaster@1: */ webmaster@1: function actions_actions_map($actions) { webmaster@1: $actions_map = array(); webmaster@1: foreach ($actions as $callback => $array) { webmaster@1: $key = md5($callback); webmaster@1: $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback; webmaster@1: $actions_map[$key]['description'] = $array['description']; webmaster@1: $actions_map[$key]['type'] = $array['type']; webmaster@1: $actions_map[$key]['configurable'] = $array['configurable']; webmaster@1: } webmaster@1: return $actions_map; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Given an md5 hash of a function name, return the function name. webmaster@1: * webmaster@1: * Faster than actions_actions_map() when you only need the function name. webmaster@1: * webmaster@1: * @param $hash webmaster@1: * MD5 hash of a function name webmaster@1: * webmaster@1: * @return webmaster@1: * Function name webmaster@1: */ webmaster@1: function actions_function_lookup($hash) { webmaster@1: $actions_list = actions_list(); webmaster@1: foreach ($actions_list as $function => $array) { webmaster@1: if (md5($function) == $hash) { webmaster@1: return $function; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Must be an instance; must check database. webmaster@1: $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters != ''", $hash)); webmaster@1: return $aid; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Synchronize actions that are provided by modules. webmaster@1: * webmaster@1: * They are synchronized with actions that are stored in the actions table. webmaster@1: * This is necessary so that actions that do not require configuration can webmaster@1: * receive action IDs. This is not necessarily the best approach, webmaster@1: * but it is the most straightforward. webmaster@1: */ webmaster@1: function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) { webmaster@1: if (!$actions_in_code) { webmaster@1: $actions_in_code = actions_list(); webmaster@1: } webmaster@1: $actions_in_db = array(); webmaster@1: $result = db_query("SELECT * FROM {actions} WHERE parameters = ''"); webmaster@1: while ($action = db_fetch_object($result)) { webmaster@1: $actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description); webmaster@1: } webmaster@1: webmaster@1: // Go through all the actions provided by modules. webmaster@1: foreach ($actions_in_code as $callback => $array) { webmaster@1: // Ignore configurable actions since their instances get put in webmaster@1: // when the user adds the action. webmaster@1: if (!$array['configurable']) { webmaster@1: // If we already have an action ID for this action, no need to assign aid. webmaster@1: if (array_key_exists($callback, $actions_in_db)) { webmaster@1: unset($actions_in_db[$callback]); webmaster@1: } webmaster@1: else { webmaster@1: // This is a new singleton that we don't have an aid for; assign one. webmaster@1: db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']); webmaster@1: watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description']))); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Any actions that we have left in $actions_in_db are orphaned. webmaster@1: if ($actions_in_db) { webmaster@1: $orphaned = array(); webmaster@1: $placeholder = array(); webmaster@1: webmaster@1: foreach ($actions_in_db as $callback => $array) { webmaster@1: $orphaned[] = $callback; webmaster@1: $placeholder[] = "'%s'"; webmaster@1: } webmaster@1: webmaster@1: $orphans = implode(', ', $orphaned); webmaster@1: webmaster@1: if ($delete_orphans) { webmaster@1: $placeholders = implode(', ', $placeholder); webmaster@1: $results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned); webmaster@1: while ($action = db_fetch_object($results)) { webmaster@1: actions_delete($action->aid); webmaster@1: watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description))); webmaster@1: } webmaster@1: } webmaster@1: else { webmaster@1: $link = l(t('Remove orphaned actions'), 'admin/build/actions/orphan'); webmaster@1: $count = count($actions_in_db); webmaster@1: 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')); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Save an action and its associated user-supplied parameter values to the database. webmaster@1: * webmaster@1: * @param $function webmaster@1: * The name of the function to be called when this action is performed. webmaster@1: * @param $params webmaster@1: * An associative array with parameter names as keys and parameter values webmaster@1: * as values. webmaster@1: * @param $desc webmaster@1: * A user-supplied description of this particular action, e.g., 'Send webmaster@1: * e-mail to Jim'. webmaster@1: * @param $aid webmaster@1: * The ID of this action. If omitted, a new action is created. webmaster@1: * webmaster@1: * @return webmaster@1: * The ID of the action. webmaster@1: */ webmaster@1: function actions_save($function, $type, $params, $desc, $aid = NULL) { webmaster@1: $serialized = serialize($params); webmaster@1: if ($aid) { webmaster@1: db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized, $desc, $aid); webmaster@1: watchdog('actions', 'Action %action saved.', array('%action' => $desc)); webmaster@1: } webmaster@1: else { webmaster@1: // aid is the callback for singleton actions so we need to keep a webmaster@1: // separate table for numeric aids. webmaster@1: db_query('INSERT INTO {actions_aid} VALUES (default)'); webmaster@1: $aid = db_last_insert_id('actions_aid', 'aid'); webmaster@1: db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc); webmaster@1: watchdog('actions', 'Action %action created.', array('%action' => $desc)); webmaster@1: } webmaster@1: webmaster@1: return $aid; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Retrieve a single action from the database. webmaster@1: * webmaster@1: * @param $aid webmaster@1: * integer The ID of the action to retrieve. webmaster@1: * webmaster@1: * @return webmaster@1: * The appropriate action row from the database as an object. webmaster@1: */ webmaster@1: function actions_load($aid) { webmaster@1: return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aid)); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Delete a single action from the database. webmaster@1: * webmaster@1: * @param $aid webmaster@1: * integer The ID of the action to delete. webmaster@1: */ webmaster@1: function actions_delete($aid) { webmaster@1: db_query("DELETE FROM {actions} WHERE aid = %d", $aid); webmaster@1: module_invoke_all('actions_delete', $aid); webmaster@1: }