Extending the subscriptions module: node reference subscriptions

Extending the subscriptions module can be very useful if you're feeling a little entrapped by its default capabilities. This is something we dealt with while working on a redesign for JAARS.org. We're still working on the project: look forward to a case study, another blog post, and the completed redesign over the next few weeks. But for now…

The subscriptions module provides a framework for notifying users about new content via email. Out of the box, however, it provides only two types of subscriptions: node subscriptions and content type subscriptions. What if you need users to be alerted when new nodes are published that are related via a node reference to the nodes they are subscribed to? Say, for instance, you want your users to be able to subscribe to a Location node, and would like them to be alerted when a new Story node is published that's related to one of their chosen Locations? In essence, this is a node reference subscription. We'll call it a Location subscription for our hypothetical use-case, though.

The first thing we do is create a node reference relationship between our subscribe-able node Location and our other node type Story. Let's call it field_subscribe, and add it to the Story content type.

Next let's define a permission for subscribing to Locations. We'll do this with a simple hook_permission(). This, and all of the rest of the code in this post, will get put in a custom module. We'll pretend that we're calling it mymodule, so the code will go in the file mymodule.module.

/**
 * Implementation of hook_permission()
 */
function mymodule_permisson() {
  return array(
    'locations subscribe' => array(
      'title' => t('Subscribe to Locations'), 
      'description' => t('Allows users to receive alerts when new Story nodes referenced to Location nodes are created.'),
    ),
  );
}

Now we need to extend the subscriptions module. We do this using hook_subscriptions($op, $arg0 = NULL, $arg1 = NULL, $arg2 = NULL), which needs to return three different data sets depending on the $op value.

The first $op we'll deal with is the queue $op, which allows modules to interfere with how subscriptions queues up events (or email alerts) to be processed. The return value for the queue $op is an array used by subscriptions to build the query which returns events that need to be processed. The array containing the query data is nested in an array who's keys identify their basic functionality. In $params['node']['field_subscribe'] for instance, node signifies which module or entity type this subscriptions extension deals with, and field_subscribe identifies which entity field we'll be dealing with. Here's the code that returns our $params variable:

/**
 * Implementation of hook_subscriptions($op, $arg0 = NULL, $arg1 = NULL, $arg2 = NULL)
 */
function mymodule_subscriptions($op, $arg0 = NULL, $arg1 = NULL, $arg2 = NULL) {
  if ($op == "queue") { 
    if ($arg0['module'] == 'node') {
      $node = $arg0['node'];
      $params['node']['field_subscribe'] = array(
        'join' => array('table' => 'field_data_field_subscribe', 'alias' => 'fs', 'on' => 's.value = fs.field_subscribe_nid'),
        'where' => array(array('fs.entity_id', $node->nid, '=')),
        'groupby' => 'fs.entity_id'
        );	
      return $params;
    }
  }
  // ... function continued below ...

The second $op we'll discuss is the fields $op. It returns the data needed by subscriptions_mail.cron.inc for sending emails to users. For our purposes the existing subscriptions functionality will work fine. Here we'll just define a subscription type and a custom mail key:

  elseif ($op == "fields") {
    return array('field_subscribe' => array(
      'mailvars_function' => '_subscriptions_content_node_mailvars',
      'mailkey' => 'subscriptions_content_node-field_subscribe',
      '!subs_type' => 'Locations'
    ));
  }
  // ... function continued below ...

Our final $op, types, returns the data necessary to build the subscriptions management interface.

The page element of the $types['location'] array indicates the callback which will return the form for managing subscriptions of the Location type. That form should simply include a list of all Locations the user is subscribed to with a checkbox by each for dropping the subscription. The callback will be passed the $account and $form variables. Writing code for creating the subscription management form is pretty simple; it won't be covered in this write-up.

The fields element accepts an array with the first value as the entity type / module the subscription interacts with, and the second value as the field it should recognize. We'll set the access element of our $types['location'] array to the custom permission we've already defined. Here's the code for the types $op:

  elseif ($op == "types") {
    $types['location'] = array(
      'title' => 'Locations',
      'access' => 'locations subscribe',
      'permission' => array(
        'title' => t('Subscribe to Locations'),
      ),
      'page' => 'mymodule_manage_locations',
      'fields' => array('node', 'field_subscribe'),
      'weight' => -10,
    );
    
    return $types;
  }
} // end of function

That's it! Now when your users subscribe to a Location node, they'll receive an email when a new Story related to that Location is published. Remember to create the callback mymodule_manage_locations($account, $form) to return the subscriptions management form. Otherwise your users won't be able to unsubscribe from Locations.

Code Drupal Drupal Planet

Read This Next