cms/drupal/includes/actions.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * This is the actions engine for executing stored actions.
       
     6  */
       
     7 
       
     8 /**
       
     9  * @defgroup actions Actions
       
    10  * @{
       
    11  * Functions that perform an action on a certain system object.
       
    12  *
       
    13  * Action functions are declared by modules by implementing hook_action_info().
       
    14  * Modules can cause action functions to run by calling actions_do(), and
       
    15  * trigger.module provides a user interface that lets administrators define
       
    16  * events that cause action functions to run.
       
    17  *
       
    18  * Each action function takes two to four arguments:
       
    19  * - $entity: The object that the action acts on, such as a node, comment, or
       
    20  *   user.
       
    21  * - $context: Array of additional information about what triggered the action.
       
    22  * - $a1, $a2: Optional additional information, which can be passed into
       
    23  *   actions_do() and will be passed along to the action function.
       
    24  *
       
    25  * @}
       
    26  */
       
    27 
       
    28 /**
       
    29  * Performs a given list of actions by executing their callback functions.
       
    30  *
       
    31  * Given the IDs of actions to perform, this function finds out what the
       
    32  * callback functions for the actions are by querying the database. Then
       
    33  * it calls each callback using the function call $function($object, $context,
       
    34  * $a1, $a2), passing the input arguments of this function (see below) to the
       
    35  * action function.
       
    36  *
       
    37  * @param $action_ids
       
    38  *   The IDs of the actions to perform. Can be a single action ID or an array
       
    39  *   of IDs. IDs of configurable actions must be given as numeric action IDs;
       
    40  *   IDs of non-configurable actions may be given as action function names.
       
    41  * @param $object
       
    42  *   The object that the action will act on: a node, user, or comment object.
       
    43  * @param $context
       
    44  *   Associative array containing extra information about what triggered
       
    45  *   the action call, with $context['hook'] giving the name of the hook
       
    46  *   that resulted in this call to actions_do().
       
    47  * @param $a1
       
    48  *   Passed along to the callback.
       
    49  * @param $a2
       
    50  *   Passed along to the callback.
       
    51  *
       
    52  * @return
       
    53  *   An associative array containing the results of the functions that
       
    54  *   perform the actions, keyed on action ID.
       
    55  *
       
    56  * @ingroup actions
       
    57  */
       
    58 function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) {
       
    59   // $stack tracks the number of recursive calls.
       
    60   static $stack;
       
    61   $stack++;
       
    62   if ($stack > variable_get('actions_max_stack', 35)) {
       
    63     watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
       
    64     return;
       
    65   }
       
    66   $actions = array();
       
    67   $available_actions = actions_list();
       
    68   $actions_result = array();
       
    69   if (is_array($action_ids)) {
       
    70     $conditions = array();
       
    71     foreach ($action_ids as $action_id) {
       
    72       if (is_numeric($action_id)) {
       
    73         $conditions[] = $action_id;
       
    74       }
       
    75       elseif (isset($available_actions[$action_id])) {
       
    76         $actions[$action_id] = $available_actions[$action_id];
       
    77       }
       
    78     }
       
    79 
       
    80     // When we have action instances we must go to the database to retrieve
       
    81     // instance data.
       
    82     if (!empty($conditions)) {
       
    83       $query = db_select('actions');
       
    84       $query->addField('actions', 'aid');
       
    85       $query->addField('actions', 'type');
       
    86       $query->addField('actions', 'callback');
       
    87       $query->addField('actions', 'parameters');
       
    88       $query->condition('aid', $conditions, 'IN');
       
    89       $result = $query->execute();
       
    90       foreach ($result as $action) {
       
    91         $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
       
    92         $actions[$action->aid]['callback'] = $action->callback;
       
    93         $actions[$action->aid]['type'] = $action->type;
       
    94       }
       
    95     }
       
    96 
       
    97     // Fire actions, in no particular order.
       
    98     foreach ($actions as $action_id => $params) {
       
    99       // Configurable actions need parameters.
       
   100       if (is_numeric($action_id)) {
       
   101         $function = $params['callback'];
       
   102         if (function_exists($function)) {
       
   103           $context = array_merge($context, $params);
       
   104           $actions_result[$action_id] = $function($object, $context, $a1, $a2);
       
   105         }
       
   106         else {
       
   107           $actions_result[$action_id] = FALSE;
       
   108         }
       
   109       }
       
   110       // Singleton action; $action_id is the function name.
       
   111       else {
       
   112         $actions_result[$action_id] = $action_id($object, $context, $a1, $a2);
       
   113       }
       
   114     }
       
   115   }
       
   116   // Optimized execution of a single action.
       
   117   else {
       
   118     // If it's a configurable action, retrieve stored parameters.
       
   119     if (is_numeric($action_ids)) {
       
   120       $action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject();
       
   121       $function = $action->callback;
       
   122       if (function_exists($function)) {
       
   123         $context = array_merge($context, unserialize($action->parameters));
       
   124         $actions_result[$action_ids] = $function($object, $context, $a1, $a2);
       
   125       }
       
   126       else {
       
   127         $actions_result[$action_ids] = FALSE;
       
   128       }
       
   129     }
       
   130     // Singleton action; $action_ids is the function name.
       
   131     else {
       
   132       if (function_exists($action_ids)) {
       
   133         $actions_result[$action_ids] = $action_ids($object, $context, $a1, $a2);
       
   134       }
       
   135       else {
       
   136         // Set to avoid undefined index error messages later.
       
   137         $actions_result[$action_ids] = FALSE;
       
   138       }
       
   139     }
       
   140   }
       
   141   $stack--;
       
   142   return $actions_result;
       
   143 }
       
   144 
       
   145 /**
       
   146  * Discovers all available actions by invoking hook_action_info().
       
   147  *
       
   148  * This function contrasts with actions_get_all_actions(); see the
       
   149  * documentation of actions_get_all_actions() for an explanation.
       
   150  *
       
   151  * @param $reset
       
   152  *   Reset the action info static cache.
       
   153  *
       
   154  * @return
       
   155  *   An associative array keyed on action function name, with the same format
       
   156  *   as the return value of hook_action_info(), containing all
       
   157  *   modules' hook_action_info() return values as modified by any
       
   158  *   hook_action_info_alter() implementations.
       
   159  *
       
   160  * @see hook_action_info()
       
   161  */
       
   162 function actions_list($reset = FALSE) {
       
   163   $actions = &drupal_static(__FUNCTION__);
       
   164   if (!isset($actions) || $reset) {
       
   165     $actions = module_invoke_all('action_info');
       
   166     drupal_alter('action_info', $actions);
       
   167   }
       
   168 
       
   169   // See module_implements() for an explanation of this cast.
       
   170   return (array) $actions;
       
   171 }
       
   172 
       
   173 /**
       
   174  * Retrieves all action instances from the database.
       
   175  *
       
   176  * This function differs from the actions_list() function, which gathers
       
   177  * actions by invoking hook_action_info(). The actions returned by this
       
   178  * function and the actions returned by actions_list() are partially
       
   179  * synchronized. Non-configurable actions from hook_action_info()
       
   180  * implementations are put into the database when actions_synchronize() is
       
   181  * called, which happens when admin/config/system/actions is visited.
       
   182  * Configurable actions are not added to the database until they are configured
       
   183  * in the user interface, in which case a database row is created for each
       
   184  * configuration of each action.
       
   185  *
       
   186  * @return
       
   187  *   Associative array keyed by numeric action ID. Each value is an associative
       
   188  *   array with keys 'callback', 'label', 'type' and 'configurable'.
       
   189  */
       
   190 function actions_get_all_actions() {
       
   191   $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC);
       
   192   foreach ($actions as &$action) {
       
   193     $action['configurable'] = (bool) $action['parameters'];
       
   194     unset($action['parameters']);
       
   195     unset($action['aid']);
       
   196   }
       
   197   return $actions;
       
   198 }
       
   199 
       
   200 /**
       
   201  * Creates an associative array keyed by hashes of function names or IDs.
       
   202  *
       
   203  * Hashes are used to prevent actual function names from going out into HTML
       
   204  * forms and coming back.
       
   205  *
       
   206  * @param $actions
       
   207  *   An associative array with function names or action IDs as keys
       
   208  *   and associative arrays with keys 'label', 'type', etc. as values.
       
   209  *   This is usually the output of actions_list() or actions_get_all_actions().
       
   210  *
       
   211  * @return
       
   212  *   An associative array whose keys are hashes of the input array keys, and
       
   213  *   whose corresponding values are associative arrays with components
       
   214  *   'callback', 'label', 'type', and 'configurable' from the input array.
       
   215  */
       
   216 function actions_actions_map($actions) {
       
   217   $actions_map = array();
       
   218   foreach ($actions as $callback => $array) {
       
   219     $key = drupal_hash_base64($callback);
       
   220     $actions_map[$key]['callback']     = isset($array['callback']) ? $array['callback'] : $callback;
       
   221     $actions_map[$key]['label']        = $array['label'];
       
   222     $actions_map[$key]['type']         = $array['type'];
       
   223     $actions_map[$key]['configurable'] = $array['configurable'];
       
   224   }
       
   225   return $actions_map;
       
   226 }
       
   227 
       
   228 /**
       
   229  * Returns an action array key (function or ID), given its hash.
       
   230  *
       
   231  * Faster than actions_actions_map() when you only need the function name or ID.
       
   232  *
       
   233  * @param $hash
       
   234  *   Hash of a function name or action ID array key. The array key
       
   235  *   is a key into the return value of actions_list() (array key is the action
       
   236  *   function name) or actions_get_all_actions() (array key is the action ID).
       
   237  *
       
   238  * @return
       
   239  *   The corresponding array key, or FALSE if no match is found.
       
   240  */
       
   241 function actions_function_lookup($hash) {
       
   242   // Check for a function name match.
       
   243   $actions_list = actions_list();
       
   244   foreach ($actions_list as $function => $array) {
       
   245     if (drupal_hash_base64($function) == $hash) {
       
   246       return $function;
       
   247     }
       
   248   }
       
   249   $aid = FALSE;
       
   250   // Must be a configurable action; check database.
       
   251   $result = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchAll(PDO::FETCH_ASSOC);
       
   252   foreach ($result as $row) {
       
   253     if (drupal_hash_base64($row['aid']) == $hash) {
       
   254       $aid = $row['aid'];
       
   255       break;
       
   256     }
       
   257   }
       
   258   return $aid;
       
   259 }
       
   260 
       
   261 /**
       
   262  * Synchronizes actions that are provided by modules in hook_action_info().
       
   263  *
       
   264  * Actions provided by modules in hook_action_info() implementations are
       
   265  * synchronized with actions that are stored in the actions database table.
       
   266  * This is necessary so that actions that do not require configuration can
       
   267  * receive action IDs.
       
   268  *
       
   269  * @param $delete_orphans
       
   270  *   If TRUE, any actions that exist in the database but are no longer
       
   271  *   found in the code (for example, because the module that provides them has
       
   272  *   been disabled) will be deleted.
       
   273  */
       
   274 function actions_synchronize($delete_orphans = FALSE) {
       
   275   $actions_in_code = actions_list(TRUE);
       
   276   $actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC);
       
   277 
       
   278   // Go through all the actions provided by modules.
       
   279   foreach ($actions_in_code as $callback => $array) {
       
   280     // Ignore configurable actions since their instances get put in when the
       
   281     // user adds the action.
       
   282     if (!$array['configurable']) {
       
   283       // If we already have an action ID for this action, no need to assign aid.
       
   284       if (isset($actions_in_db[$callback])) {
       
   285         unset($actions_in_db[$callback]);
       
   286       }
       
   287       else {
       
   288         // This is a new singleton that we don't have an aid for; assign one.
       
   289         db_insert('actions')
       
   290           ->fields(array(
       
   291             'aid' => $callback,
       
   292             'type' => $array['type'],
       
   293             'callback' => $callback,
       
   294             'parameters' => '',
       
   295             'label' => $array['label'],
       
   296             ))
       
   297           ->execute();
       
   298         watchdog('actions', "Action '%action' added.", array('%action' => $array['label']));
       
   299       }
       
   300     }
       
   301   }
       
   302 
       
   303   // Any actions that we have left in $actions_in_db are orphaned.
       
   304   if ($actions_in_db) {
       
   305     $orphaned = array_keys($actions_in_db);
       
   306 
       
   307     if ($delete_orphans) {
       
   308       $actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll();
       
   309       foreach ($actions as $action) {
       
   310         actions_delete($action->aid);
       
   311         watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => $action->label));
       
   312       }
       
   313     }
       
   314     else {
       
   315       $link = l(t('Remove orphaned actions'), 'admin/config/system/actions/orphan');
       
   316       $count = count($actions_in_db);
       
   317       $orphans = implode(', ', $orphaned);
       
   318       watchdog('actions', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), WATCHDOG_INFO);
       
   319     }
       
   320   }
       
   321 }
       
   322 
       
   323 /**
       
   324  * Saves an action and its user-supplied parameter values to the database.
       
   325  *
       
   326  * @param $function
       
   327  *   The name of the function to be called when this action is performed.
       
   328  * @param $type
       
   329  *   The type of action, to describe grouping and/or context, e.g., 'node',
       
   330  *   'user', 'comment', or 'system'.
       
   331  * @param $params
       
   332  *   An associative array with parameter names as keys and parameter values as
       
   333  *   values.
       
   334  * @param $label
       
   335  *   A user-supplied label of this particular action, e.g., 'Send e-mail
       
   336  *   to Jim'.
       
   337  * @param $aid
       
   338  *   The ID of this action. If omitted, a new action is created.
       
   339  *
       
   340  * @return
       
   341  *   The ID of the action.
       
   342  */
       
   343 function actions_save($function, $type, $params, $label, $aid = NULL) {
       
   344   // aid is the callback for singleton actions so we need to keep a separate
       
   345   // table for numeric aids.
       
   346   if (!$aid) {
       
   347     $aid = db_next_id();
       
   348   }
       
   349 
       
   350   db_merge('actions')
       
   351     ->key(array('aid' => $aid))
       
   352     ->fields(array(
       
   353       'callback' => $function,
       
   354       'type' => $type,
       
   355       'parameters' => serialize($params),
       
   356       'label' => $label,
       
   357     ))
       
   358     ->execute();
       
   359 
       
   360   watchdog('actions', 'Action %action saved.', array('%action' => $label));
       
   361   return $aid;
       
   362 }
       
   363 
       
   364 /**
       
   365  * Retrieves a single action from the database.
       
   366  *
       
   367  * @param $aid
       
   368  *   The ID of the action to retrieve.
       
   369  *
       
   370  * @return
       
   371  *   The appropriate action row from the database as an object.
       
   372  */
       
   373 function actions_load($aid) {
       
   374   return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject();
       
   375 }
       
   376 
       
   377 /**
       
   378  * Deletes a single action from the database.
       
   379  *
       
   380  * @param $aid
       
   381  *   The ID of the action to delete.
       
   382  */
       
   383 function actions_delete($aid) {
       
   384   db_delete('actions')
       
   385     ->condition('aid', $aid)
       
   386     ->execute();
       
   387   module_invoke_all('actions_delete', $aid);
       
   388 }