Actions

Workflow 5.x-2.0 released

I've finally got a project that uses the workflow module so I've been able to justify putting some time into it. As a result, I've released version 5.x-2.0. This version of workflow works with the 5.x-2.x version of actions, which is the backport of Drupal-6-style actions to Drupal 5. Thanks to mfredrickson and JacobSingh, intrepid workflow maintainers.

I plan to add the ability to change node workflow state while adding a comment to a node before continuing with a Drupal 6 version.

Slides from Drupalcon Boston 2008

Video of the presentation I gave at Drupalcon Boston 2008, titled Triggers and Actions and Hooks, Oh, My! should be appearing shortly. (In the meantime, here's me babbling about actions.) The slides are downloadable below.

Actions presentation, Boston 2008 (750k).

Drupalcon 2008 actions session

The session I proposed, Triggers and Actions and Hooks, Oh My! has been accepted at Drupalcon Boston 2008. I will be presenting the new capabilities of Drupal 6 in this area, from the big ideas to the nitty gritty. The session will be Tuesday afternoon from 5-6 pm, just before the Acquia Conference Social.

We had 26 people at the first Drupal conference in Antwerp (three years ago today, by the way!). There are currently 800 registered attendees for the upcoming Drupalcon, and the conference has reached its maximum capacity.

I'm looking forward to the presentations, the birds-of-a-feather meetings, hanging out with a bunch of geeks, some hacking time, and the business fair.

Writing an action for Drupal 6

Note: this applies to the development version of Drupal 6 as of June 12, 2007 with the actions patch applied.

Actions are atomic functions that can be assigned to run at different times during runtime. For example, you could have an action that emails a user and have that run when a node is inserted. Or when a comment is deleted. Or when a user logs in.

When writing an action, you first need to decide whether or not your action will need to know anything beyond the context in which it runs. For example, an action that prefixes the title of a node with "New! " does not need any other information; it just needs the node. Let's actually create that action a moment.

First, we need to describe the action to Drupal. We do that with hook_action_info.

<?php
 
function modulename_action_info {
  return array(
   
'modulename_prepend_new_action' => array(
      
'type' => 'node',
      
'description' => t('Prepend "New! " to the title of a node'),
      
'configurable' => FALSE,
      
'hooks' => array(
        
'nodeapi' => array('presave', 'insert', 'update', 'view'),
        
'comment' => array('insert'),
       )
    )
  );
}/
?>

The array returned is keyed by function name. So later on we'll create a function called modulename_prepend_new_action(), probably not the best name but let's roll with it.

The 'type' key declares what object type this action will work on. It will work on nodes, so it's a 'node' type of action, or a node action.

The 'description' will appear in the UI when the administrator is assigning actions to hooks on the "Assign actions" screen.

The 'configurable' key declares whether or not the action needs additional information to run. This action doesn't; it simply modifies the node it's been given. More about configurable actions in a minute.

The 'hooks' key determines where this action may be assigned. For example, this action running during the 'register' op of the user hook would make no sense. But running in the 'presave' op of the nodeapi hook, where it can modify the node object just before it is saved, makes perfect sense.

Now for the action itself. It's dead simple.

<?php
 
function modulename_prepend_new_action(&$node, $context) {
 
$node->title = t('New!') . ' ' . $node->title;
}
?>

What's in $context? Well, that's where Drupal puts any information that it thinks the action might want to use. Suppose we wanted this action to run when a comment is added. That's great. But internally, the comment is the object being worked with during the comment hook. So the actions module looks at the comment, finds the nid, loads the node, and passes that to the action (because it's a node action it expects a node). But since Drupal already has a copy of the comment object on hand, it puts it into the $context so that if the action wants to, it can look at it and make decisions based on that. So that's what $context is.

Now for the second kind of an action, a configurable action. That's like a stored procedure. For this we'll look at a comment action called Unpublish comment containing keyword(s). Obviously the action needs to know which keywords to compare with the comment body so it can figure out whether to unpublish the action or not. So it is a configurable action. Note that we don't use configurable actions directly; we make instances of them. We could make multiple action instances with descriptions like this:

Unpublish comment containing 'butter'
Unpublish comment containing 'blubber'
Unpublish comment containing 'flubber' or 'moose'

We would create these "action instances" on the "Manage actions" screen that the actions module provides. Here's the action_info() hook:

<?php
 
function comment_action_info() {
  return array(
   
'comment_unpublish_by_keyword_action' => array(
     
'description' => t('Unpublish comment containing keyword(s)'),
     
'type' => 'comment',
     
'configurable' => TRUE,
     
'hooks' => array(
       
'comment' => array('insert', 'update'),
      )
    )
  );
}
?>

Now we need to provide a way for the user to input which keywords to look for. So we create a comment_unpublish_by_keyword_action_form() function. Note we just append _form to the action function name. Here's the function, simplified:

<?php
 
function comment_unpublish_by_keyword_action_form($context) {
 
$form['keywords'] = array(
   
'#title' => t('Keywords'),
   
'#type' => 'textfield',
   
'#description' => t('The comment will be unpublished if it contains any of the keywords above. Use a comma-separated list of keywords. Example: funny, bungee jumping, "Company, Inc.".'),
   
'#default_value' => isset($context['keywords']) ? implode(',', $context['keywords']) : '',
  );
  return
$form;
}
?>

We could make a validation function (comment_unpublish_by_keyword_action_form_validate()) if we wanted to, but let's skip that and go right to the submission function. The taxonomy module already has a great implementation of turning comma-delimited keywords into an array, so we'll just use that. Code reuse!

<?php
 
function comment_unpublish_by_keyword_action_submit($form, $form_state) {
  return array(
'keywords' => taxonomy_explode_tags($form_state['values']['keywords']));
}
?>

Note that we return a keyed array of parameters needed by the action. These parameters will be available inside $context. So the action will need to look for $context['keywords'] and that's exactly what it does:

<?php
 
function comment_unpublish_by_keyword_action($comment, $context) {
  foreach (
$context['keywords'] as $keyword) {
    if (
strstr($comment->comment, $keyword)) {
     
db_query('UPDATE {comments} SET status = '. COMMENT_NOT_PUBLISHED .' WHERE cid = %d', $comment->cid);
     
watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject));
      break;
    }
  }
}
?>

There you have it. Try writing an action of your own!

Subscribe to RSS - Actions