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.

Peanut Gallery

I think I'd rather read about

Lynn

I think I'd rather read about your massage.

Hi, I'm trying to get this

awolfey

Hi, I'm trying to get this working. First, it's a bit confusing because your module name and the name of the content type you're working with are identical. I'm not sure where you're using which.

Second, where are you telling it to apply the action only for your content type?

Third, where and how did you define the actions for this trigger?

Thanks

Thanks for your comment,

Scott Hadfield

Thanks for your comment, awolfey...

1. All of the code should go into the .module file. The only place that the content type matters is in this line "if ($node->type == 'script')", where you'll need to replace "script" with whatever the node type is you want the trigger to act on. If the trigger should be available for all content types, then you can ignore that completely.

2. That can go anywhere, really, as long as it prevents the action_do from being called. In my case I put it in the hook_nodeapi:

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

3. I just used the default D6 triggers/actions interface to set that up. Make sure the modules are enabled and then actions can be added under "site configuration" and triggers can be setup under "site building".

Does that all make sense?

Thanks for the quick

awolfey

Thanks for the quick response! I believe I've followed your instructions, but I see 'No available actions for this trigger' on my new tab on the triggers page. The action I want to apply is send email. I'm going through it again to see what I did wrong.

mobility scooters

Very impressive post. You have written rally a valuable information.

oh, odd... i'm not really

Scott Hadfield

oh, odd... i'm not really sure why that would happen, i don't remember doing anything special and i can see all the actions available to me. Possibly try clearing all of the caches and see if that helps.

Thanks for this; I've been

Anonymous

Thanks for this; I've been trying to figure out how to do this very thing. However, I can't get it working yet... Where do you put this code? You mention in the .module... Which .module? Trigger.module?

You need to create a custom

Scott Hadfield

You need to create a custom module just for the trigger (unless you already have a module where it makes sense to drop the code). If you use the first method then all three of the functions -- script_hook_info, script_nodeapi, and script_script all need to be in that file. In this example my module is called script.module.

Seems you need to create your

Anonymous

Seems you need to create your own module. Did this (surprised it worked; I only have a .module and .info file. Don't I need to implement a .install, etc?) but had the same error as awolfey: no actions available.

This module took care of that: http://drupal.org/project/triggerunlock

Thanks for your quick reply.

Anonymous

Thanks for your quick reply. Didn't notice it when I posted again.

The Triggeunlock module did allow me to select any action for my custom trigger, but the action never ran... I tried the same action on a built-in trigger and it ran fine.

I just want the ability to restrict actions to certain content types... I copied your first 3 functions above and only added "if ($node->type == 'someType') {" where you specified. Still can't get it to work.

Any help or push in the right direction would be much appreciated.

Thanks again for the very informative article and your quick response!

Sorry, yet one more question.

Anonymous

Sorry, yet one more question. While trying to get this to work (still no luck) I looked up the various hooks you implement. I can find hook_hook_info and hook_nodeapi, but don't see hook_script mentioned anywhere...

Reproducing the example. The

sti_drupal

Reproducing the example. The code don't send email action.

Update

Scott Hadfield

I've updated the post with some more details and information. Hopefully it works for everyone now. I didn't realize the triggerunlock module was necessary for this as I was using workflow module which provides similar functionality.

Also, if you write your own action, then this is not necessary, as you can define which triggers it applies to yourself.

Custom trigger not showing up on triggers page

Phillip

I'm trying to create a custom trigger and it doesn't show up on the triggers page for some reason. I've tried implementing hook_hook_info() correctly from multiple modules and they just don't work. Am I missing something?

Thanks.

Can't download module

cookiesunshinex

Can't download module file.

Forbidden

You don't have permission to access /sites/scotthadfield.ca/files/script.module on this server.
Apache/2.2.8 (Ubuntu) DAV/2 SVN/1.4.6 PHP/5.2.4-2ubuntu5.6 with Suhosin-Patch Server at scotthadfield.ca Port 80

thanks, i've renamed it to

Scott Hadfield

thanks, i've renamed it to script.module.txt and you should be able to grab the file now.

Thanks for the response. I

cookiesunshinex

Thanks for the response. I would like to create a custom trigger for when a specific node type of "event" is published.

It can appear on the "content" tab. The example you use above is only for when a node is saved. I looked at hook_nodeapi and I didn't see that there was a case for publish, so I'm not sure if what I have got is going to work.

Here's what I have so far

<?php

/**
* Implementation of hook_hook_info().
*/
function eventpublish_hook_info() {
$info['eventpublish'] = array(
'publish' => array(
'eventpublish' => array(
'runs when' => t('After Publishing an event node'),
),
),
);
return $info;
}

/**
* Implementation of hook_nodeapi().
*/
function eventpublish_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
switch ($op) {
case 'publish':
if ($node->type == 'event') {
module_invoke_all('eventpublish', 'publish', $node);
}
break;
}
}

/**
* Implementation of hook_script().
*/
function eventpublish_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);
}

What I am trying to do is generate an email action to the %author when a moderator publishes and event node.

I think what you need to do

Scott Hadfield

I think what you need to do is use the insert and update cases. Then check the value of 'status' on the node and compare it to the version of the node that's already saved in the database and see if it's changed from 0 to 1.

However, I think there's a trigger by default that's on node publish/unpublish, you should probably take a look at what that's doing.

The word 'trigger' is too ambiguous.

Drupal should have named this module something else. I want to write a module which sets up a MySQL trigger inside the database, not a PHP script which triggers an action. I see that there is a db_create_table function but no db_create_trigger. Anyone know if this is possible?

Hook naming convention

I was just struggling with the "'No available actions for this trigger'" problem in Actions 5.2.x for Drupal 5 (which is a backport of D6 triggers & actions - don't ask me why I'm doing development on a D5 site, it's a long story) and I finally figured out one very important point that's mentioned but not stressed in the documentation for hook_hook_info: Namely, that if you're implementing a trigger for a custom hook in a custom module:

* The outermost array key must be the name of your module.
* The next key represents the name of the hook that triggers the events, but for custom and contributed modules, it actually must be the name of your module.

What this means is that you must name your custom hook after your module... for example if your module's name is 'foo', then you'd create:

/** Implementation of hook_foo(). */
function foo_foo($op, $data) {
...
}

Once I switched to this convention, the actions pulldown appeared on my new tab as expected, no triggerunlock module required. I have not tried this in D6, but I expect it would work.

...also need to CCK type called 'script'.

Anonymous

can you give a link to this CCK type?

Nothing special

Scott Hadfield

The script CCK type is nothing special, all it needs to have is a title and body, no extra fields necessary (identical to page or story types).

Aaarg

The ambiguous Script module name, script content type name & script as a general keyword are quite confusing in this example. Nevertheless I gained some useful insight, thanks for the post.

Thanks, I found this useful

Ryan

Thanks, I found this useful when creating a Drupal 6 Trigger for first user login. Code here: http://www.boiledocean.com/content/trigger-first-user-login

Thanks for this post, very

furzey

Thanks for this post, very useful. I don't mean to nitpick but..

> you'll also need to CCK type called 'script'.
..
> can you give a link to this CCK type?

There is some confusion there, what is meant (I think) is a node content type called 'script', where 'script' is the machine readable name. It has nothing to do with CCK fields. Also:

...specific cck types....

I think means node content types. Also, different subject, if your module name is two words with an underscore e.g.

my_module_hook_info

then it won't show up in the Triggers page, this will:

mymodule_hook_info

-
thx again.

Sorry, ignore this in my last

furzey

Sorry, ignore this in my last comment:

"my_module_hook_info
then it won't show up in the Triggers page, this will:
mymodule_hook_info"

split module names works fine, I just needed to clear my menu cache then the tab in triggers showed up.

Nice information

Hi,

Thanks to share such a nice information.