web/drupal/includes/actions.inc
branchdrupal
changeset 74 0ff3ba646492
equal deleted inserted replaced
73:fcf75e232c5b 74:0ff3ba646492
       
     1 <?php
       
     2 // $Id: actions.inc,v 1.8.2.7 2009/02/16 14:34:30 goba Exp $
       
     3 
       
     4 /**
       
     5  * @file
       
     6  * This is the actions engine for executing stored actions.
       
     7  */
       
     8 
       
     9 /**
       
    10  * Perform a given list of actions by executing their callback functions.
       
    11  *
       
    12  * Given the IDs of actions to perform, find out what the callbacks
       
    13  * for the actions are by querying the database. Then call each callback
       
    14  * using the function call $function($object, $context, $a1, $a2)
       
    15  * where $function is the name of a function written in compliance with
       
    16  * the action specification; that is, foo($object, $context).
       
    17  *
       
    18  * @param $action_ids
       
    19  *   The ID of the action to perform. Can be a single action ID or an array
       
    20  *   of IDs. IDs of instances will be numeric; IDs of singletons will be
       
    21  *   function names.
       
    22  * @param $object
       
    23  *   Parameter that will be passed along to the callback. Typically the
       
    24  *   object that the action will act on; a node, user or comment object.
       
    25  *   If the action does not act on an object, pass a dummy object. This
       
    26  *   is necessary to support PHP 4 object referencing.
       
    27  * @param $context
       
    28  *   Parameter that will be passed along to the callback. $context is a
       
    29  *   keyed array containing extra information about what is currently
       
    30  *   happening at the time of the call. Typically $context['hook'] and
       
    31  *   $context['op'] will tell which hook-op combination resulted in this
       
    32  *   call to actions_do().
       
    33  * @param $a1
       
    34  *   Parameter that will be passed along to the callback.
       
    35  * @param $a2
       
    36  *   Parameter that will be passed along to the callback.
       
    37  *
       
    38  * @return
       
    39  *   An associative array containing the result of the function that
       
    40  *   performs the action, keyed on action ID.
       
    41  */
       
    42 function actions_do($action_ids, &$object, $context = NULL, $a1 = NULL, $a2 = NULL) {
       
    43   // $stack tracks the number of recursive calls.
       
    44   static $stack;
       
    45   $stack++;
       
    46   if ($stack > variable_get('actions_max_stack', 35)) {
       
    47     watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
       
    48     return;
       
    49   }
       
    50   $actions = array();
       
    51   $available_actions = actions_list();
       
    52   $result = array();
       
    53   if (is_array($action_ids)) {
       
    54     $where = array();
       
    55     $where_values = array();
       
    56     foreach ($action_ids as $action_id) {
       
    57       if (is_numeric($action_id)) {
       
    58         $where[] = "OR aid = '%s'";
       
    59         $where_values[] = $action_id;
       
    60       }
       
    61       elseif (isset($available_actions[$action_id])) {
       
    62         $actions[$action_id] = $available_actions[$action_id];
       
    63       }
       
    64     }
       
    65 
       
    66     // When we have action instances we must go to the database to
       
    67     // retrieve instance data.
       
    68     if ($where) {
       
    69       $where_clause = implode(' ', $where);
       
    70       // Strip off leading 'OR '.
       
    71       $where_clause = '('. strstr($where_clause, " ") .')';
       
    72       $result_db = db_query('SELECT * FROM {actions} WHERE '. $where_clause, $where_values);
       
    73       while ($action = db_fetch_object($result_db)) {
       
    74         $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
       
    75         $actions[$action->aid]['callback'] = $action->callback;
       
    76         $actions[$action->aid]['type'] = $action->type;
       
    77       }
       
    78     }
       
    79 
       
    80     // Fire actions, in no particular order.
       
    81     foreach ($actions as $action_id => $params) {
       
    82       if (is_numeric($action_id)) { // Configurable actions need parameters.
       
    83         $function = $params['callback'];
       
    84         $context = array_merge($context, $params);
       
    85         $result[$action_id] = $function($object, $context, $a1, $a2);
       
    86       }
       
    87       // Singleton action; $action_id is the function name.
       
    88       else {
       
    89         $result[$action_id] = $action_id($object, $context, $a1, $a2);
       
    90       }
       
    91     }
       
    92   }
       
    93   // Optimized execution of single action.
       
    94   else {
       
    95     // If it's a configurable action, retrieve stored parameters.
       
    96     if (is_numeric($action_ids)) {
       
    97       $action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = '%s'", $action_ids));
       
    98       $function = $action->callback;
       
    99       $context = array_merge($context, unserialize($action->parameters));
       
   100       $result[$action_ids] = $function($object, $context, $a1, $a2);
       
   101     }
       
   102     // Singleton action; $action_ids is the function name.
       
   103     else {
       
   104       $result[$action_ids] = $action_ids($object, $context, $a1, $a2);
       
   105     }
       
   106   }
       
   107   $stack--;
       
   108   return $result;
       
   109 }
       
   110 
       
   111 
       
   112 /**
       
   113  * Discover all action functions by invoking hook_action_info().
       
   114  *
       
   115  * mymodule_action_info() {
       
   116  *   return array(
       
   117  *     'mymodule_functiondescription_action' => array(
       
   118  *       'type' => 'node',
       
   119  *       'description' => t('Save node'),
       
   120  *       'configurable' => FALSE,
       
   121  *       'hooks' => array(
       
   122  *         'nodeapi' => array('delete', 'insert', 'update', 'view'),
       
   123  *         'comment' => array('delete', 'insert', 'update', 'view'),
       
   124  *       )
       
   125  *     )
       
   126  *   );
       
   127  * }
       
   128  *
       
   129  * The description is used in presenting possible actions to the user for
       
   130  * configuration. The type is used to present these actions in a logical
       
   131  * grouping and to denote context. Some types are 'node', 'user', 'comment',
       
   132  * and 'system'. If an action is configurable it will provide form,
       
   133  * validation and submission functions. The hooks the action supports
       
   134  * are declared in the 'hooks' array.
       
   135  *
       
   136  * @param $reset
       
   137  *   Reset the action info static cache.
       
   138  *
       
   139  * @return
       
   140  *   An associative array keyed on function name. The value of each key is
       
   141  *   an array containing information about the action, such as type of
       
   142  *   action and description of the action, e.g.,
       
   143  *
       
   144  *   @code
       
   145  *   $actions['node_publish_action'] = array(
       
   146  *     'type' => 'node',
       
   147  *     'description' => t('Publish post'),
       
   148  *     'configurable' => FALSE,
       
   149  *     'hooks' => array(
       
   150  *       'nodeapi' => array('presave', 'insert', 'update', 'view'),
       
   151  *       'comment' => array('delete', 'insert', 'update', 'view'),
       
   152  *     ),
       
   153  *   );
       
   154  *   @endcode
       
   155  */
       
   156 function actions_list($reset = FALSE) {
       
   157   static $actions;
       
   158   if (!isset($actions) || $reset) {
       
   159     $actions = module_invoke_all('action_info');
       
   160     drupal_alter('action_info', $actions);
       
   161   }
       
   162 
       
   163   // See module_implements for explanations of this cast.
       
   164   return (array)$actions;
       
   165 }
       
   166 
       
   167 /**
       
   168  * Retrieve all action instances from the database.
       
   169  *
       
   170  * Compare with actions_list() which gathers actions by
       
   171  * invoking hook_action_info(). The two are synchronized
       
   172  * by visiting /admin/build/actions (when actions.module is
       
   173  * enabled) which runs actions_synchronize().
       
   174  *
       
   175  * @return
       
   176  *   Associative array keyed by action ID. Each value is
       
   177  *   an associative array with keys 'callback', 'description',
       
   178  *   'type' and 'configurable'.
       
   179  */
       
   180 function actions_get_all_actions() {
       
   181   $actions = array();
       
   182   $result = db_query("SELECT * FROM {actions}");
       
   183   while ($action = db_fetch_object($result)) {
       
   184     $actions[$action->aid] = array(
       
   185       'callback' => $action->callback,
       
   186       'description' => $action->description,
       
   187       'type' => $action->type,
       
   188       'configurable' => (bool) $action->parameters,
       
   189     );
       
   190   }
       
   191   return $actions;
       
   192 }
       
   193 
       
   194 /**
       
   195  * Create an associative array keyed by md5 hashes of function names.
       
   196  *
       
   197  * Hashes are used to prevent actual function names from going out into
       
   198  * HTML forms and coming back.
       
   199  *
       
   200  * @param $actions
       
   201  *   An associative array with function names as keys and associative
       
   202  *   arrays with keys 'description', 'type', etc. as values. Generally
       
   203  *   the output of actions_list() or actions_get_all_actions() is given
       
   204  *   as input to this function.
       
   205  *
       
   206  * @return
       
   207  *   An associative array keyed on md5 hash of function name. The value of
       
   208  *   each key is an associative array of function, description, and type
       
   209  *   for the action.
       
   210  */
       
   211 function actions_actions_map($actions) {
       
   212   $actions_map = array();
       
   213   foreach ($actions as $callback => $array) {
       
   214     $key = md5($callback);
       
   215     $actions_map[$key]['callback']     = isset($array['callback']) ? $array['callback'] : $callback;
       
   216     $actions_map[$key]['description']  = $array['description'];
       
   217     $actions_map[$key]['type']         = $array['type'];
       
   218     $actions_map[$key]['configurable'] = $array['configurable'];
       
   219   }
       
   220   return $actions_map;
       
   221 }
       
   222 
       
   223 /**
       
   224  * Given an md5 hash of a function name, return the function name.
       
   225  *
       
   226  * Faster than actions_actions_map() when you only need the function name.
       
   227  *
       
   228  * @param $hash
       
   229  *   MD5 hash of a function name
       
   230  *
       
   231  * @return
       
   232  *   Function name
       
   233  */
       
   234 function actions_function_lookup($hash) {
       
   235   $actions_list = actions_list();
       
   236   foreach ($actions_list as $function => $array) {
       
   237     if (md5($function) == $hash) {
       
   238       return $function;
       
   239     }
       
   240   }
       
   241 
       
   242   // Must be an instance; must check database.
       
   243   $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters <> ''", $hash));
       
   244   return $aid;
       
   245 }
       
   246 
       
   247 /**
       
   248  * Synchronize actions that are provided by modules.
       
   249  *
       
   250  * They are synchronized with actions that are stored in the actions table.
       
   251  * This is necessary so that actions that do not require configuration can
       
   252  * receive action IDs. This is not necessarily the best approach,
       
   253  * but it is the most straightforward.
       
   254  */
       
   255 function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
       
   256   if (!$actions_in_code) {
       
   257     $actions_in_code = actions_list(TRUE);
       
   258   }
       
   259   $actions_in_db = array();
       
   260   $result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
       
   261   while ($action = db_fetch_object($result)) {
       
   262     $actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description);
       
   263   }
       
   264 
       
   265   // Go through all the actions provided by modules.
       
   266   foreach ($actions_in_code as $callback => $array) {
       
   267     // Ignore configurable actions since their instances get put in
       
   268     // when the user adds the action.
       
   269     if (!$array['configurable']) {
       
   270       // If we already have an action ID for this action, no need to assign aid.
       
   271       if (array_key_exists($callback, $actions_in_db)) {
       
   272         unset($actions_in_db[$callback]);
       
   273       }
       
   274       else {
       
   275         // This is a new singleton that we don't have an aid for; assign one.
       
   276         db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']);
       
   277         watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description'])));
       
   278       }
       
   279     }
       
   280   }
       
   281 
       
   282   // Any actions that we have left in $actions_in_db are orphaned.
       
   283   if ($actions_in_db) {
       
   284     $orphaned = array();
       
   285     $placeholder = array();
       
   286 
       
   287     foreach ($actions_in_db as $callback => $array) {
       
   288       $orphaned[] = $callback;
       
   289       $placeholder[] = "'%s'";
       
   290     }
       
   291 
       
   292     $orphans = implode(', ', $orphaned);
       
   293 
       
   294     if ($delete_orphans) {
       
   295       $placeholders = implode(', ', $placeholder);
       
   296       $results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned);
       
   297       while ($action = db_fetch_object($results)) {
       
   298         actions_delete($action->aid);
       
   299         watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description)));
       
   300       }
       
   301     }
       
   302     else {
       
   303       $link = l(t('Remove orphaned actions'), 'admin/settings/actions/orphan');
       
   304       $count = count($actions_in_db);
       
   305       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), WATCHDOG_WARNING);
       
   306     }
       
   307   }
       
   308 }
       
   309 
       
   310 /**
       
   311  * Save an action and its associated user-supplied parameter values to the database.
       
   312  *
       
   313  * @param $function
       
   314  *   The name of the function to be called when this action is performed.
       
   315  * @param $params
       
   316  *   An associative array with parameter names as keys and parameter values
       
   317  *   as values.
       
   318  * @param $desc
       
   319  *   A user-supplied description of this particular action, e.g., 'Send
       
   320  *   e-mail to Jim'.
       
   321  * @param $aid
       
   322  *   The ID of this action. If omitted, a new action is created.
       
   323  *
       
   324  * @return
       
   325  *   The ID of the action.
       
   326  */
       
   327 function actions_save($function, $type, $params, $desc, $aid = NULL) {
       
   328   $serialized = serialize($params);
       
   329   if ($aid) {
       
   330     db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = '%s'", $function, $type, $serialized, $desc, $aid);
       
   331     watchdog('actions', 'Action %action saved.', array('%action' => $desc));
       
   332   }
       
   333   else {
       
   334     // aid is the callback for singleton actions so we need to keep a
       
   335     // separate table for numeric aids.
       
   336     db_query('INSERT INTO {actions_aid} VALUES (default)');
       
   337     $aid = db_last_insert_id('actions_aid', 'aid');
       
   338     db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
       
   339     watchdog('actions', 'Action %action created.', array('%action' => $desc));
       
   340   }
       
   341 
       
   342   return $aid;
       
   343 }
       
   344 
       
   345 /**
       
   346  * Retrieve a single action from the database.
       
   347  *
       
   348  * @param $aid
       
   349  *   integer The ID of the action to retrieve.
       
   350  *
       
   351  * @return
       
   352  *   The appropriate action row from the database as an object.
       
   353  */
       
   354 function actions_load($aid) {
       
   355   return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = '%s'", $aid));
       
   356 }
       
   357 
       
   358 /**
       
   359  * Delete a single action from the database.
       
   360  *
       
   361  * @param $aid
       
   362  *   integer The ID of the action to delete.
       
   363  */
       
   364 function actions_delete($aid) {
       
   365   db_query("DELETE FROM {actions} WHERE aid = '%s'", $aid);
       
   366   module_invoke_all('actions_delete', $aid);
       
   367 }