You are here

notifications.module in Notifications 5

Notifications module

This is the base module of the notifications framework. It handles event processing, queueing, message composition and sending.

Different subscriptions types are provided by plug-in modules implementing hook_notifications() Most of the UI is implemented in notifications_ui module The messaging framework is used for message delivery Token module is used for token replacement in messages

This is based on the previous subscriptions module

Development Seed, http://www.developmentseed.org, 2007

File

notifications.module
View source
<?php

/**
 * @file
 * Notifications module
 *
 * This is the base module of the notifications framework. It handles event processing, queueing,
 * message composition and sending.
 * 
 * Different subscriptions types are provided by plug-in modules implementing hook_notifications()
 * Most of the UI is implemented in notifications_ui module
 * The messaging framework is used for message delivery
 * Token module is used for token replacement in messages 
 * 
 * This is based on the previous subscriptions module
 * 
 * Development Seed, http://www.developmentseed.org, 2007 
 *
 */

// Define some values for subscription status
// Blocked subscriptions, for blocked users
define('NOTIFICATIONS_SUBSCRIPTION_BLOCKED', 0);

// Enabled ones, will produce notifications
define('NOTIFICATIONS_SUBSCRIPTION_ACTIVE', 1);

// Temporarily disabled ones, maybe user on holidays
define('NOTIFICATIONS_SUBSCRIPTION_INACTIVE', 2);

/**
 * Implementation of hook_menu().
 */
function notifications_menu($may_cache) {
  global $user;

  // we need the user to to build some urls
  if ($may_cache) {

    // Administration. Override messaging main item
    $items[] = array(
      'title' => t('Messaging & Notifications'),
      'path' => 'admin/messaging',
      'callback' => 'messaging_admin_overview_page',
      'access' => user_access('administer messaging') || user_access('administer notifications'),
      'description' => t('Administration of messaging and notifications'),
    );

    // Notifications settings
    $items[] = array(
      'path' => 'admin/messaging/notifications',
      'title' => t('Notifications settings'),
      'description' => t('Site settings for user notifications.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'notifications_settings_form',
      'access' => user_access('administer site configuration'),
    );
    $items[] = array(
      'path' => 'admin/messaging/notifications/settings',
      'title' => t('Settings'),
      'weight' => -10,
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );

    /*
    $items[] = array(
      'path' => 'admin/messaging/notifications/subscriptions',
      'title' => t('Subscription types'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'notifications_admin_subscriptions_form',
      'type' => MENU_LOCAL_TASK,
    );
    */
    $items[] = array(
      'path' => 'admin/messaging/notifications/intervals',
      'title' => t('Intervals'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'notifications_send_intervals_form',
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/messaging/notifications/events',
      'title' => t('Events'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'notifications_admin_events_form',
      'type' => MENU_LOCAL_TASK,
    );

    // Notifications status and queue management
    $items[] = array(
      'path' => 'admin/messaging/notifications-status',
      'title' => t('Notifications status'),
      'description' => t('Manage notifications status queue'),
      'callback' => 'notifications_admin_status_page',
      'access' => user_access('administer notifications'),
    );
    $items[] = array(
      'path' => 'admin/messaging/notifications-status/overview',
      'title' => t('Overview'),
      'description' => t('Notifications queue overview.'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'admin/messaging/notifications-status/queue',
      'title' => t('Queue'),
      'description' => t('Manage notifications queue.'),
      'callback' => 'notifications_admin_queue',
      'type' => MENU_LOCAL_TASK,
    );

    // Subscribe links. For this items access will be checked later in the page
    $items[] = array(
      'path' => 'notifications/subscribe',
      'type' => MENU_CALLBACK,
      'callback' => 'notifications_page_subscribe',
      'access' => $user->uid,
    );

    // Unsubscribe links This page will need to work with anonymous users
    $items[] = array(
      'path' => 'notifications/unsubscribe',
      'type' => MENU_CALLBACK,
      'callback' => 'notifications_page_unsubscribe',
      'access' => TRUE,
    );
  }
  else {
    if (arg(0) == 'notifications' || arg(1) == 'notifications' || arg(2) == 'notifications' || arg(2) == 'notifications-status') {
      include_once drupal_get_path('module', 'notifications') . '/notifications.admin.inc';
    }
    if ($user->uid && arg(0) == 'user' && is_numeric(arg(1)) && ($user->uid == arg(1) && user_access('maintain own subscriptions') || user_access('administer notifications'))) {
      $account = $user->uid == arg(1) ? $user : user_load(array(
        'uid' => arg(1),
      ));
      $items[] = array(
        'path' => 'user/' . $account->uid . '/notifications',
        'type' => MENU_LOCAL_TASK,
        'title' => t('Notifications'),
        'callback' => 'notifications_page_user_overview',
        'callback arguments' => array(
          $account,
        ),
      );
      $items[] = array(
        'path' => 'user/' . $account->uid . '/notifications/overview',
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'title' => t('Overview'),
        'weight' => -10,
      );
    }
  }
  return $items;
}

/**
 * Implementation of hook_perms()
 * 
 */
function notifications_perm() {
  return array_merge(array(
    'administer notifications',
    'maintain own subscriptions',
  ));
}

/**
 * Implementation of hook_user().
 */
function notifications_user($type, $edit, &$user, $category = NULL) {
  switch ($type) {
    case 'delete':

      // Delete related data on tables
      notifications_delete_subscriptions(array(
        'uid' => $user->uid,
      ));
      break;
    case 'update':
      if (isset($edit['status'])) {
        if ($edit['status'] == 0) {

          // user is being blocked now
          // Delete pending notifications and block existing active subscriptions
          db_query('DELETE FROM {notifications_queue} WHERE uid = %d', $user->uid);
          db_query('UPDATE {notifications} SET status = %d WHERE status = %d AND uid = %d', NOTIFICATIONS_SUBSCRIPTION_BLOCKED, NOTIFICATIONS_SUBSCRIPTION_ACTIVE, $user->uid);
        }
        else {

          // User may be being unblocked, unblock subscriptions if any
          db_query('UPDATE {notifications} SET status = %d WHERE status = %d AND uid = %d', NOTIFICATIONS_SUBSCRIPTION_ACTIVE, NOTIFICATIONS_SUBSCRIPTION_BLOCKED, $user->uid);
        }
      }
      break;
    case 'form':
      if ((user_access('maintain own subscriptions') || user_access("admin users subscriptions")) && $category == 'account') {

        // Add settings into Messaging group
        $form = messaging_user($type, $edit, $user, $category);
      }
      break;
  }
}

/**
 * Implementation of hook_form_alter()
 * - Override title for messaging settings
 */
function notifications_form_alter($form_id, &$form) {
  if ($form_id == 'user_edit' && $form['_category']['#value'] == 'account' && (user_access('maintain own subscriptions') || user_access('administer notifications'))) {
    $form['messaging']['#title'] = t('Messaging and Notifications settings');
    $send_intervals = _notifications_send_intervals();
    $form['messaging']['notifications_send_interval'] = array(
      '#type' => 'select',
      '#title' => t('Default send interval'),
      '#options' => $send_intervals,
      '#default_value' => notifications_user_setting('send_interval', $form['_account']['#value']),
      '#disabled' => count($send_intervals) == 1,
      '#description' => t('Default send interval for subscriptions.'),
    );
  }
}

/**
 * Gets a user setting, defaults to default system setting for each
 * 
 * @param $name
 *   Setting name
 * @param $account
 *   Optional user account, will default to current user
 * @param $default
 *   Optional default to return if this is not set
 */
function notifications_user_setting($name, $account = NULL, $default = NULL) {
  global $user;
  $account = $account ? $account : $user;

  // Default send method is taken from messaging module
  if ($name == 'send_method') {
    return messaging_method_default($account);
  }
  $field = 'notifications_' . $name;
  if (isset($account->{$field})) {
    return $account->{$field};
  }
  else {
    return variable_get('notifications_default_' . $name, $default);
  }
}

/**
 * Process subscriptions events
 */
function notifications_event($event) {
  global $user;

  // Fill in event with default values, that can be changed by modules implementing hook_notifications('event trigger', ..)
  // Such modules can mark the event to be discarded changing the 'save' and 'queue' options
  $event += array(
    'uid' => $user->uid,
    'load_args' => '',
    'created' => time(),
    'module' => 'notifications',
    // Module that triggered the event
    'type' => '',
    // Object/event type
    'action' => '',
    // Action that happened to the object
    'params' => array(),
  );

  // Check whether we have to save and queue this event, defaults to yes if not set
  $info = variable_get('notifications_events', array());
  if (isset($info[$event['type']][$event['action']]) && !$info[$event['type']][$event['action']]) {

    // Do not store nor queue this event, can be changed by plug-in modules
    $event += array(
      'save' => FALSE,
      'queue' => FALSE,
    );
  }
  else {
    $event += array(
      'save' => TRUE,
      'queue' => TRUE,
    );
  }
  $event = (object) $event;

  // Notify other modules we are about to trigger some subscriptions event
  // Modules can do cleanup operations or modify event properties
  notifications_module_invoke('event trigger', $event);

  // Store event, unles marked not to be saved
  if ($event->save) {
    notifications_event_save($event);
  }

  // Send event to queue for subscriptions, unless marked not to
  if ($event->queue) {
    notifications_queue($event);
  }
  return $event;
}

/**
 * Queue events for notifications
 *
 * @param $event
 *   Event array.
 */
function notifications_queue($event) {
  global $notifications_send_inmediate;

  // Build query fields for this event type. If no arguments retrieved, skip this step
  if ($query_args = notifications_module_information('query', 'event', $event->type, $event)) {
    $query['args'] = array(
      $event->eid,
      $event->created,
      $event->type,
    );
    foreach ($query_args as $query_params) {
      $query = notifications_query_build($query_params, $query);
    }

    // We throw in all the conditions and check the number of matching conditions
    // that must be equal to the subscription conditions number
    $sql = 'INSERT INTO {notifications_queue} (uid, sid, module, eid, send_interval, send_method, cron, created, conditions) ' . 'SELECT DISTINCT s.uid, s.sid, s.module, %d, s.send_interval, s.send_method, s.cron, %d, s.conditions ' . 'FROM {notifications} s INNER JOIN {notifications_fields} f ON s.sid = f.sid ' . implode(' ', $query['join']);
    $sql .= " WHERE s.status = 1 AND s.event_type = '%s' AND s.send_interval >= 0 AND ((" . implode(') OR (', $query['where']) . ')) ';

    // Add one more condition if we don't send notifications on own posts
    if (!variable_get('notifications_sendself', 0)) {
      $sql .= 'AND s.uid != %d ';
      $query['args'][] = $event->uid;
    }

    // Some group by fields are not really needed but added for pgsql compatibility
    $sql .= 'GROUP BY s.uid, s.sid, s.module, s.send_interval, s.send_method, s.cron, s.conditions HAVING s.conditions = count(f.sid)';
    db_query($sql, $query['args']);
  }

  // Modules can do cleanup operations or modify the queue
  notifications_module_invoke('event queued', $event);

  // If immediate sending enabled, store eid for sending on page exit.
  if (variable_get('notifications_send_immediate', 0)) {
    $notifications_send_inmediate[] = $event->eid;
  }
}

/**
 * Implementation of hook_exit()
 * 
 * This is where the inmediate sending is done if enabled, so we are sure all other modules
 * have finished node processing when node update.
 */
function notifications_exit() {
  global $notifications_send_inmediate;
  if (!empty($notifications_send_inmediate)) {
    require_once drupal_get_path('module', 'notifications') . '/notifications.cron.inc';
    foreach ($notifications_send_inmediate as $eid) {
      notifications_process_rows(array(
        'cron' => 1,
        'eid' => $eid,
        'send_interval' => 0,
      ));
    }
  }
}

/**
 * Query builder for subscriptions
 * 
 * @param $params
 *   Array of query conditions
 */
function notifications_query_build($params, $base = array()) {
  $query = $base + array(
    'select' => array(),
    'join' => array(),
    'where' => array(),
    'args' => array(),
  );
  foreach ($params as $name => $elements) {
    if ($name == 'fields') {
      foreach ($elements as $field => $value) {

        // Handle array values with IN conditions
        if (is_array($value)) {
          $placeholders = array_fill(0, count($value), "'%s'");
          $query['where'][] = "f.field = '%s' AND f.value IN (" . implode(', ', $placeholders) . ")";
          $query['args'][] = $field;
          $query['args'] = array_merge($query['args'], $value);
        }
        else {
          $query['where'][] = "f.field = '%s' AND f.value = '%s'";
          $query['args'][] = $field;
          $query['args'][] = $value;
        }
      }
    }
    elseif (is_array($elements)) {
      $query[$name] = array_merge($query[$name], $elements);
    }
    else {
      $query[$name][] = $elements;
    }
  }
  return $query;
}

/**
 * Stores new events
 * 
 * @param $event
 *   Event object
 * @ TODO: Avoid to and from array conversion
 */
function notifications_event_save(&$event) {
  $event = (array) $event;
  $event['eid'] = db_next_id('{notifications_event}_eid');
  db_query("INSERT INTO {notifications_event} (eid, module, type, action, uid, created, params) VALUES (%d, '%s', '%s', '%s', %d, %d, '%s')", $event['eid'], $event['module'], $event['type'], $event['action'], $event['uid'], $event['created'], serialize($event['params']));
  $event = (object) $event;
}

/**
 * Get subscription for a given user
 * 
 * @param $uid
 *   User id
 * @param $event_type
 *   Event type
 * @param $oid
 *   Object id for caching. I.e. for a node it will be nid
 * @param $object
 *   Object to check subscriptions to. I.e. $node
 * 
 * @return
 *   Array of subscriptions for this user and object indexed by sid 
 */
function notifications_user_get_subscriptions($uid, $event_type, $oid, $object = NULL, $refresh = FALSE) {
  static $subscriptions;
  if ($refresh || !isset($subscriptions[$uid][$event_type][$oid])) {
    $subscriptions[$uid][$event_type][$oid] = array();
    $query_args = notifications_module_information('query', 'user', $event_type, $object);

    // Base query
    $query = array(
      'args' => array(
        $uid,
        $event_type,
      ),
    );
    foreach ($query_args as $query_params) {
      $query = notifications_query_build($query_params, $query);
    }

    // Build the query merging all the parts
    $sql = 'SELECT s.*, f.* FROM {notifications} s INNER JOIN {notifications_fields} f ON s.sid = f.sid ';
    if (!empty($query['join'])) {
      $sql .= implode(' ', $query['join']);
    }
    $sql .= " WHERE s.uid = %d AND event_type = '%s' ";
    if (!empty($query['where'])) {
      $sql .= " AND ((" . implode(') OR (', $query['where']) . '))';
    }
    $result = db_query($sql, $query['args']);
    while ($sub = db_fetch_object($result)) {
      if (!isset($subscriptions[$uid][$event_type][$oid][$sub->sid])) {
        $subscriptions[$uid][$event_type][$oid][$sub->sid] = $sub;
      }
      $subscriptions[$uid][$event_type][$oid][$sub->sid]->fields[$sub->field] = $sub->value;
    }
  }
  return $subscriptions[$uid][$event_type][$oid];
}

/**
 * Update or create subscription
 * 
 * This function checks for duplicated subscriptions before saving.
 * If a similar subscription is found it will be updated.
 * If no subscription is found and it is new, the sid will be added into the object.
 * 
 * @param $subscription
 *   Subscription object or array
 */
function notifications_save_subscription(&$subscription) {
  global $user;
  $subscription = (object) $subscription;
  $subscription->conditions = count($subscription->fields);
  $account = $subscription->uid ? user_load(array(
    'uid' => $subscription->uid,
  )) : $user;

  // Default values for fields: send_interval, send_method, etc...
  foreach (_notifications_subscription_defaults($account) as $field => $value) {
    if (!isset($subscription->{$field})) {
      $subscription->{$field} = $value;
    }
  }

  // The cron parameter will be always enabled because we are not managing push/pull methods anymore at this level.
  $subscription->cron = 1;
  if ($subscription->sid) {
    $op = 'update';
    db_query("UPDATE {notifications} SET uid = %d, type = '%s', event_type = '%s', conditions = %d, send_interval = '%d', send_method = '%s', cron = %d, module = '%s', status = %d WHERE sid = %d", $subscription->uid, $subscription->type, $subscription->event_type, $subscription->conditions, $subscription->send_interval, $subscription->send_method, $subscription->cron, $subscription->module, $subscription->status, $subscription->sid);
    db_query("DELETE FROM {notifications_fields} WHERE sid = %d", $subscription->sid);
  }
  elseif ($duplicate = notifications_get_subscriptions(array(
    'uid' => $subscription->uid,
    'type' => $subscription->type,
    'event_type' => $subscription->event_type,
    'module' => $subscription->module,
  ), $subscription->fields)) {

    // We've found duplicates, resolve conflict updating first, deleting the rest
    $update = array_shift($duplicate);
    $update->send_interval = $subscription->send_interval;
    $uddate->send_method = $subscription->send_method;
    $subscription = $update;

    // If there are more, delete, keep the table clean
    while (array_shift($duplicate)) {
      notifications_delete_subscription($duplicate->sid);
    }
    return notifications_save_subscription($subscription);
  }
  else {
    $op = 'insert';
    $subscription->sid = db_next_id('{notifications}_sid');
    db_query("INSERT INTO {notifications} (sid, uid, type, event_type, conditions, send_interval, send_method, cron, module, status) VALUES(%d, %d, '%s', '%s', %d, %d, '%s', %d, '%s', %d)", $subscription->sid, $subscription->uid, $subscription->type, $subscription->event_type, $subscription->conditions, $subscription->send_interval, $subscription->send_method, $subscription->cron, $subscription->module, $subscription->status);
  }

  // There may be subscriptions with no fields, some people are coding such plug-ins.
  if ($subscription->fields) {
    foreach ($subscription->fields as $name => $value) {
      db_query("INSERT INTO {notifications_fields} (sid, field, value) VALUES(%d, '%s', '%s')", $subscription->sid, $name, $value);
    }
  }
  notifications_module_invoke($op, $subscription);
}

/**
 * Get an individual subscription.
 *
 * @param $subs
 *   Either a subscription object or a subscription id (sid).
 * @param $refresh
 *   Force cache refresh
 * @return
 *   Subscriptions object.
 */
function notifications_load_subscription($subs, $refresh = FALSE) {
  static $cache = array();
  if (is_object($subs)) {
    $sid = $subs->sid;
    $subscription = $subs;
  }
  else {
    $sid = $subs;
  }
  if ($refresh || !array_key_exists($sid, $cache)) {
    if (!isset($subscription)) {
      $subscription = db_fetch_object(db_query("SELECT * FROM {notifications} WHERE sid = %d", $sid));
    }
    if ($subscription) {
      $subscription->fields = array();
      $result = db_query("SELECT * FROM {notifications_fields} WHERE sid = %d", $sid);
      while ($condition = db_fetch_object($result)) {
        $subscription->fields[$condition->field] = $condition->value;
      }
    }
    $cache[$sid] = $subscription;
  }
  return $cache[$sid];
}

/**
 * Delete subscription and clean up related data.
 * 
 * It also removes pending notifications related to that subscription 
 * 
 * @param $sid
 *   Id of subscriptin to delete
 */
function notifications_delete_subscription($sid) {
  foreach (array(
    'notifications',
    'notifications_fields',
    'notifications_queue',
  ) as $table) {
    db_query("DELETE FROM {" . $table . "} WHERE sid = %d", $sid);
  }
}

/**
 * Delete multiple subscriptions and clean up related data (pending notifications, fields).
 * 
 * Warning: It will delete also subscriptions with more conditions than the fields passed.
 * 
 * @param array $params
 *   Array of multiple conditions in the notifications table to delete subscriptions
 * @param array $conditions
 *   Array of multiple conditions in the notifications_fields table to delete subscriptions
 */
function notifications_delete_subscriptions($params, $conditions = array()) {
  $join = $where = $args = array();
  foreach ($params as $field => $value) {
    $where[] = 'n.' . $field . " = '%s'";
    $args[] = $value;
  }

  // Now we need to join once the fields table for each condition
  if ($conditions) {
    $index = 0;
    foreach ($conditions as $field => $value) {
      $alias = 'nf' . $index++;
      $join[] = "INNER JOIN {notifications_fields} {$alias} ON n.sid = {$alias}.sid";
      $where[] = "{$alias}.field = '%s'";
      $where[] = "{$alias}.value = '%s'";
      $args[] = $field;
      $args[] = $value;
    }
  }

  // Query notificatinons that meet these conditions and build an array
  $result = db_query('SELECT n.sid FROM {notifications} n ' . implode(' ', $join) . ' WHERE ' . implode(' AND ', $where), $args);
  $delete = array();
  while ($n = db_fetch_object($result)) {
    $delete[] = $n->sid;
  }

  // This is the actual deletion. We've fetched the values from the db so this needs no escaping.
  if ($delete) {
    $str_sids = implode(',', $delete);
    foreach (array(
      'notifications_fields',
      'notifications_queue',
      'notifications',
    ) as $table) {
      db_query("DELETE FROM {" . $table . "} WHERE sid IN ({$str_sids})");
    }
  }
}

/**
 * Get subscriptions that fit a set of conditions.
 *
 * @param $params
 *   Array of parameters for the query
 * @param $conditions
 *   Optional array of condition fields
 * @param $limit
 *   Whether to limit the result to subscriptions with exatly that condition fields
 * @param $key
 *   Optional key field to use as the array index. Will default to sid 
 * 
 * @return
 *   Array of subscriptions indexed by uid, module, field, value, author
 */
function notifications_get_subscriptions($params, $conditions = array(), $limit = TRUE, $key = 'sid', $pager = NULL) {

  // Build query
  $join = $where = array();
  if ($conditions) {
    if ($limit) {
      $params += array(
        'conditions' => count($conditions),
      );
    }
    $index = 0;
    foreach ($conditions as $name => $value) {
      $alias = "f{$index}";
      $join[] = "INNER JOIN {notifications_fields} {$alias} ON s.sid = {$alias}.sid ";
      $params["{$alias}.field"] = $name;
      if (!is_null($value)) {
        $params["{$alias}.value"] = $value;
      }
      $index++;
    }
  }
  foreach ($params as $field => $value) {
    $name = strstr($field, '.') ? $field : 's.' . $field;
    $where[] = is_numeric($value) && strstr($field, 's.') ? $name . ' = %d' : "{$name} = '%s'";
  }
  $sql = 'SELECT * FROM {notifications} s ' . implode(' ', $join) . ' WHERE ' . implode(' AND ', $where);
  if ($pager) {
    $sql .= ' ORDER BY s.sid';
    $result = pager_query($sql, $pager, 0, NULL, $params);
  }
  else {
    $result = db_query($sql, $params);
  }
  $subscriptions = array();
  while ($s = db_fetch_object($result)) {
    $subscriptions[$s->{$key}] = notifications_load_subscription($s);
  }
  return $subscriptions;
}

/**
 * Get info about subscription types
 *
 * @param $type
 *   String, the subscriptions type OPTIONAL
 * @param $field
 *   String, a specific field to retrieve info from OPTIONAL
 * @param $default
 *   Default value when querying a specific field
 * 
 * @return
 *   Information for a given field and type
 *   or information for a given field for all types
 */
function notifications_subscription_types($type = NULL, $field = NULL, $default = NULL) {
  static $types;
  if (!isset($types)) {
    $types = notifications_module_information('subscription types');

    // Variable overrides
    if ($settings = variable_get('notifications_subscription_types', NULL)) {
      foreach ($settings as $key => $value) {

        // Check, because there may be settings for types that have been disabled later
        if (isset($types[$key]) && is_array($value)) {
          $types[$key] = array_merge($types[$key], $value);
        }
      }
    }
    notifications_alter('subscription_types', $types);
  }
  if ($field && $type) {
    return isset($types[$type][$field]) ? $types[$type][$field] : NULL;
  }
  elseif ($field) {
    $return = array();
    foreach ($types as $id => $info) {
      $return[$id] = isset($info[$field]) ? $info[$field] : $default;
    }
    return $return;
  }
  elseif ($type) {
    return isset($types[$type]) ? $types[$type] : array();
  }
  else {
    return $types;
  }
}

/**
 * Invokes hook_notifications() with a single parameter or more but not needing
 * an object to be passed as reference.
 */
function notifications_module_information($op, $arg0 = NULL, $arg1 = NULL, $arg2 = NULL) {
  $object = NULL;
  return notifications_module_invoke($op, $arg0, $arg1, $arg2);
}

/**
 * Invokes hook_notifications() in every module.
 *
 * We cannot use module_invoke() for this, because the arguments need to
 * be passed by reference.
 */
function notifications_module_invoke($op, &$arg0, $arg1 = NULL, $arg2 = NULL) {
  $result = array();
  foreach (module_implements('notifications') as $module) {
    $function = $module . '_notifications';
    if ($return = $function($op, $arg0, $arg1, $arg2)) {
      $result = array_merge($result, $return);
    }
  }
  return $result;
}

/**
 * Implementation of hook_messaging()
 * 
 * This hook provides information about the mensaje templates this module uses and related tokens.
 * 
 * Depending on $op, this hook takes different parameters and returns different pieces of information:
 * 
 * - 'message groups'
 *   Get array of message groups, each of which will have one or more keys for different templates
 *   Each group should have a unique key, so it should start with the module name
 * - 'message keys'
 *   Get message template parts for a given group ($arg1)
 *   Return array of key => name for each part
 * - 'messages'
 *   Get default message templates for a given group ($arg1).
 *   It should return default texts, indexed by message key that will be the default templates
 *   These templates may be edited on the 'Messaging templates' page
 * - 'tokens'
 *   Get available tokens for a given message group and key ($arg1).
 *   Return array of token keys that will be available for this message templates
 *   The tokens themselves may be default tokens (provided by token module) or we can add new
 *   tokens implementing hook_token_list() and hook_token_value()
 * 
 * @param $op
 *   Operation, type of information to retrieve
 * @param $arg1, $arg2...
 *   Different parameters depending on $op
 */
function notifications_messaging($op, $arg1 = NULL, $arg2 = NULL, $arg3 = NULL, $arg4 = NULL) {
  switch ($op) {
    case 'message groups':

      // Generic notifications event
      $info['notifications-event'] = array(
        'module' => 'notifications',
        'name' => t('Notifications event'),
        'description' => t('Fallback for all Notifications events.'),
      );
      $info['notifications-digest'] = array(
        'module' => 'notifications',
        'name' => t('Notifications digest'),
        'description' => t('Common parts for Notifications digests.'),
      );
      return $info;
    case 'message keys':
      $type = $arg1;
      switch ($type) {
        case 'notifications-event':

          // Event notifications
          return array(
            'subject' => t('Subject for event notifications'),
            'header' => t('Header for event notifications'),
            'main' => t('Content for event notifications'),
            'footer' => t('Footer for event notifications'),
          );
        case 'notifications-digest':
          return array(
            'subject' => t('Subject for digested notifications'),
            'header' => t('Header for digested notifications'),
            'footer' => t('Footer for digested notifications'),
          );
      }
      break;
    case 'messages':
      $type = $arg1;

      // Event notifications
      if ($type == 'notifications-event') {
        return array(
          'subject' => t('Event notification for [user] from [site-name]'),
          'header' => t("Greetings [user],"),
          'main' => t("A item to which you are subscribed has been updated"),
          'footer' => array(
            t('This is an automatic message from [site-name]'),
            t('To manage your subscriptions, browse to [subscriptions-manage]'),
            t('You can unsubscribe at [unsubscribe-url]'),
          ),
        );
      }

      // Digested messages
      if ($type == 'notifications-digest') {
        return array(
          'subject' => t('[site-name] subscription update for [user]'),
          'header' => t("Greetings, [user].\n\nThese are your messages"),
          'main' => t("A [type] has been updated: [title]\n\n[event_list]"),
          'footer' => array(
            t('This is an automatic message from [site-name]'),
            t('To manage your subscriptions, browse to [subscriptions-manage]'),
          ),
        );
      }
      break;
    case 'tokens':
      $type = explode('-', $arg1);
      $tokens = array();

      // These are the token groups that will be used for this module's messages
      if ($type[0] == 'notifications') {
        $tokens = array(
          'global',
          'subscription',
          'user',
        );
        if ($type[1] == 'event') {
          $tokens[] = 'event';
        }
      }
      return $tokens;
  }
}

/**
 * Implementation of hook_token_values()
 * 
 * @ TODO: Work out event tokens
 */
function notifications_token_values($type, $object = NULL, $options = array()) {
  switch ($type) {
    case 'subscription':
      $values = array();
      if ($subscription = $object) {
        $link = notifications_get_link('unsubscribe', array(
          'sid' => $subscription->sid,
          'signed' => TRUE,
          'destination' => FALSE,
        ));
        $values['unsubscribe-url'] = url($link['href'], $link['query'], NULL, TRUE);
      }
      return $values;
    case 'user':
      if ($account = $object) {
        return array(
          'subscriptions-manage' => $account->uid ? url("user/{$account->uid}/notifications", NULL, NULL, TRUE) : '',
        );
      }
  }
}

/**
 * Implementation of hook_token_list(). Documents the individual
 * tokens handled by the module.
 */
function notifications_token_list($type = 'all') {
  $tokens = array();
  if ($type == 'user' || $type == 'all') {
    $tokens['user']['subscriptions-manage'] = t('The url for the current user to manage subscriptions.');
  }
  if ($type == 'subscription' || $type == 'all') {
    $tokens['subscription']['unsubscribe-url'] = t('The url for disabling a specific subscription.');
  }
  if ($type == 'event' || $type == 'all') {
    $tokens['event']['event-list'] = t('List of events for message digests');
    $tokens['event']['event-detail'] = t('Detailed information for event');
  }
  return $tokens;
}

/**
 * Get event types
 */
function notifications_event_types($type = NULL, $action = NULL) {
  static $info;
  if (!$info) {
    $types = notifications_module_information('event types');
    foreach ($types as $type_info) {
      $info[$type_info['type']][$type_info['action']] = $type_info;
    }
    notifications_alter('event_types', $info);
  }
  if ($action) {
    return isset($info[$type][$action]) ? $info[$type][$action] : array();
  }
  elseif ($type) {
    return isset($info[$type]) ? $info[$type] : array();
  }
  else {
    return $info;
  }
}

/**
 * Implementation of hook_cron()
 */
function notifications_cron() {
  require_once drupal_get_path('module', 'notifications') . '/notifications.cron.inc';
  notifications_process_run();
}

/**
 * Return url for subscriptions. Confirmation form is not optional.
 */
function notifications_get_link($type, $params) {
  global $user;
  $params += array(
    'uid' => $user->uid,
    'signed' => FALSE,
    'destination' => $_GET['q'],
  );
  switch ($type) {
    case 'subscribe':
      $elements = array(
        'subscribe',
        $params['uid'],
        $params['type'],
        implode(',', array_keys($params['fields'])),
        implode(',', $params['fields']),
      );
      break;
    case 'unsubscribe':
      $elements = array(
        'unsubscribe',
        $params['sid'],
      );
      break;
  }

  // Build query string
  $query = array();
  if ($params['destination']) {
    $query[] = 'destination=' . $params['destination'];
  }
  if ($params['signed']) {
    $query[] = 'signature=' . _notifications_signature($elements);
  }
  return array(
    'href' => 'notifications/' . implode('/', $elements),
    'query' => implode('&', $query),
  );
}

/**
 * Check access to objects
 * 
 * This will check permissions for subscriptions and events before subscribing
 * and before getting updates.
 * 
 * @param $type
 *   Type of object to check for access. Possible values:
 *   - 'event', will check access to event objects
 *   - 'subscription', will check access to subscribed objects
 */
function notifications_user_allowed($type, $account, $object = NULL) {

  // Invoke notifications hook and check for a FALSE return value
  $permissions = notifications_module_information('access', $type, $account, $object);
  if ($permissions) {
    return !in_array(FALSE, $permissions);
  }
  else {

    // If no module has anthing to say about access I guess it will be true
    return TRUE;
  }
}

/**
 * Implementation of notifications_hook()
 * 
 * Check access permissions to subscriptions
 */
function notifications_notifications($op, &$arg0, $arg1 = NULL, $arg2 = NULL) {
  switch ($op) {
    case 'access':
      if ($arg0 == 'subscription') {
        $account = $arg1;
        $subscription = $arg2;

        // First we check valid subscription type
        $access = FALSE;
        if ($subscription->type && ($info = notifications_subscription_types($subscription->type))) {

          // To allow mixed subscription types to work we dont have a fixed field list
          // Then check specific access to this type. Each type must have a permission
          if (!empty($info['access callback'])) {
            $access = call_user_func($info['access callback'], $account, $subscription);
          }
          elseif (!empty($info['access']) && user_access($info['access'], $account) || user_access('administer notifications', $account)) {

            // Check matching fields
            if (!array_diff($info['fields'], array_keys($subscription->fields))) {
              $access = TRUE;
            }
          }
        }
        return array(
          $access,
        );
      }
      break;
    case 'digest methods':

      // Return array of digesting engines
      $info['short'] = array(
        'type' => 'short',
        'name' => t('Short'),
        'description' => t('Produces one line per event, grouped by object'),
        'digest callback' => 'notifications_process_digest_short',
      );
      $info['long'] = array(
        'type' => 'long',
        'name' => t('Long'),
        'description' => t('Adds full information for each event'),
        'digest callback' => 'notifications_process_digest_long',
      );
      return $info;
  }
}

/**
 * List of send intervals. These may be overriden in a variable.
 */
function _notifications_send_intervals() {
  return variable_get('notifications_send_intervals', array(
    -1 => t('Never'),
    0 => t('Immediately'),
    3600 => t('Every hour'),
    43200 => t('Twice a day'),
    86400 => t('Daily'),
    604800 => t('Weekly'),
  ));
}

/**
 * Information about digesting method for a send interval.
 * 
 * @return array()
 *   Ditest information for that interval, or all the information if no interval
 */
function notifications_digest_method($send_interval = NULL, $refresh = FALSE) {
  static $digest_methods, $intervals;
  if (!isset($digest_methods) || $refresh) {

    // Method information
    foreach (notifications_module_information('digest methods') as $method) {
      $digest_methods[$method['type']] = $method;
    }

    // Mapping interval -> method
    $intervals = variable_get('notifications_digest_methods', array());
  }
  if (is_null($send_interval)) {
    return $digest_methods;
  }
  elseif (!empty($intervals[$send_interval]) && isset($digest_methods[$intervals[$send_interval]])) {
    return $digest_methods[$intervals[$send_interval]];
  }
  else {

    // Default, that will be 'short' if interval > 0, none otherwise
    return $send_interval > 0 ? $digest_methods['short'] : NULL;
  }
}

/**
 * List of send methods
 */
function _notifications_send_methods($account = NULL) {
  return variable_get('notifications_send_methods', messaging_method_list($account));
}

/**
 * Signature for url parameters
 */
function _notifications_signature($params) {
  return md5('notifications:' . drupal_get_private_key() . ':' . implode(':', $params));
}

/**
 * Default values for subscription
 */
function _notifications_subscription_defaults($account = NULL) {
  return array(
    'send_interval' => notifications_user_setting('send_interval', $account, 0),
    'send_method' => notifications_user_setting('send_method', $account, ''),
    'module' => 'notifications',
    'status' => 1,
  );
}

/**
 * Callback for module dependent data
 * 
 * Some data stored in the notifications system is meant to be processed by other modules and
 * this is indicated by a module column in the data.
 * 
 * This function calls the module function if available, defaulting to the notifications provided
 * function when not. The arguments are passed as is
 * 
 * @param $module
 *   Module name
 * @param $function
 *   Function name in module
 */
function notifications_callback() {
  $args = func_get_args();
  $module = array_shift($args);
  $function = array_shift($args);
  if ($module && function_exists($module . '_' . $function)) {
    $callback = $module . '_' . $function;
  }
  else {
    $callback = 'notifications_' . $function;
  }
  return call_user_func_array($callback, $args);
}

/**
 * This dispatch function hands off structured Drupal arrays to type-specific
 * _notifications_*_alter implementations. Modelled after Drupal 6 drupal_alter()
 * 
 * @param $type
 *   The data type of the structured array. 'form', 'links',
 *   'node_content', and so on are several examples.
 * @param $data
 *   The structured array to be altered.
 * @param ...
 *   Any additional params will be passed on to the called
 *   hook_notifications_$type_alter functions.
 */
function notifications_alter($type, &$data) {

  // Now, use func_get_args() to pull in any additional parameters passed into
  // the drupal_alter() call.
  $args = array(
    &$data,
  );
  $additional_args = func_get_args();
  array_shift($additional_args);
  array_shift($additional_args);
  $args = array_merge($args, $additional_args);
  foreach (module_implements('notifications_' . $type . '_alter') as $module) {
    $function = $module . '_notifications_' . $type . '_alter';
    call_user_func_array($function, $args);
  }
}

/**
 * PHP4 Compatibility
 * 
 * These modules are being developed and tested with PHP 5. 
 * 
 * However, patches for PHP 4.x compatibility will be welcomed here 
 * (as long as they don't break the PHP5 version or course).
 */
if (!function_exists('array_combine')) {
  function array_combine($arr1, $arr2) {
    $out = array();
    $arr1 = array_values($arr1);
    $arr2 = array_values($arr2);
    foreach ($arr1 as $key1 => $value1) {
      $out[(string) $value1] = $arr2[$key1];
    }
    return $out;
  }
}
if (!function_exists('array_diff_key')) {
  function array_diff_key() {
    $arrs = func_get_args();
    $result = array_shift($arrs);
    foreach ($arrs as $array) {
      foreach ($result as $key => $v) {
        if (array_key_exists($key, $array)) {
          unset($result[$key]);
        }
      }
    }
    return $result;
  }
}

Functions

Namesort descending Description
notifications_alter This dispatch function hands off structured Drupal arrays to type-specific _notifications_*_alter implementations. Modelled after Drupal 6 drupal_alter()
notifications_callback Callback for module dependent data
notifications_cron Implementation of hook_cron()
notifications_delete_subscription Delete subscription and clean up related data.
notifications_delete_subscriptions Delete multiple subscriptions and clean up related data (pending notifications, fields).
notifications_digest_method Information about digesting method for a send interval.
notifications_event Process subscriptions events
notifications_event_save Stores new events
notifications_event_types Get event types
notifications_exit Implementation of hook_exit()
notifications_form_alter Implementation of hook_form_alter()
notifications_get_link Return url for subscriptions. Confirmation form is not optional.
notifications_get_subscriptions Get subscriptions that fit a set of conditions.
notifications_load_subscription Get an individual subscription.
notifications_menu Implementation of hook_menu().
notifications_messaging Implementation of hook_messaging()
notifications_module_information Invokes hook_notifications() with a single parameter or more but not needing an object to be passed as reference.
notifications_module_invoke Invokes hook_notifications() in every module.
notifications_notifications Implementation of notifications_hook()
notifications_perm Implementation of hook_perms()
notifications_query_build Query builder for subscriptions
notifications_queue Queue events for notifications
notifications_save_subscription Update or create subscription
notifications_subscription_types Get info about subscription types
notifications_token_list Implementation of hook_token_list(). Documents the individual tokens handled by the module.
notifications_token_values Implementation of hook_token_values()
notifications_user Implementation of hook_user().
notifications_user_allowed Check access to objects
notifications_user_get_subscriptions Get subscription for a given user
notifications_user_setting Gets a user setting, defaults to default system setting for each
_notifications_send_intervals List of send intervals. These may be overriden in a variable.
_notifications_send_methods List of send methods
_notifications_signature Signature for url parameters
_notifications_subscription_defaults Default values for subscription

Constants