hook_notifications(): Defining and Reacting to Notifications Information (2.x)

Last updated on
30 April 2025

This page explains how to implement hook_notifications(), which is the main integration point for most of what happens in the Notifications module. If you haven't already, you should probably start at Adding new notification events and subscription types. You can also use hook_notifications() to react when Notifications takes certain actions.

Start by copying this template into your module, replacing MODULENAME with your module's name. Each case is described briefly inline and in detail below. Parameters for each case are described inline.

/**
 * Implementation of hook_notifications().
 */
function MODULENAME_notifications($op, &$arg0 = NULL, $arg1 = NULL, $arg2 = NULL) {
  switch ($op) {

    /**
     * DEFINITIONS
     */
    // SUBSCRIPTIONS
    case 'subscription types': // Kinds of subscriptions a user can add, e.g. "Subscribe to thread" and "Subscribe to stream"
      $types = array();
      return $types;

    case 'subscription fields': // Describe important properties of an object a subscription may be against.
                                // Used to get info about the fields used in queries, not to actually build queries.
                                // Also used to display options for adding an arbitrary subscription.
      $fields = array();
      return $fields;

    case 'names': // Determine the administrative name of a subscription and store it in the subscription's "names" attribute
      $subscription = &$arg0; // The subscription object
      break;

    // EVENTS
    case 'event load': // Load the objects relevant to an event and store them in the event object. Called before sending messages in order to evaluate tokens in the message templates, so objects' keys should correspond to token types.
      $event = &$arg0; // The event object
      break;

    case 'event types': // Define the events that can trigger a status-related notification
      $types = array();
      return $types;

    case 'query': // Describe which field values identify an object so that Notifications can detect whether a subscription exists for it.
      $task = $arg0; // Either 'user' or 'event'
      $event_type = $arg1; // The event type
      $object = $arg2; // The event object if $task == 'event', or the (node/user/etc.) object being subscribed to if $task == 'user'
      break;

    // SUBSCRIBE LINKS
    case 'user options': // Describe the links that should show up on the user for subscribing to their posts
      $account = $arg0; // The subscribing user
      $author = $arg1; // The user being subscribed to
      $options = array();
      return $options;

    case 'node options': // Describe the links that should show up on the node for subscribing to it
      $account = $arg0; // The subscribing user
      $node = $arg1; // The user being subscribed to
      $options = array();
      return $options;

    // MISCELLANEOUS
    case 'digest methods': // Describe new digest formats. Default ones include "short" and "long."
      $info = array();
      return $info;


    /**
     * REACTIONS
     */
    case 'access': // Determine whether a user has access to a subscription or to be notified for an event
      $op = $arg0; // Either 'event' or 'subscription'
      $account = &$arg1; // The user whose access to this subscription is being evaluated
      $object = &$arg2; // the event object if $op == 'event' and the subscription object if $op = 'subscription'
      $access = TRUE;
      return array($access); // Return the boolean in an array to make merging the result with other modules easier

    case 'insert': // Allows reacting when a subscription is saved
      $subscription = $arg0; // The subscription object
      break;

    case 'update': // Allows reacting when a subscription is updated
      $subscription = $arg0; // The subscription object
      break;

    case 'event trigger': // Allows modifying the event object or reacting when an event occurs
      $event = $arg0; // The event object
      break;

    case 'event queued': // Allows reacting when an event has been added to the notifications queue
      $event = $arg0; // The event object
      break;

    default:
      break;
  }
}

The first parameter, $op, is the "operation" that the Notifications module is performing when your hook_notifications() implementation gets called. As noted above, the other parameters depend on the value of $op. Each operation is described in detail below.

Like the other steps of the Adding new notification events and subscription types tutorial, the cases are arranged here in an order that should make them easiest to understand. However, there are inter-dependent parts, so you may need to make an educated guess about certain values and then return and adjust them once you've fully defined what they mean.

case 'subscription types'
Define new types of subscriptions a user can add. Examples of subscription types include "Subscribe to thread" and "Subscribe to posts by this user." You should return an array of subscription types, where each subscription type is an associative array.

Notice that we have to say what event type is associated with each subscription even though we haven't really defined our event types yet. Now's as good a time as any -- go ahead and decide what event type(s) you want. Event types are, sensibly enough, mostly used to tell what type of event you're dealing with in other $op cases. They typically correspond to the kind of object you want to get notifications about, and you will typically have one event type per object type you want to keep track of. The only event type used in the core Notifications package is "node."

      $types = array();
      $types['thread'] = array( // "thread" is the subscription type
        'event_type' => 'node',
        'title' => t('Thread'),
        'access' => 'subscribe to content', // the permission required to use this subscription type
        'page callback' => 'notifications_content_page_thread', // the callback for the page where users can see their subscriptions of this type
        'user page' => 'user/%user/notifications/thread', // the menu path where users can see their subscriptions of this type
        'fields' => array('nid'), // The identifying field(s) of the object that corresponds to the subscription's event type. Typically the primary key of the table in which the object is stored, although field names don't actually have to correspond to actual database columns at all. You will provide more information about these fields in the "subscriptions fields" case.
        'description' => t('Subscribe to all changes and comments for a thread.'),
        'disabled' => FALSE, // Usually you will determine whether a subscription is disabled based on the setting you created for your subscription types in Part 1 of this tutorial.
      );
      return $types;

Now that you've done this, remember that we had to make use of subscription types in Part 1 of this tutorial in order to create settings pages for them -- even though we hadn't defined our subscription types yet. Well, now you've defined your subscription types, so go back to your hook_menu()/settings pages to make sure the subscription types you used there are the same as the ones you just defined.

case 'subscription fields'
Describe important properties of the object your subscription types let people subscribe to, typically corresponding with the event type of your subscription type. In other words, if your subscription type lets people subscribe to nodes, then for this $op you will define the fields that uniquely identify a node (namely, the nid). This is used to get information about which fields to use in queries when determining whether a user already has a given subscription. It is also used to display options for adding an arbitrary subscription. You can actually define as many fields as you want here, but the only ones that will get used are the ones that correspond to the fields property of the relevant subscription.

There are two kinds of fields: those that can have arbitrary values (like a node ID) and those that have predetermined values (like a node type). These fields will have different properties used to determine which values to accept on when adding a custom subscription of the applicable type. Fields that can accept arbitrary values should have autocomplete callbacks, while fields that have predetermined values need just one callback to retrieve their options. Give your callbacks appropriate names for now and we will address how to implement them after we define the fields.

      $fields['nid'] = array( // "nid" is the name of the field that corresponds with values in the "fields" property of subscriptions
        'name' => t('Node'), // The friendly name of the field, used as the title for this field when adding custom subscriptions
        'field' => 'nid', // should be the same as the field name
        'type' => 'int', // the data type of the field, either "int" or "string"
        'autocomplete path' => 'notifications/autocomplete/node/title', // the callback page to use when autocompleting
        'autocomplete callback' => 'notifications_node_nid2autocomplete', // the callback function used when autocompleting
        'format callback' => 'notifications_node_nid2title', // the function that converts autocomplete values into "friendly" names
        'value callback' => 'notifications_node_title2nid', // the function that converts autocomplete "friendly" names into values
      );
      $fields['type'] = array(
        'name' => t('Node type'),
        'field' => 'type',
        'type' => 'string',
        'options callback' => 'notifications_content_types_callback', // the function that returns the possible options for this field
      );

Alright, now we need to define our callbacks. Let's start with the "type" field because it's easier: we just have to define a callback that returns an associative array mapping machine names of our options to human-readable values. For example, on a very simple site, the callback might return something like this:

  array(
    'page' => t('Page'),
    'story' => t('Story'),
  );

The code you need to write to return such an array will vary depending on the type of content you're dealing with -- just make sure that if you allow disabling subscriptions to some types that you should not include disabled types in the array you return.

On to the nid field. There's a lot more you need to do for this one. If you really want to, you can cheat a little bit and pass FALSE instead of a function name for the callback values. If you do that you won't have to do all the work to implement the callbacks but your fields won't be autocompleted when a user adds a custom subscription of your type.

First we need to add an autocomplete page to our hook_menu() implementation. Here's an example from the Notifications Content submodule:

  $items['notifications/autocomplete/node/title'] = array(
    'title' => 'Node title autocomplete',
    'page callback' => 'notifications_node_autocomplete_title',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'file' => 'notifications.node.inc',
  );

Note that the page callback for our menu item is not the same as the autocomplete callback for our field type. The page callback works the same as other autocomplete functions -- it accepts a $string as a parameter, gathers an array of results with similar names/titles keyed by unique identifiers, and calls drupal_json on that array. (Make sure the unique identifiers really are unique so that you can look up the titles in reverse.) The included Notifications Content module is not a very good example here, so just take a look at the core user_autocomplete() and taxonomy_autocomplete() functions. Be aware that taxonomy_autocomplete() allows multiple values in the autocomplete field, while you should only accept a single value.

The function we designated as the field's autocomplete callback is sort of similar: given a uniquely identifying value (like a nid) it should return the string that will actually get displayed to the user in the autocomplete field (like a node title). It should also be possible to take that string and go in reverse to find the uniquely identifying value. But before you implement your autocomplete callback, note that the format callback is literally exactly the same except that it has an additional $html parameter that determines whether the returned value should be HTML-safe or not.

/**
 * An example (simplified) format callback.
 *
 * Given an ID, returns a name/title to display in the autocomplete field.
 *
 * @param $id
 *   The ID of the object the user is looking for.
 * @param $html
 *   Whether HTML is allowed in the return value or not.
 * @param $subscription_type
 *   The type of subscription the user is attempting to add.
 * @return
 *   A human-readable value to display in the autocomplete field.
 */
function notifications_node_nid2title($id, $html = FALSE, $subscription_type = '') {
  $node = node_load($id);
  return $html ? $node->title : check_plain($node->title);
}

/**
 * An example autocomplete callback.
 */
function notifications_node_nid2autocomplete($id, $subscription_type = '') {
  return notifications_node_nid2title($id, FALSE, $subscription_type);
}

The value callback does the same thing in reverse: it takes a name/title and returns an ID. It is called when the "Add custom subscription" form is submitted to validate that an acceptable value was entered.

/**
 * The value callback for the nid field.
 *
 * @param $name
 *   The value the user entered in the autocomplete field.
 * @param $field
 *   A string identifying the field for use with form_set_error().
 * @param $subscription_type
 *   The type of subscription the user is attempting to add.
 * @return
 *   The ID of the specified object if the object exists.
 */
function notifications_content_author_uid($name, $field = NULL, $subscription_type = '') {
  if ($account = user_load(array('name' => $name))) {
    return $account->uid;
  }
  elseif ($field) {
    form_set_error($field, t('User name not found.'));
  }
}

That's a lot of code for one little field...

case 'names'
This one's a lot easier. Given a subscription object, you need to determine a "friendly" name for it which will be used when a user looks at a list of their subscriptions. The subscription object has a "fields" property which at this stage is an associative array where the keys correspond to the field types you defined in the "subscription fields" case and the values are the actual values of those fields for this subscription (i.e. an actual node ID, not the field definition for the nid). Once you've determined a name for the subscription, store it in the "names" property of the subscription, which is an associative array where the keys correspond to subscription types and the values correspond to the human-friendly name.
      $subs = &$arg0; // The subscription object is passed as the first argument. Be sure to take it by reference.
      if ($subs->event_type == 'node') { // Only specify the name for subscriptions you've defined.
        if (!empty($subs->fields['nid']) && ($node = node_load($subs->fields['nid']))) { // check if you can load the object the subscription is for
          $subs->names['thread'] = t('Thread: @title', array('@title' => $node->title)); // store the friendly name in the "names" array
        }
      }
case 'event load'
Load the objects relevant to an event and store them in the event object so they will be available later. This is called before sending messages in order to evaluate tokens in the message templates, so the keys of the objects array should correspond to token types (where the value is the object, or the $data parameter passed to hook_token_values()). You will also have these objects available in the "access" case (explained later).

To assist in loading the relevant objects, the event object has a "params" property which is an associative array where the keys correspond to the fields you defined in the "subscription fields" case and the values are the values of those fields for this event.

      $event = &$arg0; // The first parameter is the event object
      if ($event->type == 'node') { // Only operate on events relative to the objects you're working with
        if (!empty($event->params['nid']) && empty($event->objects['node'])) {
          if ($node = node_load($event->params['nid'])) {
            $event->objects['node'] = $node;
          }
          else { // Node not available anymore, mark event for deletion
            $event->delete = TRUE;
          }
        }
      }
case 'event types'
Define new events for which a user can be notified. Note that this is not the same as $event->type and $event_type elsewhere in the code! What you're really doing here is closer to defining the $event object itself. For example, below we define events for creating a node, updating a node, and commenting on a node.

A note about the "digest" key: The first element of that array is the key you used in the "event load" case for the object relevant to this event, and the second element is the field of that object by which digest content should be grouped. See nofitications_digest_event_info() (yes, strangely it's spelled wrong everywhere it's used) and notifications_process_digest_short() in notifications.cron.inc if you want to understand the details.

      $types = array();
      $types[] = array(
        'type' => 'node', // This is actually the event type as used elsewhere in the code.
        'action' => 'insert', // Note which actions you use -- you'll need them again later. Use hyphens instead of spaces and underscores.
        'name' => t('New content of type [type-name] has been submitted'), // It seems that this never gets used anywhere?
        'line' => t('[type-name] [title] by [author-name]'), // The fallback default text for digest lines for these events
        'digest' => array('node', 'type'), // array('token type', 'field to group by')
        'description' => t('Node creation'), // The description of this event used in the list of events for which to enable/disable subscriptions at admin/messaging/notifications/events
      );
      $types[] = array(
        'type' => 'node',
        'action' => 'update',
        'name' => t('[type-name]: [title]'),
        'line' => t('The [type-name] has been updated'),
        'digest' => array('node', 'nid'), 
        'description' => t('Node update'),
      );      
      $types[] = array(
        'type' => 'node',
        'action' => 'comment',
        'name' => t('[type-name]: [title]'),
        'line' => t('New comment by [comment-author-name]: [comment-title]'), 
        'digest' => array('node', 'nid'),
        'description' => t('Node comment'),
      );
      return $types;
case 'query'
Provide values for the fields necessary to identify an object so that Notifications can detect whether a subscription exists for it. This gets called twice, once with $task == 'user' and once with $task == 'event'. (You can also include things other than fields that are compatible with Notifications' query-building system. If you want to dive down that rabbit hole, take a look at notifications_queue() and notifications_user_get_subscriptions() [which show examples of query arrays] as well as notifications_query_build() [which breaks down those arrays into SQL] in notifications.module.)
      $task = $arg0; // Either 'user' or 'event'
      $event_type = $arg1; // The event type
      $object = $arg2; // The event object if $task == 'event', or the (node/user/etc.) object being subscribed to if $task == 'user'
      // Based on tracing the code, I think that $arg2->node is actually the nid, not the node -- but somehow this works.
      if ($arg0 == 'event' && $arg1 == 'node' && ($node = $arg2->node) ||
          $arg0 == 'user' && $arg1 == 'node' && ($node = $arg2)) {
        $query[]['fields'] = array(
            'nid' => $node->nid,
            'type' => $node->type,
            'author' => $node->uid,
        );
        return $query;
      }
case 'user options'
Describe the links that should show up on the user's profile for subscribing to their posts. Though not shown in the example below, you should make sure that you only display links enabled in the settings you created in Part 1 of this tutorial.
      $account = $arg0; // The subscribing user
      $author = $arg1; // The user being subscribed to
      $options = array();
      $options[] = array(
        'name' => t('All posts by @name', array('@name' => $author->name)), // the text of the "subscribe" link
        'type' => 'node', // the event type of this subscription
        'fields' => array('uid' => $author->uid), // the field identifying the user whose posts are being subscribed to
      );
      return $options;
case 'node options'
Like the "user options" case, you should describe the links that should show up on the node for subscribing to it (and to things related to it). Though not shown in the example below, you should make sure that you only display links enabled in the settings you created in Part 1 of this tutorial.
      $account = $arg0; // The subscribing user
      $node = $arg1; // The user being subscribed to
      $options = array();
      $options[] = array(
        'name' => t('This post'),
        'type' => 'thread', // the subscription type
        'fields' => array('nid' => $node->nid),
      );
      return $options;
case 'digest methods'
Describe new digest formats. Default ones include "short" and "long." This isn't related to either the subscriptions or events you've just defined, and it's very likely that you won't need to implement it.
      $info = array();
      $info['short'] = array(
        'type' => 'short',
        'name' => t('Short'),
        'description' => t('Produces one line per event, grouped by object'),
        'digest callback' => 'notifications_process_digest_short',
      );
      return $info;
case 'access'
Determine whether a user has access to a subscription or to be notified by an event. Return array(TRUE) if access is permitted and array(FALSE) if access is denied (the boolean value is wrapped in an array to make it easier to combine the result with other hook_notifications() implementations).

Usually users should be permitted to subscribe if they have the appropriate "subscribe" permission and they have permission to view the subscribed object.

      $op = $arg0; // Either 'event' or 'subscription'
      $account = &$arg1; // The user whose access to this subscription is being evaluated
      $object = &$arg2; // the event object if $op == 'event' and the subscription object if $op = 'subscription'
      $access = TRUE;
      // This is a simplified version of the implementation in notifications_content.module.
      if ($type == 'event' && $object->type == 'node') {        
        if (!empty($object->objects['node'])) {
          $access = notifications_content_node_allow($account, $object->objects['node']);
        }
      }
      elseif ($type == 'subscription') {
        $access = TRUE;
        if (!empty($object->fields['nid'])) {
          if ($node = node_load($object->fields['nid'])) {
            $access =  notifications_content_node_allow($account, $node);
          }
          else {
            $access = FALSE;
          }
        }
      }
      return array($access); // Return the boolean in an array to make merging the result with other modules easier
case 'insert'
A new subscription has just been saved. You can react to this if you need to store extra data about the subscription. $arg0 is the $subscription object.
case 'update'
A subscription has just been updated. You can react to this if you need to store extra data about the subscription. $arg0 is the $subscription object.
case 'event trigger'
A new event has just been saved. You can react to this if you need to store extra data about the event. $arg0 is the $event object.
case 'event queued'
An event has just been updated. You can react to this if you need to store extra data about the event. $arg0 is the $event object.

That's it for hook_notifications. If you've made it this far, head back to the Adding new notification events and subscription types tutorial to finish your plugin.

If you'd like to look at other hook_notifications() implementations, there are two in the Notifications package in notifications_content.module and notifications_tags.module, plus one provided by the Facebook-style Statuses module's Notifications Integration submodule in fbss_notifications.module. It's probably a good idea to do this just for a sanity check that this documentation is up-to-date.

Help improve this page

Page status: Not set

You can: