[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/includes/ -> actions.inc (source)

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


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7