Writing actions (Drupal 6.x)
Drupal actions are best understood using an appropriate definition.
For Drupal site administrators: actions are individual "things that Drupal can do." Some examples of actions are sending an email, publishing a node, banning a user or promoting a node to the front page of a website. Typically you will encounter actions in Drupal's configuration screens.
For developers: an action is a function that operates like a stored procedure. The function's parameters, if any, are stored in the database and the function is executed by retrieving these stored parameters and calling the function.
Can you give me an example of where an action might be useful?
Actions are usually used to configure Drupal's response to an event. For example, suppose a Drupal site administrator wants to be notified by email whenever a new user signs up. She would configure a "Send email" action and use the trigger module to assign that action to execute when a new user joins the site. (Technically, that would be when the user hook's 'insert' op runs.)
Where does the actions code actually live?
The actions engine, which executes actions, lives in includes/actions.inc. The dispatcher for actions is in modules/trigger.module.
The configuration screens for adding, removing, and configuring individual actions are part of system.module.
The interface for assigning actions to events (that is, hooks) is provided by modules/trigger.module.
The hook that describes actions (hook_actions_info()) and the actions themselves live in individual modules. Actions that affect nodes, like the "Publish node" action, live in node.module.
How do I create a new action?
There are two steps. First, we must describe the action to Drupal using hook_action_info(). Then, we must actually write the code that will be executed.
Describing an action with hook_action_info()
Let's take a look at the user module's implementation of hook_action_info():
<?php
/**
* Implementation of hook_action_info().
*/
function user_action_info() {
return array(
'user_block_user_action' => array(
'description' => t('Block current user'),
'type' => 'user',
'configurable' => FALSE,
'hooks' => array(
'nodeapi' => array('presave', 'delete', 'insert', 'update', 'view'),
'comment' => array('view', 'insert', 'update', 'delete'),
'user' => array('logout'),
),
),
'user_block_ip_action' => array(
'description' => t('Ban IP address of current user'),
'type' => 'user',
'configurable' => FALSE,
'hooks' => array(
'nodeapi' => array('presave', 'delete', 'insert', 'update', 'view'),
'comment' => array('view', 'insert', 'update', 'delete'),
'user' => array('logout'),
)
),
);
}
?>hook_action_info() must return an array, keyed by the function names of the actions being described. In user.module's user_action_info() we are describing two actions. We'll focus on the first one. The array key of the first action being described is 'user_block_user_action'. That's the name of the function that will actually be executed when this action runs. The name of the function is constructed by using the following convention:
modulename + description of what the function does + '_action'
In this case it is
user + block user + action
which gives us 'user_block_user_action'.
Next, we need to provide some information in the array using the following keys:
description: an easy-to-understand description of what the action does
type: the type is determined by what object the action acts on. Possible choices are node, user, comment, and system. Or your own custom type.
configurable: TRUE or FALSE. This determines the interface that Drupal will use when configuring actions. When set to FALSE we have the simplest case, where there is no interface to configure the action. In our example, the "Block current user" action does not need any additional information since the current user can be easily determined by Drupal at runtime. A more complicated action, such as a "Send email" action, would need to know things like who to send the email to, what to put in the subject line and the body of the email, etc.
hooks: this is an array of all of the operations this action is appropriate for, keyed by hook name. The actions module uses this to filter out inappropriate actions when presenting the interface for assigning actions to events. For example, the "Block current user" action defines the 'logout' operation of the 'user' hook, but not the 'login' operation. That's because it would be kind of silly to block a user as soon as the user logged in. It should be noted that this is an interface issue only; Drupal does not enforce these restrictions on execution of actions. Note: if you are writing actions in your own modules and you simply want to declare support for all possible hooks, you can set 'hooks' => array('any' => TRUE).
Writing an action
Now that we've described the action to Drupal, we can write the actual code that runs when the action is executed. Let's look at the code for the "Block current user" action:
<?php
/**
* Implementation of a Drupal action.
* Blocks the current user.
*/
function user_block_user_action(&$object, $context = array()) {
// get the uid from the object
if (isset($object->uid)) {
$uid = $object->uid;
}
elseif (isset($context['uid'])) {
$uid = $context['uid'];
}
else {
global $user;
$uid = $user->uid;
}
// make sure we have a user record
if ($uid) {
$user = user_load($uid);
// block the user
db_query("UPDATE {users} SET status = 0 WHERE uid = %d", $uid);
// log out the user
sess_destroy_uid($uid);
// record a message noting the action taken
watchdog('action', 'Blocked user %name / uid=%uid.', array('%name' => check_plain($user->name), '%uid' => $uid));
}
}
?>Note that this code is now out of sync with the code in Drupal core (http://api.drupal.org/api/function/user_block_user_action) from where it was originally taken; there is an open issue (#497358: user_block_user_action() may log incomplete watchdog message) to fix it in core.
First, let's look at the function signature for the action. Two parameters are passed, an object and an array.
$object: this is the object on which the action expects to act. It corresponds with the 'type' that was declared for this action in hook_action_info(). For example, if the type 'user' was declared, the action will be passed a user object.
$context: this is an array that contains additional information that may be helpful for the action to look at to determine the context under which the action is currently running. For example, the actions module sets the 'hook' and 'op' keys of the context array (e.g., 'hook' may be 'nodeapi' and 'op' may be 'insert') so that the action can examine them and make various decisions if necessary.
Next, let's look at the action itself. It really has two parts. First, it determines the user to block by first looking at the user object it has been passed; failing that, it looks in the context array for the uid; failing that, it uses the global $user to determine the uid to block itself. Some of you may be curious about this. Why have these fallback positions? Why not require the passage of the "correct" first parameter? The answer is twofold. First, with actions we want a function signature that is universal so the underlying actions engine has to do less work, so we can have better performance. Second, suppose you want to block 50 users. Doing a full user_load() on each one just to get an object you can pass to an action is not performant when you can hand over the uid in the context array instead.
The second part of the action is where the user is actually blocked and a watchdog entry recorded.
Now you know how to make a nonconfigurable action. A configurable action must also provide form handlers in order for the administrator to set the action's stored parameters. Look at the "Unpublish comment containing keyword(s)" action in comment.module for an example, specifically at the functions comment_unpublish_by_keyword_action_form(), comment_unpublish_by_keyword_action_submit(), and comment_unpublish_by_keyword_action().
I've written a module that provides hooks. How can I assign actions to run when those hooks run?
Use the instructions provided in the writing triggers page of the handbook. Additional examples are also available in the node_hook_info() or comment_hook_info() or user_hook_info() functions.
I want to execute an action but I don't want to use actions.module.
OK. You can run any action directly from code by using the actions_do() function, which lives in includes/actions.inc.
I want to execute an action but only if certain conditions are met
Have a look at the Rules module and its documentation which is specifically designed to allow you to define conditionally executed actions, such as "send an email when a new node is posted, but only if the content type is 'node'."
I've updated my action declaration in hook_action_info() but the action has not been updated. Help!
Getting Drupal to refresh the info is slightly cumbersome, until you work out how. When you visit the actions settings page at admin/settings/actions Drupal will detect (via actions_synchronize() that the action has been updated but won't automatically start using the new action information.
Go to the "Recent log entries" report (admin -> reports -> recent log entries) and you should see a note about orphaned action(s). Click on the message to view the full details and then click on the link "Remove orphaned actions". Note that this will also remove any trigger assignments for this action.
Your latest and greatest action declaration will now be used. Don't forget to revisit the triggers page to assign your action(s) again.
Note that any changes you make to the code of your custom action implementation take effect immediately - it is only the information declaration in hook_action_info() that is hard to erase!

Configurable actions
Configurable actions require a form. A little spelunking revealed that what's needed for a configurable action action_function is to define functions action_function_form($context) and action_function_submit($form, $form_state) (and probably action_function_validate too).
For example, the action comment_unpublish_by_keyword_action is configurable and defines functions comment_unpublish_by_keyword_action_form and comment_unpublish_by_keyword_action_submit.
More on configurable actions in John VanDyk Blog
This blog has an example of a configurable action:
http://www.sysarchitects.com/node/47
The code under "Writing an
The code under "Writing an action" is not entirely complete.
The following should be added right after the last 'else' condition:
<?phpif(!isset($user))
{
$user = user_load($uid);
}
?>
... otherwise the $user object will not exist of one of the first two conditions (if/elseif) matches, and the watchdog entry will be incomplete.
---
Yuriy Babenko
www.yubastudios.com
My Drupal tutorials: http://yubastudios.com/blog/tag/tutorials
The code above seems to have
The code above seems to have been updated though not exactly as you suggest. The same bug is still in http://api.drupal.org/api/function/user_block_user_action, as noted above.
gpk
----
www.alexoria.co.uk
Sending email to multiple recipients
I can not send an email via an action to multiple recipients unless I send that email to a special forwarding address. I was wondering if there was any chance this could change in the near future (i.e. one could enter multiple email addresses - perhaps comma delimited - in similar fashion as one can in the Webform module).
The Action Email Role module allows you to send emails to all users of a particular role, but this isn't always what is needed.
There is a discussion of this in the support forums here: http://drupal.org/node/463992
Makes sense, but...
Hi all,
This seems to make sense to me, and Im fired up about creating some new Actions, but whenever I make a change or addition, nothing changes when I view /admin/settings/actions.
I've even tried simple things like changing 'description' => t('Publish post') to 'description' => t('XXPublish post') in mode.module, and the change never hits the site nor database.
I've followed the instructions under "I've updated my action declaration in...", but I don't even see any orphaned records in recent log entries.
Could there be a problem with actions_synchronize not running?
note: Im running Drupal 6-1.4 vanilla.
Any help is appreciated.
Thanks
-todd
changing action names
Before you can go to "Recent log entries" and see the orphaned message, you should disable the module associated with the action. Re enable it after you clear out the orphaned action.