I am working on a Notifications plugin, and after starting to write my notes down about how to do this, decided to just post them here. Please feel free to come make modifications and changes. Eventually I hope to post this on the Drupal handbook as well. Thanks. --Adrian
Sending automated e-mails from Drupal using Messaging and Notifications
To implement a notifications plugin, you must implement the following functions:
Use hook_messaging, hook_token_list and hook_token_values to create the messages that will be sent.
Use hook_notifications to create the subscription types
Add code to fire events (eg in hook_nodeapi)
Add all UI elements to allow users to subscribe/unsubscribe
Understanding Messaging
The Messaging module is used to compose messages that can be delivered using various formats, such as simple mail, HTML mail, Twitter updates, etc. These formats are called "send methods." The backend details do not concern us here; what is important are the following concepts:
TOKENS: tokens are provided by the "tokens" module. They allow you to write keywords in square brackets, [like-this], that can be replaced by any arbitrary value. Note: the token groups you create must match the keys you add to the $events-objects[$key] array.
MESSAGE KEYS: A key is a part of a message, such as the greetings line. Keys can be different for each send method. For example, a plaintext mail's greeting might be "Hi, [user]," while an HTML greeing might be "Hi, [user]," and Twitter's might just be "[user-firstname]: ". Keys can have any arbitrary name. Keys are very simple and only have a machine-readable name and a user-readable description, the latter of which is only seen by admins.
MESSAGE GROUPS: A group is a bunch of keys that often, but not always, might be used together to make up a complete message. For example, a generic group might include keys for a greeting, body, closing and footer. Groups can also be "subclassed" by selecting a "fallback" group that will supply any keys that are missing. Groups are also associated with modules; I'm not sure what these are used for.
Understanding Notifications
The Notifications module revolves around the following concepts:
SUBSCRIPTIONS: Notifications plugins may define one or more types of subscriptions. For example, notifications_content defines subscriptions for:
Threads (users are notified whenever a node or its comments change)
Content types (users are notified whenever a node of a certain type is created or is changed)
Users (users are notified whenever another user is changed)
Subscriptions refer to both the user who's subscribed, how often they wish to be notified, the send method (for Messaging) and what's being subscribed to. This last part is defined in two steps. Firstly, a plugin defines several "subscription fields" (through a hook_notifications op of the same name), and secondly, "subscription types" (also an op) defines which fields apply to each type of subscription. For example, notifications_content defines the fields "nid," "author" and "type," and the subscriptions "thread" (nid), "nodetype" (type), "author" (author) and "typeauthor" (type and author), the latter referring to something like "any STORY by JOE." Fields are used to link events to subscriptions; an event must match all fields of a subscription (for all normal subscriptions) to be delivered to the recipient.
The $subscriptions object is defined in subsequent sections. Notifications prefers that you don't create these objects yourself, preferring you to call the notifications_get_link() function to create a link that users may click on, but you can also use notifications_save_subscription and notifications_delete_subscription to do it yourself.
EVENTS: An event is something that users may be notified about. Plugins create the $event object then call notifications_event($event). This either sends out notifications immediately, queues them to send out later, or both. Events include the type of thing that's changed (eg 'node', 'user'), the ID of the thing that's changed (eg $node-nid, $user-uid) and what's happened to it (eg 'create'). These are, respectively, $event-type, $event-oid (object ID) and $event-action.
Warning: notifications_content_nodeapi also adds a $event-node field, referring to the node itself and not just $event-oid = $node-nid. This is not used anywhere in the core notifications module; however, when the $event is passed back to the 'query' op (see below), we assume the node is still present.
Events do not refer to the user they will be referred to; instead, Notifications makes the connection between subscriptions and events, using the subscriptions' fields.
MATCHING EVENTS TO SUBSCRIPTIONS: An event matches a subscription if it has the same type as the event (eg "node") and if the event matches all the correct fields. This second step is determined by the "query" hook op, which is called with the $event object as a parameter. The query op is responsible for giving Notifications a value for all the fields defined by the plugin. For example, notifications_content defines the 'nid', 'type' and 'author' fields, so its query op looks like this (ignore the case where $event_or_user = 'user' for now):
$event_or_user = $arg0;
$event_type = $arg1;
$event_or_object = $arg2;
if ($event_or_user == 'event' && $event_type == 'node' && ($node = $event_or_object->node) ||
$event_or_user == 'user' && $event_type == 'node' && ($node = $event_or_object)) {
$query[]['fields'] = array(
'nid' => $node->nid,
'type' => $node->type,
'author' => $node->uid,
);
return $query;
After extracting the $node from the $event, we set $query[]['fields'] to a dictionary defining, for this event, all the fields defined by the module. As you can tell from the presence of the $query object, there's way more you can do with this op, but they are not covered here.
DIGESTING AND DEDUPING:
Understanding the relationship between Messaging and Notifications
Usually, the name of a message group doesn't matter, but when being used with Notifications, the names must follow very strict patterns. Firstly, they must start with the name "notifications," and then are followed by either "event" or "digest," depending on whether the message group is being used to represent either a single event or a group of events.
For 'events,' the third part of the name is the "type," which we get from Notification's $event-type (eg: notifications_content uses 'node'). The last part of the name is the operation being performed, which comes from Notification's $event-action. For example:
notifications-event-node-comment might refer to the message group used when someone comments on a node
notifications-event-user-update to a user who's updated their profile
Hyphens cannot appear anywhere other than to separate the parts of these words.
For 'digest' messages, the third and fourth part of the name come from hook_notification's "event types" callback, specifically this line:
$types[] = array(
'type' => 'node',
'action' => 'insert',
...
'digest' => array('node', 'type'),
);
$types[] = array(
'type' => 'node',
'action' => 'update',
...
'digest' => array('node', 'nid'),
);
In this case, the first event type (node insertion) will be digested with the notifications-digest-node-type message template providing the header and footer, likely saying something like "the following [type] was created." The second event type (node update) will be digested with the notifications-digest-node-nid message template.
Data Structure and Callback Reference
$event
The $event object has the following members:
$event-type: The type of event. Must match the type in hook_notification::"event types". {notifications_event}
$event-action: The action the event describes. Most events are sorted by [$event-type][$event-action]. {notifications_event}.
$event-object[$object_type]: All objects relevant to the event. For example, $event-object['node'] might be the node that the event describes. $object_type can come from the 'event types' hook (see below). The main purpose appears to be to be passed to token_replace_multiple as the second parameter. $event-object[$event-type] is assumed to exist in the short digest processing functions, but this doesn't appear to be used anywhere. Not saved in the database; loaded by hook_notifications::"event load"
$event-oid: apparently unused. The id of the primary object relevant to this event (eg the node's nid).
$event-module: apparently unused
$event-params[$key]: Mainly a place for plugins to save random data. The main module will serialize the contents of this array but does not use it in any way. However, notifications_ui appears to do something weird with it, possibly by using subscriptions' fields as keys into this array. I'm not sure why though.
hook_notifications
op 'subscription types': returns an array of subscription types provided by the plugin, in the form $key = array(...) with the following members:
event_type: this subscription can only match events whose $event-type has this value. Stored in the database as notifications.event_type for every individual subscription. Apparently, this can be overiden in code but I wouldn't try it (see notifications_save_subscription).
fields: an unkeyed array of fields that must be matched by an event (in addition to the event_type) for it to match this subscription. Each element of this array must be a key of the array returned by op 'subscription fields' which in turn must be used by op 'query' to actually perform the matching.
title: user-readable title for their subscriptions page (eg the 'type' column in user/%uid/notifications/subscriptions)
description: a user-readable description.
page callback: used to add a supplementary page at user/%uid/notifications/blah. This and the following are used by notifications_ui as a part of hook_menu_alter. Appears to be partially deprecated.
user page: user/%uid/notifications/blah.
op 'event types': returns an array of event types, with each event type being an array with the following members:
type: this will match $event-type
action: this will match $event-action
digest: an array with two ordered (non-keyed) elements, "type" and "field." 'type' is used as an index into $event-objects. 'field' is also used to group events like so: $event-objects[$type]-$field. For example, 'field' might be 'nid' - if the object is a node, the digest lines will be grouped by node ID. Finally, both are used to find the correct Messaging template; see discussion above.
description: used on the admin "Notifications-Events" page
name: unused, use Messaging instead
line: deprecated, use Messaging instead
Other Stuff
This is an example of the main query that inserts an event into the queue:
INSERT INTO {notifications_queue}
(uid,
destination,
sid,
module,
eid,
send_interval,
send_method,
cron,
created,
conditions)
SELECT DISTINCT
s.uid,
s.destination,
s.sid,
s.module,
%d, // event ID
s.send_interval,
s.send_method,
s.cron,
%d, // time of the event
s.conditions
FROM {notifications} s
INNER JOIN {notifications_fields} f ON s.sid = f.sid
WHERE (s.status = 1)
AND (s.event_type = '%s') // subscription type
AND (s.send_interval >= 0)
AND (s.uid <> %d)
AND (
(f.field = '%s' AND f.intval IN (%d)) // everything from 'query' op
OR (f.field = '%s' AND f.intval = %d)
OR (f.field = '%s' AND f.value = '%s')
OR (f.field = '%s' AND f.intval = %d))
GROUP BY
s.uid,
s.destination,
s.sid,
s.module,
s.send_interval,
s.send_method,
s.cron,
s.conditions
HAVING s.conditions = count(f.sid)