[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/includes/ -> actions.inc (source)

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


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7