Note that the Action Example (and Trigger Example) in the Examples Project should also help you with learning about actions and triggers.

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.

As you do when implementing all hooks, you need to write a custom module if you want to add your own actions. For more help on writing modules, see the guide to creating modules for Drupal 6.x.

Describing an action with hook_action_info()

Let's take a look at the user module's implementation of hook_action_info():

/**
 * 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:

/**
 * 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 'story'."

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!

Comments

infojunkie’s picture

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.

johnbarclay’s picture

This blog has an example of a configurable action:

http://www.sysarchitects.com/node/47

mooreds’s picture

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.

tqvn2004’s picture

I want to have an action for my customized node, which basically set the moderate flag of the node. My implementation of hook_action_info() is:

function mynode_action_info() {
  return array(
    'mynode_moderate_action' => array(
      'type' => 'mynode',
      'description' => t('Moderate post'),
      'configurable' => FALSE,
      'behavior' => array('changes_node_property'),
      'hooks' => array(
        'nodeapi' => array('presave'),
        'comment' => array('insert', 'update'),
      ),
    ),
    'mynode_unmoderate_action' => array(
      'type' => 'mynode',
      'description' => t('Unmoderate post'),
      'configurable' => FALSE,
      'behavior' => array('changes_node_property'),
      'hooks' => array(
        'nodeapi' => array('presave'),
        'comment' => array('delete', 'insert', 'update'),
      ),
    ),
  );
}

These are my implementation of the action:

function mynode_moderate_action(&$node, $context = array()) {
   $node = $context['node'];
   $node->moderate = 0;
   watchdog('action', 'Set @type %title to moderated.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
}
function mynode_unmoderate_action(&$node, $context = array()) {
   $node = $context['node'];
   $node->moderate = 1;
   watchdog('action', 'Set @type %title to unmoderated.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
}

To my surpise, $node object was empty, and I have to refer to $context['node']. I thought the $node object should contain the node reference, if "behaviour" is set to "changes_node_property"? This code works only if I declare it in node.module (but I don't like the idea of patching node.module):

function node_moderate_action(&$node, $context = array()) {
   $node->moderate = 0;
   watchdog('action', 'Set @type %title to moderated.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
}
function node_unmoderate_action(&$node, $context = array()) {
   $node->moderate = 1;
   watchdog('action', 'Set @type %title to unmoderated.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
}
infojunkie’s picture

'type' => 'mynode' should be 'type' => 'node'.

tqvn2004’s picture

I tried that, but it does not help at all. This is an old post of mine about Action for Drupal 5.x with Drupal 6.x style:

http://drupal.org/node/368708

Btw, I just upgrade my site to Drupal 6.x :)

_vid’s picture

Hi tqvn2004,
Did you work this out in D6?

I just figured out a way to get the node's moderate status changed when a workflow transitions from one state to another.
Now when I change the workflow state from 'pending approval' to 'approved' the moderate value of the node is updated as well.

To do this I'm using rules on event: 'Workflow state has changed'

If
'Check workflow transition from Pending approval to Approved'
Do
Execute custom PHP code

$node->moderate=true;
return array("node" => $node);

Now that node is out of the moderation queue.
That trigger in Workflow also includes a couple of it's own actions: publish the node and email the node author.

Rilla Creative’s picture

I know the custom action works and I've called it through Rules manually and it works fine. But when I schedule it, it doesn't fire off even though the ruleset gets called.

Here is my previous post about it. http://drupal.org/node/833178

Anyone have any idea why this isn't working for me?

Tom

darrenphillips’s picture

If your new action is not showing up in the triggers lists for assignment you may need to visit the actions admin page first. There's an actions synchronize that is run when you visit the actions admin page that updates the db with any new actions.

thamizhchelvan’s picture

HI,
I am trying to create my own action, where action is listed if configurable = FALSE, but it's not listed in the action list page if it has configurable = TRUE. Here is the code, what I have.

function mymodule_action_info(){
return array(
'mymodule_nwc_nofy_action' => array(
'description' => t('Notify Term Save'),
'type' => 'term',
'configurable' => TRUE,
'hooks' => array(
'taxonomy' => array('insert', 'update'),
),
),
);
}

function mymodule_nwc_nofy_action_form($context){
//process form
return $form;
}

Please help me on this.

kurapikats’s picture

I think this page shouldn't be here. Like on triggers section.

maneesh_thakur’s picture

hi,

I am trying to add custom code for action for my nodes. custom code is executed but nothing is done.
I have also added a pre-defined event to update 'content title' => this works fine.

"this is the log shown on the page,

0 ms "After updating existing content" has been invoked.
0.158 ms Executing the rule "generate_toc_for_update" on rule set "After updating existing content"
100.441 ms Action execution: "custom PHP code_to_gen_toc_for_updated"
102.364 ms Action execution: "Set updated content's title"
102.565 ms Saved variable updated content of type node.
126.305 ms "After updating existing content" has been invoked.
126.461 ms Not executing the rule "generate_toc_for_update" on rule set "After updating existing content" to prevent recursion.
126.562 ms Evaluation of "After updating existing content" has been finished.
128.443 ms Evaluation of "After updating existing content" has been finished."

Example, I have created a triggered rule with,
Event : After updating existing content
Custom Action: I have placed simple code here in the 'PHP Code' box,

$node->body .= <h3> ONE </h3><br><h3> TWO </h3><br><h3> THREE </h3>;
return array("node" => $node);

Let me know if I am missing on anything here. Am i doing right ?
I have drupal 6.x version on my server.
Writing your own custom module to get custom actions to work. Is this the only way out with rules ?

Regards.

senortim’s picture

I think I'm not understanding something fundamental. Is there a plug-in architecture for Actions in D6? And if so, is there a library of these actions?

I simply want to be able to bulk en/disable comments on nodes. Having looked at node.module, this looks pretty easy, but I hate the idea of modifying core modules, and am not familiar enough with Drupal to understand now to create a module for this express purpose. Thousands have already done this, so I also don't want to recreate the wheel. (I'd have thought this would be a default action!)

yechuah’s picture

I don't think you are after actions. The node_operations hook does what you want. Perform these operations at admin/content/node.

comment_operations.info

name =  Comment Operations
description =  Bulk operate comment settings.
core = 6.x

comment_operations.module

function comment_operations_node_operations() {
  return array(
    'comment_node_read_write' => array(
      'label' => t('Comment node read write'),
      'callback' => 'comment_operations_update',
      'callback arguments' => array(COMMENT_NODE_READ_WRITE),
    ),
    'comment_node_read_only' => array(
      'label' => t('Comment node read only'),
      'callback' => 'comment_operations_update',
      'callback arguments' => array(COMMENT_NODE_READ_ONLY),
    ),
    'comment_node_disabled' => array(
      'label' => t('Comment disabled'),
      'callback' => 'comment_operations_update',
      'callback arguments' => array(COMMENT_NODE_DISABLED),
    ),
  );
}

function comment_operations_update($nids, $comment) {
  foreach ($nids as $nid) {
    $node = node_load($nid);
    if ($comment != $node->comment) {
      $node->comment = $comment;
      node_save($node);
    }
  }
}
TMWagner’s picture

This page is titled
Writing actions (Drupal 6.x)

A few paragraphs in, a subsections begins "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.

The link under hook_action_info() leads to
http://api.drupal.org/api/drupal/modules--system--system.api.php/functio...

This link clearly has nothing to do with Drupal 6.x. It would be nice to have this information where it belonged and the information that should be here, actually here.

Further, before a discussion about "Writing Actions in Drupal"; it would be helpful to talk about the precursor to an Action; the Event, then optionally, the Trigger, THEN the Action.

giorgio79’s picture

Is there a module for creating actions?

Such as changing a node field value?

This could be similar to Rules, but that is for control structures.