Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

Posted by: 
Dominique De Cooman

The rules module is a very powerful module that allows you to make things react in your site when certain events happen (like a comment has been made) under certain conditions (for example when the user has role x).

A lot of events, conditions and actions are allready out of the box or provided by some contrib modules who have rules integration.

But what to do when you have a module and you want to add rules support? First of all, why should you add rules support? Why not do everything in code?

Example

Let me explain this with an example. We have a custom module that allows administrators define packages which users can order (the packages are not nodes, why they are not is offtopic). So when a package is bought an event is fired. We also have a custom condition which allows to check on the type of the package. Finally we have a custom action that changes the status of the order made. (We have a database table with packages and one with orders of packages)

Now a site administrator can define a set of mails using these rules. For example a mail could be send to the acting user when he buys a subscription package or a system message could be set when the user buys a package that increases his amount of credits. For each type of package bought a different mail could be sent. You get the idea, imagine having to change in code the mail on this action, or the system messages everytime the site owner wants to have "Dear customer" instead of "Allmighty customer" on top of his mails/messages.

Now lets look at the code you need to have your custom rules.
There are 3 hooks you ll need to implement, so create a custom module and create an extra file called your_module.rules.inc, all our code rules related code will go in there.

Events

The first hook is hook_rules_event_info() and will defne our custom event.

<?php
/**
* Implementation of hook_rules_event_info().
* @ingroup rules
*/
function your_module_rules_event_info() {
  return array(
    
'your_module_package_bought' => array(
      
'label' => t('A package was bought'),
      
'module' => 'your_module',
      
'arguments' => array(
        
'acting_user' => array('type' => 'user''label' => t('The user who bought the package.')),
        
'package' => array('type' => 'package''label' => t('The purchased package.')),
      ),
    ),
  );
}
?>

What we have done is defined an action that registers the acting user and the package object. Let's trigger that action when we buy a package. (This function will be located in your_module.module)

<?php
function your_module_buy_package() {
  
//here the code for buying a package will be located
  //when that code returns that a package was bought trigger the rule
  
$order order_load($oid);//$oid will be the id of the order made
  
$package package_load($pid);//pid will be the id of the bought package
  
global $user;
  
rules_invoke_event('your_module_package_bought'$user$package$order);
  
watchdog('your_module',t('A Member !uid has ordered (order:!oid) package !pid', array('!uid' => $user->uid'pid' => $package->pid'oid' => $order->oid)));
}
?>

In the rules interface on the site you ll define your triggered rule by selecting this event.
Notice how we log the event in the watchdog. This is good practice because you want to follow up what happens on your site. You could also have the watchdog logging configured in the triggered rules interface when defining actions for your event, but I think watchdog logging is more something that belongs in code and it is something the site admin should not be able to configure (unlike mails and user messages).

Conditions

Now we ll define the condition so you can base your reaction on the the type of package that was bought. For this one you ll need to implement hook_condition_info()

<?php
/**
 * implementation of hook_rules_condition_info()
 */
function your_module_rules_condition_info() {
  return array(
    
'your_module_condition_package_type' => array(
      
'label' => t('Type of the package'),
      
'arguments' => array(
        
'package_id' => array('type' => 'value''label' => t('The type of the purchased package.')),
        
'package_type' => array('type' => 'string''label' => t('The type of the purchased package is')),
      ),
      
'module' => 'your_module',
    ),
  );
}

/**
 * Condition packages_condition_packagetype
 */
function your_module_condition_package_type($pid$type) {
  
$package package_load($pid);
  return (
$package->type == $type) ? true false;
}
?>

When the condition is assessed the function your_module_condition_package_type is executed using the argument pid (package id) provided by the event and the argument type defined in the interface.

The function will return true if the package type bought is the same as the package type defined in the interface.

Actions

A lot of other actions are allready defined. For our actions we 'll allready have the mail and message action available to us. We configure the "send a mail to a user" and to do this we'll need some tokens.

Replacement tokens

As you have noticed there are some handy replacement tokens we could use out of the box, like the acting user for example. We could then add a replacement token for the user name ([acting_user:user]). In the mail that token will be replaced by the username of the acting user.
In our example we could build a custom replacement token for the name of the package. Using the package id we could retieve the name of the package.

To get custom tokens in drupal we'll use the token api functions hook_token_values () and hook_token_list() (you ll put this piece of code in your_module.module)

The hook_token_list will provide data to the interface and the hook_token_values will replace the token [package:package_name] you'll put in your mails.

<?php
/**
 * Implementation of hook_token_list()
 */
function your_module_token_list($type 'all') {
  if (
$type == 'all' || $type == 'package') {
    
$tokens['your_module'] = array(
      
'package_name' => t('The name of the package'),   
    );
  }

  return 
$tokens;
}

/**
 * Implementation of hook token api
 */
function your_module_token_values($type$object NULL$options = array()) {
  
$tokens = array();
  if (
$object) {
    if (
$type == 'package') {
      
$tokens = array(
        
'package_name' => $object->name,
      );
    }
  }

  return 
$tokens;
}
?>

Only one funtion is missing to make the rules load our package the package_load function. This could look like this:

<?php
function package_load($pid) {
  
$package db_fetch_object("SELECT pid, name, type FROM {packages} WHERE pid = %d"$pid);
    
  return 
$package;
}
?>

In our invocation we gave the package as an argument, by doing so rules gave our package to the your_module_token_values() function in the $object argument so it could replace the package_name token with the real name of our token.

Custom actions

The final hook is hook_rules_action_info() which defines your custom action.

<?php
/**
 * Implementation of hook_rules_action_info().
 */
function packages_rules_action_info() {
  return array(
    
'your_module_action_change_order_status' => array(
      
'label' => t('Change the order status'),
      
'arguments' => array(
        
'order' => array('type' => 'value''label' => t('The order object.')),
      ),
      
'module' => 'your_module',
    ),
  );
}
?>

The function doing the action uses the settings variable. You can configure the value of the status by interface this way. If you dont want that just hardcode the value, this just demonstrates that you can have a form in the interface capturing custom values.

<?php
function your_module_action_change_order_status($oid) {
  
$q "UPDATE {orders} SET status = '%s' WHERE id = %d";
  
db_query($q$settings['status'], $oid);
  
//better would be to create an order_save function but you get the idea
}

/**
* Action drupal message configuration form.
*/
function your_module_action_change_order_status_form($settings, &$form) {
  
$settings += array('status' => '');

  
$form['settings']['status'] = array(
    
'#type' => 'textfield',
    
'#title' => t('Status'),
    
'#default_value' => $settings['status'],
    
'#description' => t('The order status will change to the one supplied here.'),
  );
}

?>

The order load could be something like this:

<?php
function order_load($oid) {
  
$order db_fetch_object("SELECT oid, status, uid FROM {orders} WHERE oid = %d"$oid);
    
  return 
$order;
}
?>

Conclusion

As demonstrated rules is a powerfull module, with an equally powerfull api capable of defining custom events, conditions and actions.
Rules integrates with all major modules (and the token api), and it is imho when developing modules, if possible, a point of integration worth to consider.

Extra

For more information about the api see the rules.api.php file in the rules package. It shows some altering capabilities and how to expose custom objects to rules.

Comments

Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

I'm working to add Flag and Rules integration to a gaming website I run to make it a bit more streamlined and I found this guide very enlightening. I was actually surprised that all three Rules hooks had such similar syntax. Thanks for laying it out so simply.

Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

i a gree with you please visit my site http://caramembuatblog.co/ help me

Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

Now, that's some real help here. I will test it right away.

Thank you!

Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

How can I get the nid value when I execute a cutsom action after the event create a new content ?

Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

Hi

Thanks for the tutorial.

The function your_module_rules_event_info() only defines 2 arguments but the rules_invoke_event('your_module_package_bought', $user, $package, $order); call additionally sents the $order variable. Is this correct ?

Thanksa again.

Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

Another great article.

We are making a switch to GIT so we need as much functionality out of the DB without affecting the user experience.

Cheers,
Amarjit.

Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

Need to create custom rule on submit action. If i click on apply job then message will be send to owner of the job with person name and their resume by using private message

Drupal custom Rules how to write your own events conditions, actions and custom object (+custom tokens)

is this great tutorial for Drupal 7? please do mention the version in your tutorials

Add new comment