2007-06-29 18:06:51 +00:00
< ? php
2008-06-29 12:07:15 +00:00
/**
* @ file
* This is the actions engine for executing stored actions .
*/
2009-10-10 17:29:17 +00:00
/**
* @ defgroup actions Actions
* @ {
* Functions that perform an action on a certain system object .
*
2010-02-26 16:55:18 +00:00
* Action functions are declared by modules by implementing hook_action_info () .
* Modules can cause action functions to run by calling actions_do (), and
* trigger . module provides a user interface that lets administrators define
* events that cause action functions to run .
2009-10-10 17:29:17 +00:00
*
2010-02-26 16:55:18 +00:00
* Each action function takes two to four arguments :
* - $entity : The object that the action acts on , such as a node , comment , or
* user .
* - $context : Array of additional information about what triggered the action .
* - $a1 , $a2 : Optional additional information , which can be passed into
* actions_do () and will be passed along to the action function .
2009-10-10 17:29:17 +00:00
*
2011-12-19 14:37:02 +00:00
* @ }
2009-10-10 17:29:17 +00:00
*/
2008-06-29 12:07:15 +00:00
/**
2009-09-19 11:07:37 +00:00
* Performs a given list of actions by executing their callback functions .
2008-06-29 12:07:15 +00:00
*
2009-09-19 11:07:37 +00:00
* Given the IDs of actions to perform , this function finds out what the
* callback functions for the actions are by querying the database . Then
* it calls each callback using the function call $function ( $object , $context ,
* $a1 , $a2 ), passing the input arguments of this function ( see below ) to the
* action function .
2008-06-29 12:07:15 +00:00
*
* @ param $action_ids
2009-09-19 11:07:37 +00:00
* The IDs of the actions to perform . Can be a single action ID or an array
* of IDs . IDs of configurable actions must be given as numeric action IDs ;
* IDs of non - configurable actions may be given as action function names .
2008-06-29 12:07:15 +00:00
* @ param $object
2009-09-19 11:07:37 +00:00
* The object that the action will act on : a node , user , or comment object .
2008-06-29 12:07:15 +00:00
* @ param $context
2009-09-19 11:07:37 +00:00
* Associative array containing extra information about what triggered
* the action call , with $context [ 'hook' ] giving the name of the hook
* that resulted in this call to actions_do () .
2008-06-29 12:07:15 +00:00
* @ param $a1
2009-09-19 11:07:37 +00:00
* Passed along to the callback .
2008-06-29 12:07:15 +00:00
* @ param $a2
2009-09-19 11:07:37 +00:00
* Passed along to the callback .
2011-12-05 12:52:27 +00:00
*
2008-06-29 12:07:15 +00:00
* @ return
2009-09-19 11:07:37 +00:00
* An associative array containing the results of the functions that
* perform the actions , keyed on action ID .
2010-02-26 16:55:18 +00:00
*
* @ ingroup actions
2008-06-29 12:07:15 +00:00
*/
function actions_do ( $action_ids , $object = NULL , $context = NULL , $a1 = NULL , $a2 = NULL ) {
2008-10-02 02:06:00 +00:00
// $stack tracks the number of recursive calls.
2008-06-29 12:07:15 +00:00
static $stack ;
$stack ++ ;
if ( $stack > variable_get ( 'actions_max_stack' , 35 )) {
2011-07-04 16:58:33 +00:00
watchdog ( 'actions' , 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.' , array (), WATCHDOG_ERROR );
2008-06-29 12:07:15 +00:00
return ;
}
$actions = array ();
$available_actions = actions_list ();
2009-05-31 03:12:19 +00:00
$actions_result = array ();
2008-06-29 12:07:15 +00:00
if ( is_array ( $action_ids )) {
2008-10-10 08:49:51 +00:00
$conditions = array ();
2008-06-29 12:07:15 +00:00
foreach ( $action_ids as $action_id ) {
if ( is_numeric ( $action_id )) {
2008-10-10 08:49:51 +00:00
$conditions [] = $action_id ;
2008-06-29 12:07:15 +00:00
}
elseif ( isset ( $available_actions [ $action_id ])) {
$actions [ $action_id ] = $available_actions [ $action_id ];
}
}
2008-12-04 18:42:03 +00:00
// When we have action instances we must go to the database to retrieve
// instance data.
2008-10-10 08:49:51 +00:00
if ( ! empty ( $conditions )) {
$query = db_select ( 'actions' );
$query -> addField ( 'actions' , 'aid' );
$query -> addField ( 'actions' , 'type' );
$query -> addField ( 'actions' , 'callback' );
$query -> addField ( 'actions' , 'parameters' );
$query -> condition ( 'aid' , $conditions , 'IN' );
$result = $query -> execute ();
foreach ( $result as $action ) {
2008-06-29 12:07:15 +00:00
$actions [ $action -> aid ] = $action -> parameters ? unserialize ( $action -> parameters ) : array ();
$actions [ $action -> aid ][ 'callback' ] = $action -> callback ;
$actions [ $action -> aid ][ 'type' ] = $action -> type ;
}
}
// Fire actions, in no particular order.
foreach ( $actions as $action_id => $params ) {
2008-12-04 18:42:03 +00:00
// Configurable actions need parameters.
if ( is_numeric ( $action_id )) {
2008-06-29 12:07:15 +00:00
$function = $params [ 'callback' ];
2009-09-20 07:47:44 +00:00
if ( function_exists ( $function )) {
$context = array_merge ( $context , $params );
$actions_result [ $action_id ] = $function ( $object , $context , $a1 , $a2 );
}
else {
$actions_result [ $action_id ] = FALSE ;
}
2008-06-29 12:07:15 +00:00
}
// Singleton action; $action_id is the function name.
else {
2009-05-31 03:12:19 +00:00
$actions_result [ $action_id ] = $action_id ( $object , $context , $a1 , $a2 );
2008-06-29 12:07:15 +00:00
}
}
}
2008-12-04 18:42:03 +00:00
// Optimized execution of a single action.
2008-06-29 12:07:15 +00:00
else {
// If it's a configurable action, retrieve stored parameters.
if ( is_numeric ( $action_ids )) {
2008-10-10 08:49:51 +00:00
$action = db_query ( " SELECT callback, parameters FROM { actions} WHERE aid = :aid " , array ( ':aid' => $action_ids )) -> fetchObject ();
2008-06-29 12:07:15 +00:00
$function = $action -> callback ;
2009-09-20 07:47:44 +00:00
if ( function_exists ( $function )) {
$context = array_merge ( $context , unserialize ( $action -> parameters ));
$actions_result [ $action_ids ] = $function ( $object , $context , $a1 , $a2 );
}
else {
$actions_result [ $action_ids ] = FALSE ;
}
2008-06-29 12:07:15 +00:00
}
// Singleton action; $action_ids is the function name.
else {
2010-03-07 07:22:42 +00:00
if ( function_exists ( $action_ids )) {
$actions_result [ $action_ids ] = $action_ids ( $object , $context , $a1 , $a2 );
}
else {
// Set to avoid undefined index error messages later.
$actions_result [ $action_ids ] = FALSE ;
}
2008-06-29 12:07:15 +00:00
}
}
2008-10-02 02:06:00 +00:00
$stack -- ;
2009-05-31 03:12:19 +00:00
return $actions_result ;
2008-06-29 12:07:15 +00:00
}
/**
2009-09-19 11:07:37 +00:00
* Discovers all available actions by invoking hook_action_info () .
2008-06-29 12:07:15 +00:00
*
2009-09-19 11:07:37 +00:00
* This function contrasts with actions_get_all_actions (); see the
* documentation of actions_get_all_actions () for an explanation .
2008-06-29 12:07:15 +00:00
*
* @ param $reset
* Reset the action info static cache .
2011-12-05 12:52:27 +00:00
*
2008-06-29 12:07:15 +00:00
* @ return
2009-09-19 11:07:37 +00:00
* An associative array keyed on action function name , with the same format
* as the return value of hook_action_info (), containing all
* modules ' hook_action_info () return values as modified by any
* hook_action_info_alter () implementations .
*
* @ see hook_action_info ()
2008-06-29 12:07:15 +00:00
*/
function actions_list ( $reset = FALSE ) {
2010-03-07 07:22:42 +00:00
$actions = & drupal_static ( __FUNCTION__ );
2008-06-29 12:07:15 +00:00
if ( ! isset ( $actions ) || $reset ) {
$actions = module_invoke_all ( 'action_info' );
drupal_alter ( 'action_info' , $actions );
}
2008-12-04 18:42:03 +00:00
// See module_implements() for an explanation of this cast.
2010-05-06 05:59:31 +00:00
return ( array ) $actions ;
2008-06-29 12:07:15 +00:00
}
/**
2009-09-19 11:07:37 +00:00
* Retrieves all action instances from the database .
2008-06-29 12:07:15 +00:00
*
2009-09-19 11:07:37 +00:00
* This function differs from the actions_list () function , which gathers
* actions by invoking hook_action_info () . The actions returned by this
* function and the actions returned by actions_list () are partially
* synchronized . Non - configurable actions from hook_action_info ()
* implementations are put into the database when actions_synchronize () is
2011-12-05 12:52:27 +00:00
* called , which happens when admin / config / system / actions is visited .
* Configurable actions are not added to the database until they are configured
* in the user interface , in which case a database row is created for each
2009-09-19 11:07:37 +00:00
* configuration of each action .
2008-06-29 12:07:15 +00:00
*
* @ return
2009-09-19 11:07:37 +00:00
* Associative array keyed by numeric action ID . Each value is an associative
* array with keys 'callback' , 'label' , 'type' and 'configurable' .
2008-06-29 12:07:15 +00:00
*/
function actions_get_all_actions () {
2009-09-19 11:07:37 +00:00
$actions = db_query ( " SELECT aid, type, callback, parameters, label FROM { actions} " ) -> fetchAllAssoc ( 'aid' , PDO :: FETCH_ASSOC );
2008-10-10 08:49:51 +00:00
foreach ( $actions as & $action ) {
$action [ 'configurable' ] = ( bool ) $action [ 'parameters' ];
unset ( $action [ 'parameters' ]);
unset ( $action [ 'aid' ]);
2007-06-29 18:06:51 +00:00
}
2008-06-29 12:07:15 +00:00
return $actions ;
}
/**
2010-05-01 08:12:23 +00:00
* Creates an associative array keyed by hashes of function names or IDs .
2008-06-29 12:07:15 +00:00
*
2008-12-04 18:42:03 +00:00
* Hashes are used to prevent actual function names from going out into HTML
* forms and coming back .
2008-06-29 12:07:15 +00:00
*
* @ param $actions
2009-09-19 11:07:37 +00:00
* An associative array with function names or action IDs as keys
* and associative arrays with keys 'label' , 'type' , etc . as values .
* This is usually the output of actions_list () or actions_get_all_actions () .
2011-12-05 12:52:27 +00:00
*
2008-06-29 12:07:15 +00:00
* @ return
2010-05-01 08:12:23 +00:00
* An associative array whose keys are hashes of the input array keys , and
2009-09-19 11:07:37 +00:00
* whose corresponding values are associative arrays with components
* 'callback' , 'label' , 'type' , and 'configurable' from the input array .
2008-06-29 12:07:15 +00:00
*/
function actions_actions_map ( $actions ) {
$actions_map = array ();
foreach ( $actions as $callback => $array ) {
2010-05-01 08:12:23 +00:00
$key = drupal_hash_base64 ( $callback );
2008-06-29 12:07:15 +00:00
$actions_map [ $key ][ 'callback' ] = isset ( $array [ 'callback' ]) ? $array [ 'callback' ] : $callback ;
2009-09-19 11:07:37 +00:00
$actions_map [ $key ][ 'label' ] = $array [ 'label' ];
2008-06-29 12:07:15 +00:00
$actions_map [ $key ][ 'type' ] = $array [ 'type' ];
$actions_map [ $key ][ 'configurable' ] = $array [ 'configurable' ];
}
return $actions_map ;
}
/**
2011-12-05 12:52:27 +00:00
* Returns an action array key ( function or ID ), given its hash .
2008-06-29 12:07:15 +00:00
*
2009-09-19 11:07:37 +00:00
* Faster than actions_actions_map () when you only need the function name or ID .
2008-06-29 12:07:15 +00:00
*
* @ param $hash
2010-05-01 08:12:23 +00:00
* Hash of a function name or action ID array key . The array key
2009-09-19 11:07:37 +00:00
* is a key into the return value of actions_list () ( array key is the action
* function name ) or actions_get_all_actions () ( array key is the action ID ) .
2011-12-05 12:52:27 +00:00
*
2008-06-29 12:07:15 +00:00
* @ return
2009-09-19 11:07:37 +00:00
* The corresponding array key , or FALSE if no match is found .
2008-06-29 12:07:15 +00:00
*/
function actions_function_lookup ( $hash ) {
2009-09-19 11:07:37 +00:00
// Check for a function name match.
2008-06-29 12:07:15 +00:00
$actions_list = actions_list ();
foreach ( $actions_list as $function => $array ) {
2010-05-01 08:12:23 +00:00
if ( drupal_hash_base64 ( $function ) == $hash ) {
2008-06-29 12:07:15 +00:00
return $function ;
}
}
2010-05-01 08:12:23 +00:00
$aid = FALSE ;
2009-09-19 11:07:37 +00:00
// Must be a configurable action; check database.
2010-05-01 08:12:23 +00:00
$result = db_query ( " SELECT aid FROM { actions} WHERE parameters <> '' " ) -> fetchAll ( PDO :: FETCH_ASSOC );
foreach ( $result as $row ) {
if ( drupal_hash_base64 ( $row [ 'aid' ]) == $hash ) {
$aid = $row [ 'aid' ];
break ;
}
}
return $aid ;
2008-06-29 12:07:15 +00:00
}
/**
2009-09-19 11:07:37 +00:00
* Synchronizes actions that are provided by modules in hook_action_info () .
2008-06-29 12:07:15 +00:00
*
2009-09-19 11:07:37 +00:00
* Actions provided by modules in hook_action_info () implementations are
* synchronized with actions that are stored in the actions database table .
* This is necessary so that actions that do not require configuration can
* receive action IDs .
2008-12-20 05:20:21 +00:00
*
* @ param $delete_orphans
2009-09-19 11:07:37 +00:00
* If TRUE , any actions that exist in the database but are no longer
2008-12-20 05:20:21 +00:00
* found in the code ( for example , because the module that provides them has
* been disabled ) will be deleted .
2008-06-29 12:07:15 +00:00
*/
2008-12-20 05:20:21 +00:00
function actions_synchronize ( $delete_orphans = FALSE ) {
$actions_in_code = actions_list ( TRUE );
2009-09-19 11:07:37 +00:00
$actions_in_db = db_query ( " SELECT aid, callback, label FROM { actions} WHERE parameters = '' " ) -> fetchAllAssoc ( 'callback' , PDO :: FETCH_ASSOC );
2008-06-29 12:07:15 +00:00
// Go through all the actions provided by modules.
foreach ( $actions_in_code as $callback => $array ) {
2008-12-04 18:42:03 +00:00
// Ignore configurable actions since their instances get put in when the
// user adds the action.
2008-06-29 12:07:15 +00:00
if ( ! $array [ 'configurable' ]) {
// If we already have an action ID for this action, no need to assign aid.
2010-10-28 02:27:09 +00:00
if ( isset ( $actions_in_db [ $callback ])) {
2008-06-29 12:07:15 +00:00
unset ( $actions_in_db [ $callback ]);
}
else {
// This is a new singleton that we don't have an aid for; assign one.
2008-10-10 08:49:51 +00:00
db_insert ( 'actions' )
-> fields ( array (
'aid' => $callback ,
'type' => $array [ 'type' ],
'callback' => $callback ,
'parameters' => '' ,
2009-09-19 11:07:37 +00:00
'label' => $array [ 'label' ],
2008-10-10 08:49:51 +00:00
))
-> execute ();
2010-08-22 11:04:09 +00:00
watchdog ( 'actions' , " Action '%action' added. " , array ( '%action' => $array [ 'label' ]));
2008-06-29 12:07:15 +00:00
}
}
}
// Any actions that we have left in $actions_in_db are orphaned.
if ( $actions_in_db ) {
2008-09-26 16:13:37 +00:00
$orphaned = array_keys ( $actions_in_db );
2008-06-29 12:07:15 +00:00
if ( $delete_orphans ) {
2009-09-19 11:07:37 +00:00
$actions = db_query ( 'SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)' , array ( ':orphaned' => $orphaned )) -> fetchAll ();
2009-03-18 09:50:46 +00:00
foreach ( $actions as $action ) {
2008-06-29 12:07:15 +00:00
actions_delete ( $action -> aid );
2010-08-22 11:04:09 +00:00
watchdog ( 'actions' , " Removed orphaned action '%action' from database. " , array ( '%action' => $action -> label ));
2008-06-29 12:07:15 +00:00
}
}
else {
2009-09-01 16:50:12 +00:00
$link = l ( t ( 'Remove orphaned actions' ), 'admin/config/system/actions/orphan' );
2008-06-29 12:07:15 +00:00
$count = count ( $actions_in_db );
2008-09-26 16:13:37 +00:00
$orphans = implode ( ', ' , $orphaned );
2011-09-27 16:51:14 +00:00
watchdog ( 'actions' , '@count orphaned actions (%orphans) exist in the actions table. !link' , array ( '@count' => $count , '%orphans' => $orphans , '!link' => $link ), WATCHDOG_INFO );
2008-06-29 12:07:15 +00:00
}
2007-08-29 14:57:50 +00:00
}
}
2008-06-29 12:07:15 +00:00
/**
2009-09-19 11:07:37 +00:00
* Saves an action and its user - supplied parameter values to the database .
2008-06-29 12:07:15 +00:00
*
* @ param $function
* The name of the function to be called when this action is performed .
2009-06-18 10:37:37 +00:00
* @ param $type
* The type of action , to describe grouping and / or context , e . g . , 'node' ,
* 'user' , 'comment' , or 'system' .
2008-06-29 12:07:15 +00:00
* @ param $params
2008-12-04 18:42:03 +00:00
* An associative array with parameter names as keys and parameter values as
* values .
2009-09-19 11:07:37 +00:00
* @ param $label
* A user - supplied label of this particular action , e . g . , ' Send e - mail
2008-12-04 18:42:03 +00:00
* to Jim ' .
2008-06-29 12:07:15 +00:00
* @ param $aid
* The ID of this action . If omitted , a new action is created .
2011-12-05 12:52:27 +00:00
*
2008-06-29 12:07:15 +00:00
* @ return
* The ID of the action .
*/
2009-09-19 11:07:37 +00:00
function actions_save ( $function , $type , $params , $label , $aid = NULL ) {
2008-12-04 18:42:03 +00:00
// aid is the callback for singleton actions so we need to keep a separate
// table for numeric aids.
2008-10-10 08:49:51 +00:00
if ( ! $aid ) {
2009-10-18 06:56:24 +00:00
$aid = db_next_id ();
2008-06-29 12:07:15 +00:00
}
2008-10-10 08:49:51 +00:00
db_merge ( 'actions' )
-> key ( array ( 'aid' => $aid ))
-> fields ( array (
'callback' => $function ,
'type' => $type ,
'parameters' => serialize ( $params ),
2009-09-19 11:07:37 +00:00
'label' => $label ,
2008-10-10 08:49:51 +00:00
))
-> execute ();
2009-09-19 11:07:37 +00:00
watchdog ( 'actions' , 'Action %action saved.' , array ( '%action' => $label ));
2008-06-29 12:07:15 +00:00
return $aid ;
}
/**
2009-09-19 11:07:37 +00:00
* Retrieves a single action from the database .
2008-06-29 12:07:15 +00:00
*
* @ param $aid
2008-12-04 18:42:03 +00:00
* The ID of the action to retrieve .
2011-12-05 12:52:27 +00:00
*
2008-06-29 12:07:15 +00:00
* @ return
* The appropriate action row from the database as an object .
*/
function actions_load ( $aid ) {
2009-09-19 11:07:37 +00:00
return db_query ( " SELECT aid, type, callback, parameters, label FROM { actions} WHERE aid = :aid " , array ( ':aid' => $aid )) -> fetchObject ();
2008-06-29 12:07:15 +00:00
}
/**
2009-09-19 11:07:37 +00:00
* Deletes a single action from the database .
2008-06-29 12:07:15 +00:00
*
* @ param $aid
2008-12-04 18:42:03 +00:00
* The ID of the action to delete .
2008-06-29 12:07:15 +00:00
*/
function actions_delete ( $aid ) {
2008-10-10 08:49:51 +00:00
db_delete ( 'actions' )
-> condition ( 'aid' , $aid )
-> execute ();
2008-06-29 12:07:15 +00:00
module_invoke_all ( 'actions_delete' , $aid );
}