'Messaging & Notifications',
'access arguments' => array('administer notifications'),
'description' => 'Administer and configure messaging and notifications',
'page callback' => 'system_admin_menu_block_page',
'file' => 'system.admin.inc',
'file path' => drupal_get_path('module', 'system'),
);
$items['admin/messaging/subscriptions'] = array(
'title' => 'Subscriptions',
'description' => 'Manage users notifications.',
'page callback' => 'notifications_admin_status_page',
'access arguments' => array('administer notifications'),
'file' => 'notifications.admin.inc',
);
$items['admin/messaging/subscriptions/overview'] = array(
'title' => 'Overview',
'description' => 'Subscriptions overview.',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
'file' => 'notifications.admin.inc',
);
$items['admin/messaging/subscriptions/admin'] = array(
'title' => 'Administer',
'description' => 'Administer subscriptions.',
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array('notifications_admin_subscriptions'),
'access arguments' => array('administer notifications'),
'file' => 'notifications.admin.inc',
);
$items['admin/messaging/subscriptions/queue'] = array(
'title' => 'Queue',
'description' => 'Notifications queue.',
'page callback' => 'notifications_admin_queue',
'type' => MENU_LOCAL_TASK,
'access arguments' => array('administer notifications'),
'file' => 'notifications.admin.inc',
);
// Site settings
$items['admin/messaging/notifications'] = array(
'title' => 'Notifications Settings',
'description' => 'Site settings for user notifications.',
'page callback' => 'drupal_get_form',
'page arguments' => array('notifications_settings_form'),
'access arguments' => array('administer site configuration'),
'file' => 'notifications.admin.inc',
);
$items['admin/messaging/notifications/settings'] = array(
'title' => 'General',
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
'file' => 'notifications.admin.inc',
);
$items['admin/messaging/notifications/intervals'] = array(
'title' =>'Intervals',
'page callback' => 'drupal_get_form',
'page arguments' => array('notifications_send_intervals_form'),
'type' => MENU_LOCAL_TASK,
'access arguments' => array('administer site configuration'),
'file' => 'notifications.admin.inc',
);
$items['admin/messaging/notifications/events'] = array(
'title' =>'Events',
'page callback' => 'drupal_get_form',
'page arguments' => array('notifications_admin_events_form'),
'type' => MENU_LOCAL_TASK,
'access arguments' => array('administer site configuration'),
'file' => 'notifications.admin.inc',
);
// Subscribe links. For this items access will be checked later in the page
$items['notifications/subscribe/%user'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'notifications_page_subscribe',
'page arguments' => array(2),
'access callback' => 'notifications_access_subscribe',
'access arguments' => array(2),
'file' => 'notifications.pages.inc',
);
// Unsubscribe links This page will need to work with anonymous users
$items['notifications/unsubscribe/%'] = array(
'type' => MENU_CALLBACK,
'page callback' => 'notifications_page_unsubscribe',
'page arguments' => array(2),
'access callback' => TRUE,
'file' => 'notifications.pages.inc',
);
// Edit subscription
$items['notifications/subscription/%notifications_subscription'] = array(
'type' => MENU_CALLBACK,
'title' => 'Subscription',
'page callback' => 'drupal_get_form',
'page arguments' => array('notifications_subscription_form', 2),
'access callback' => 'notifications_subscription_access',
'access arguments' => array('edit', 2),
'file' => 'notifications.pages.inc',
);
$items['user/%user/notifications'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'Notifications',
'page callback' => 'notifications_page_user_overview',
'page arguments' => array(1),
'access callback' => 'notifications_access_user',
'access arguments' => array(1),
'file' => 'notifications.pages.inc',
);
$items['user/%user/notifications/overview'] = array(
'type' => MENU_DEFAULT_LOCAL_TASK,
'title' => 'Overview',
'weight' => -10,
);
return $items;
}
/**
* Menu access callback for user subscriptions
*/
function notifications_access_user($account) {
global $user;
return $account->uid &&
($user->uid == $account->uid && user_access('maintain own subscriptions') || user_access('administer notifications'));
}
/**
* Menu access callback for subscribe links
*
* More access checking depending on subscription type will be done at the destination page
*/
function notifications_access_subscribe($account) {
global $user;
return $account && $account->uid && (user_access('administer notifications') || $user->uid == $account->uid);
}
/**
* Menu loading, subscription
*/
function notifications_subscription_load($sid) {
return notifications_load_subscription($sid);
}
/**
* Menu access callback
*/
function notifications_subscription_access($op, $subscription, $account = NULL) {
global $user;
$account = $account ? $account : $user;
if (user_access('administer notifications', $account)) return TRUE;
switch($op) {
case 'edit':
case 'unsubscribe':
return ($subscription->uid == $account->uid) && user_access('maintain own subscriptions');
}
return FALSE;
}
/**
* Implementation of hook_perms()
*
*/
function notifications_perm() {
return 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;
}
}
/**
* Implementation of hook_form_alter()
* - Override title for messaging settings
*/
function notifications_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'user_profile_form' && $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
$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(), // Object parameters
);
// 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;
db_query("INSERT INTO {notifications_event} (module, type, action, uid, created, params) VALUES ('%s', '%s', '%s', %d, %d, '%s')",
$event['module'], $event['type'], $event['action'], $event['uid'], $event['created'], serialize($event['params']));
$event['eid'] = db_last_insert_id('notifications_event', 'eid');
$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';
db_query("INSERT INTO {notifications} (uid, type, event_type, conditions, send_interval, send_method, cron, module, status) VALUES(%d, '%s', '%s', %d, %d, '%s', %d, '%s', %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_last_insert_id('notifications', 'sid');
}
// 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 notifications 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) ? $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
*
* @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) {
static $types;
if (!isset($types)) {
$types = notifications_module_information('subscription types');
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] = $info[$field];
}
return $return;
}
elseif ($type) {
return isset($types[$type]) ? $types[$type] : array();
}
else {
return $types;
}
}
/**
* 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) {
static $digest_methods, $intervals;
if (!isset($digest_methods)) {
// 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;
}
}
/**
* 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'),
'main' => t('Line for digested events'),
'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;
case 'method update':
// A messaging method has been disabled ($arg1) and replaced by the new one ($arg2)
// Update subscriptions
db_query("UPDATE {notifications} SET send_method = '%s' WHERE send_method = '%s'", $arg2, $arg1);
// Purge notifications queue, we may lost some notifications but it's the safest option.
db_query("DELETE FROM {notifications_queue} WHERE send_method = '%s'", $arg1);
break;
}
}
/**
* 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, 'absolute' => TRUE));
$values['unsubscribe-url'] = url($link['href'], $link['options']);
}
return $values;
case 'user':
if ($account = $object) {
return array(
'subscriptions-manage' => $account->uid ? url("user/$account->uid/notifications", array('absolute' =>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() {
include_once drupal_get_path('module', 'notifications') .'/notifications.cron.inc';
notifications_process_run();
}
/**
* Return link array for subscriptions
*
* @param $type
* Link type: 'subscribe' | 'unsubscribe'
* @param $params
* Aditional parameters for the subscription, may be
* - uid, the user for which the link is generated
* - confirm, whether to show confirmation page or not
* - signed, to produce a signed link that can be used by anonymous users (Example: unsubscribe link in emails)
* - Other subscription parameters: type, fields...
*/
function notifications_get_link($type, $params) {
global $user;
$params += array(
'uid' => $user->uid,
'confirm' => TRUE,
'signed' => FALSE,
'destination' => $_GET['q'],
'query' => array(),
);
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 using named parameters
$query = $params['query'];
if ($params['destination']) {
$query['destination'] = $params['destination'];
}
// To skip the confirmation form, the link must be signed
// Note tat the query string will be 'confirm=1' to skip confirmation form
if (!$params['confirm']) {
$query['confirm'] = 1;
$params['signed'] = 1;
}
if ($params['signed']) {
$query['signature'] = _notifications_signature($elements, !$params['confirm']);
}
// Build final link parameters
$options['query'] = $query;
foreach (array('absolute', 'html') as $name) {
if (isset($params[$name])) {
$options[$name] = $params[$name];
}
}
return array(
'href' => 'notifications/'. implode('/', $elements),
'options' => $options,
);
}
/**
* 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'),
)
);
}
/**
* List of send methods
*/
function _notifications_send_methods($account = NULL) {
return variable_get('notifications_send_methods', messaging_method_list($account));
}
/**
* Signature for url parameters
*
* @param $params
* Subscription parameters
* @param $skip_confirm
* TRUE to skip confirmation form
*/
function _notifications_signature($params, $skip_confirm = FALSE) {
return md5('notifications:'. drupal_get_private_key() .':'. ($skip_confirm ? 1 : 0) . ':' . 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,
);
}
/**
* Status list
*/
function _notifications_subscription_status() {
return array(
NOTIFICATIONS_SUBSCRIPTION_BLOCKED => t('blocked'),
NOTIFICATIONS_SUBSCRIPTION_ACTIVE => t('active'),
NOTIFICATIONS_SUBSCRIPTION_INACTIVE => t('inactive'),
);
}
/**
* 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);
}
}
/**
* Generic subscriptions content form
*
* Builds a form for a user to manage its own subscriptions with
* some generic parameters
*
* Currently it only manages simple condition fields
* @param $account
* User account
* @param $type
* Subscription type
* @param $subscriptions
* Current subscriptions of this type. If null they'll be loaded
* @param $list
* Array with available subscriptions indexed by field value
* @param $defaults
* Default value for subscriptions
* @param $options
* Optional, array of aditional options for the form
*/
function notifications_user_form($form_state, $account, $type, $subscriptions, $list, $defaults, $options = array()) {
// Complete defaults
$info = notifications_subscription_types($type);
$field = $info['fields'][0];
$field_title = !empty($options['title']) ? $options['title'] : '';
if (is_null($subscriptions)) {
// Fetch subscriptions with given parameters
$subscriptions = notifications_get_subscriptions(array('type' => $type, 'event_type' => $info['event_type'], 'uid' => $account->uid), TRUE, 'value');
}
$defaults += array(
'sid' => 0,
'type' => $type,
'event_type' => $info['event_type'],
);
$defaults += _notifications_subscription_defaults($account);
// Hide Send method column if only one
$send_methods = _notifications_send_methods();
$header = array(theme('table_select_header_cell'), $field_title, t('Send interval'));
if (count($send_methods) > 1) {
$header[] = t('Send method');
}
$form['defaults'] = array('#type' => 'value', '#value' => $defaults);
$form['account'] = array('#type' => 'value', '#value' => $account);
$form['current'] = array('#type' => 'value', '#value' => $subscriptions);
$form['subscription_fields'] = array('#type' => 'value', '#value' => array());
$form['subscriptions'] = array(
'#tree' => TRUE,
'#theme' => 'notifications_form_table',
'#header' => $header,
);
foreach ($list as $key => $title) {
$rowdefaults = isset($subscriptions[$key]) ? (array)($subscriptions[$key]) : $defaults;
$rowdefaults += $rowdefaults;
$form['subscriptions']['checkbox'][$key] = array(
'#type' => 'checkbox',
'#default_value' => $rowdefaults['sid'],
);
$form['subscriptions']['title'][$key] = array(
'#value' => $title,
);
$form['subscriptions']['send_interval'][$key] = array(
'#type' => 'select',
'#options' => _notifications_send_intervals(),
'#default_value' => $rowdefaults['send_interval'],
);
// Hide send methods if only one available
if (count($send_methods) > 1) {
$form['subscriptions']['send_method'][$key] = array(
'#type' => 'select',
'#options' => _notifications_send_methods(),
'#default_value' => $rowdefaults['send_method'],
);
} else {
$form['subscriptions']['send_method'][$key] = array('#type' => 'value', '#value' => $rowdefaults['send_method']);
}
// Pass on the fields for processing
$form['subscription_fields']['#value'][$key] = array($field => $key);
}
$form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
return $form;
}
/**
* Process generic form submission
*/
function notifications_user_form_submit($form, &$form_state) {
$form_values = $form_state['values'];
$account = $form_values['account'];
$current = $form_values['current'];
$defaults = $form_values['defaults'];
$defaults += array('uid' => $account->uid);
$fields = $form_values['subscription_fields'];
$values = $form_values['subscriptions'];
$check = 'checkbox';
foreach ($values[$check] as $index => $value) {
$subscription = NULL;
if ($value) {
// Checked, save only if new or changed
if (!isset($current[$index])) {
$subscription = $defaults;
}
elseif ($current[$index]->send_interval != $values['send_interval'][$index] || $current[$index]->send_method != $values['send_method'][$index]) {
$subscription = (array)($current[$index]);
}
// Complete and save
if ($subscription) {
$subscription['send_interval'] = $values['send_interval'][$index];
$subscription['send_method'] = $values['send_method'][$index];
$subscription['fields'] = $fields[$index];
notifications_save_subscription($subscription);
}
}
elseif (isset($current[$index])) {
notifications_delete_subscription($current[$index]->sid);
}
}
}
/**
* Implementation of hook_theme()
*/
function notifications_theme() {
return array(
'notifications_form_table' => array(
'arguments' => array('element' => NULL),
'file' => 'notifications.admin.inc',
),
'notifications_send_intervals' => array(
'arguments' => array('element' => NULL),
'file' => 'notifications.admin.inc',
),
'notifications_digest_short_body' => array(
'arguments' => array('text' => NULL, 'list' => NULL),
'file' => 'notifications.cron.inc',
),
'notifications_digest_short_line' => array(
'arguments' => array('line' => NULL, 'group' => NULL),
'file' => 'notifications.cron.inc',
),
'notifications_digest_long_body' => array(
'arguments' => array('header' => NULL, 'content' => NULL, 'footer' => NULL),
'file' => 'notifications.cron.inc',
),
'notifications_admin_subscriptions' => array(
'arguments' => array('form' => NULL),
'file' => 'notifications.admin.inc',
),
'notifications_subscriptions_filter_form' => array(
'arguments' => array('form' => NULL),
'file' => 'notifications.admin.inc',
),
'notifications_table_form' => array(
'arguments' => array('form' => NULL),
'file' => 'notifications.admin.inc',
),
);
}
/**
* Short hand for info logs
*/
function notifications_log($message = NULL, $variables = NULL) {
return _notifications_log('info', $message, $variables); dsm($message);
}
/**
* Quick logging for debugging and manual queue processing
*/
function _notifications_log($type, $message = NULL, $variables = NULL, $severity = WATCHDOG_NOTICE) {
static $logs;
if ($message) {
$logs[] = array($type, $message, $variables, $severity);
}
else {
return $logs ? array_map('_notifications_log_format', $logs) : '';
}
}
/**
* Format notifications log
*/
function _notifications_log_format($log) {
list($type, $string, $args, $severity) = $log;
if ($args) {
// Transform arguments before inserting them.
$append = array();
foreach ($args as $key => $value) {
if (is_array($value) || is_object($value)) {
$value = print_r($value, TRUE);
}
switch ($key[0]) {
case '@':
// Escaped only.
$args[$key] = check_plain($value);
break;
case '%':
$args[$key] = theme('placeholder', $value);
break;
case '!':
// Pass-through.
$args[$key] = $value;
break;
default:
// Append to string a key value pair, different from watchdog format
$append[] = ' ' . $key . '= ' . check_plain($value);
break;
}
}
$string = strtr($string, $args);
$append ? $string .= ' ' . implode(', ', $append) : NULL;
}
return ' '.$type . ': ' . $string;
}
// For PHP4 compatibility
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;
}
}