triggers

Creating Custom Triggers in Drupal

27 comments

So after playing around for several hours tonight trying to build my own custom trigger I think I've finally figured it out. I thought I'd post it here for reference.

Though triggers are included in core in Drupal 6 it seems they're very poorly documented. The extent of the documentation I could find was on this trigger page in the handbook. Pro Drupal Development also has a chapter on it but unfortunately isn't really explicit enough for me with some parts.

Disclaimer: I don't really know what I'm doing, so please let me know if I've done anything wrong.

Prereqs:

  • Drupal 6
  • The triggers module should be enabled.
  • A commenter also pointed out that the triggerunlock module is also necessary. If you have your own custom actions this isn't needed though.
  • If you're following this example, you'll also need to CCK type called 'script'.

What I want to do is create a custom trigger in my module. Let's say I want to execute a custom action on my server whenever a node of a type script (as defined in the 'script' module) is created. The first step is to define hook_hook_info.

/**
 * Implementation of hook_menu_alter().
 */
function script_hook_info() {
  return array(
    'script' => array(
      'script' => array(
        'insert' => array(
          'runs when' => t('After script is created'),
        ),
      ),
    ),
  );
}

This will create a new tab in my triggers page that will be named after the name of my module as set in my .info file.

Two things I still need to do to make sure this trigger actually does anything... define hook_script and call it from a module_invoke or module_invoke_all whenever the triggering event happens and call the actions using actions_do(). Since these hooks should be executed on node insert I'll need to use hook_nodeapi().

/**
 * Implementation of hook_nodeapi().
 */
function script_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'insert':
      module_invoke_all('script', 'insert', $node);
      break;
  }
}

The second aspect (calling the associated actions) requires an implementation of hook_script() (the alternative being to just replace the module_invoke line with this functions body.

/**
 * Implementation of hook_script().
 */
function script_script($op, $node) {
  $aids = _trigger_get_hook_aids('script', $op);
  $context = array(
    'hook' => 'script',
    'op' => $op,
    'node' => $node,
  );
  actions_do(array_keys($aids), $node, $context);
}

The first line in the function is a call to _trigger_get_hook_aids which returns the list of action ID's that have been assigned to this trigger. This is a private function, but seems to be the only way to get the list of actions. The last line in the function calls actions_do(), which processes all the actions assigned to this trigger.

In this particular example you'll need a custom content type called 'script'.

And that's all there is to creating your own custom triggers.

Alternatively, I don't need to create a hook for the trigger, instead I could just piggyback off of the nodeapi hook, since this is a node operation.

The hook_hook_info looks a little different with:

function script_hook_info() {
  $info['script'] = array(
    'nodeapi' => array(
      'script' => array(
        'runs when' => t('After script is created'),
      ),
    ),
  );
  return $info;
}

And then just a slight change to hook_script() to change the hook from 'script' to 'nodeapi'.

/**
 * Implementation of hook_script().
 */
function script_script($op, $node) {
  $aids = _trigger_get_hook_aids('nodeapi', $op);
  $context = array(
    'hook' => 'nodeapi',
    'op' => $op,
    'node' => $node,
  );
  actions_do(array_keys($aids), $node, $context);
}

One other thing that was bugging me with triggers was the inability to only use them on specific cck types. There's no way to setup a trigger to only get called when an event happens to a single content type (without using the workflow module, which I found has some problems using variables in actions). Put an "if ($node->type == 'script')" somewhere appropriate, in my code I'd wrap that around the call to module_invoke_all(), for example:

function script_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'insert':
      if ($node->type == 'script') {
        module_invoke_all('script', 'insert', $node);
      }
      break;
  }
}

----

After writing this I found this blog post which also looks quite useful.

I've made some updates to this example, so hopefully it will work for everyone now.

You can download the example module here.