You are here

messaging.module in Messaging 6.2

File

messaging.module
View source
<?php

/**
 * Drupal Messaging Framework
 *
 * This is a messaging framework to allow message sending in a channel independent way
 * It will provide a common API for sending while allowing plugins for multiple channels
 * 
 * If enabled the html_to_text module, a new filter will be available
 * 
 * By Development Seed, http://wwww.developmentseed.org
 */

// Method with push delivery. Messages will be pushed to the user using messaging sending methods.
define('MESSAGING_TYPE_PUSH', 1);

// Method type with pull delivery. Messages will be pulled using messaging pull methods
define('MESSAGING_TYPE_PULL', 2);

// Outgoing method
define('MESSAGING_TYPE_OUTGOING', 4);

// Incoming method
define('MESSAGING_TYPE_INCOMING', 8);

// Shorthand type: Push + Outgoing
define('MESSAGING_TYPE_SEND', MESSAGING_TYPE_PUSH | MESSAGING_TYPE_OUTGOING);

// Special string for empty text part
define('MESSAGING_EMPTY', '<none>');

/**
 * Implementation of hook_help().
 */
function messaging_help($path, $arg) {
  switch ($path) {
    case 'admin/help#messaging':
      $output = '<p>' . t('The messaging module is the engine that handles outgoing messages and message queueing for different sending methods.') . '</p>';
      $output .= '<p>' . t('You need to enable one or more of the included plug-ins to be able to actually take advantage of it.') . '</p>';
      return $output;
    default:
      if ($arg[0] == 'admin') {
        if ($arg[1] == 'settings' && $arg[2] == 'filters') {
          return '<p>' . t('Filters are used also for messaging. If the input format is to be used only for messaging you don\'t need to allow any role for it.') . '</p>';
        }
        if ($arg[1] == 'messaging') {
          require_once drupal_get_path('module', 'messaging') . '/messaging.admin.inc';
          return messaging_admin_help($path, $arg);
        }
      }
  }
}

/**
 * Implementation of hook_menu()
 */
function messaging_menu() {
  $items['admin/messaging'] = array(
    'title' => 'Messaging',
    'access arguments' => array(
      'administer messaging',
    ),
    'description' => 'Administer and configure messaging',
    'page callback' => 'system_admin_menu_block_page',
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/messaging/settings'] = array(
    'title' => 'Messaging settings',
    'description' => 'Configuration of messaging framework',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'messaging_admin_settings',
    ),
    'access arguments' => array(
      'administer messaging',
    ),
    'file' => 'messaging.admin.inc',
  );
  $items['admin/messaging/settings/overview'] = array(
    'title' => 'Messaging',
    'description' => 'Configuration of sending methods',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/messaging/settings/method'] = array(
    'title' => 'Send methods',
    'description' => 'Configuration of sending methods',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'messaging_admin_method_settings',
    ),
    'access arguments' => array(
      'administer messaging',
    ),
    'weight' => -10,
    'file' => 'messaging.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/messaging/settings/method/overview'] = array(
    'title' => 'General',
    'description' => 'General settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/messaging/template'] = array(
    'title' => 'Message templates',
    'description' => 'Configuration of message templates',
    'page callback' => 'messaging_admin_template',
    'access arguments' => array(
      'administer messaging',
    ),
    'file' => 'messaging.admin.inc',
  );
  $items['admin/messaging/template/edit'] = array(
    'title' => 'Message templates',
    'page callback' => 'messaging_admin_template_edit',
    'type' => MENU_CALLBACK,
    'access arguments' => array(
      'administer messaging',
    ),
    'file' => 'messaging.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_perm()
 */
function messaging_perm() {
  return array(
    'administer messaging',
  );
}

/**
 * Implementation of hook_user().
 *
 * Adds fieldset and default sending method setting.
 */
function messaging_user($type, $edit, &$user, $category = NULL) {
  switch ($type) {
    case 'form':
      if ($category == 'account' && ($list = messaging_method_list($user))) {
        $form['messaging'] = array(
          '#type' => 'fieldset',
          '#title' => t('Messaging settings'),
          '#weight' => 5,
          '#collapsible' => TRUE,
        );
        $form['messaging']['messaging_default'] = array(
          '#type' => 'select',
          '#title' => t('Default send method'),
          '#default_value' => messaging_method_default($user),
          '#options' => $list,
          '#description' => t('Default sending method for getting messages from this system.'),
          '#disabled' => count($list) == 1,
        );
        return $form;
      }
      break;
  }
}

/** Messaging API **/

/**
 * Send message to user represented by account
 *
 * We are applying same output filter for everybody, depending on send method
 *
 * The final rendering of the message depends on send method too. I.e. a mail messaging
 * method may want to insert '--' before the body footer.
 *
 * @ TODO Consider whether it makes sense to allow users decide on formatting
 *
 * @param $account
 *   User object to recieve message.
 * @param $message
 *   Array of message parts that will be compiled depending on send method.
 *   Mandatory message parts, which may have nexted parts are:
 *   - 'type'
 *   - 'subject'
 *   - 'body'. The message body may have 'header', 'content', 'footer', 'etc'
 * @param $method
 *   Optional send method. Defaults to the user account predefined method
 */
function messaging_message_send_user($account, &$message, $method = NULL, $queue = 0) {
  messaging_debug('Sending message to user', array(
    'account' => $account,
    'message' => $message,
    'method' => $method,
  ));

  // Build array of parameters so they can be overridden by callbacks
  $message = (object) $message;
  $params = array(
    'message' => $message,
    // Get default sending method, or default for this user account
    'method' => $method ? $method : messaging_method_default($account),
    'queue' => $queue,
    'account' => $account,
    'log' => TRUE,
  );

  // Check user and availability if there's a callback for that. Depending on the sending method,
  // when user is not available it may just be discarded / queued / redirected through other method
  if ($callback = messaging_method_info($method, 'user callback')) {
    $params = _messaging_callback_invoke($callback, $params);
  }

  // Set the parameters back into variables. I don't like extract()
  $method = $params['method'];
  $queue = $params['queue'];

  // Now check that we have a destination for this user account. If set to FALSE we just don't try anymore
  if (isset($params['destination'])) {
    $destination = $params['destination'];
  }
  elseif ($account) {
    $destination = messaging_user_destination($account, $method, $message);
  }

  // Set some values into the message
  $message->method = $method;
  $message->account = $account;

  // Send the message or, if no destination, abort the message sending
  if (!empty($destination)) {
    messaging_debug('Found destination for user, sending message', array(
      'method' => $method,
      'destination' => $destination,
    ));
    return messaging_message_send(array(
      $destination,
    ), $message, $method, $queue);
  }
  elseif (!empty($params['log'])) {

    // Save the message, let it there for further reference
    messaging_log('Destination not available for user account', array(
      'method' => $method,
      'account' => $account,
    ));
    $message->destination = 'ERROR';
    if ($info = messaging_method_info($method)) {
      $message = messaging_message_prepare($message, $info);
      $message = messaging_message_render($message, $info);
    }
    else {
      $message->subject = $message->body = '';
    }
    $message->cron = $message->queue = 0;
    $message->log = 1;
    $message->success = FALSE;
    $message = messaging_store('save', $message);
  }
  return FALSE;
}

/**
 * Get destination from user account.
 * 
 * This will handle also anonymous user accounts that should have a 'destination' property
 */
function messaging_user_destination($account, $method, $message = NULL) {
  if ($info = messaging_method_info($method)) {
    if (($property = messaging_method_info($method, 'destination')) && !empty($account->{$property})) {

      // Get destination property from user account
      return $account->{$property};
    }
    elseif (empty($account->uid) && !empty($account->destination)) {

      // Anonymous account with destination property
      return $account->destination;
    }
    elseif ($callback = _messaging_callback_get($info, 'destination')) {

      // We have a mapping function, it may also work for anonymous users
      return _messaging_callback_invoke($callback, $account, $message);
    }
  }
}

/**
 * Send message to array of destinations. The message is rendered just once.
 * 
 * The $message array may have the following elements
 *   'subject' => Message subject, may be already rendered or not
 *   'body' => Message content, may be already rendered or not
 *   'params' => Optional message params, indexed by sending method group
 *      I.e. params for mail methods will be in $message['params']['mail']
 *   'render' => Optional flag to mark the message subject and body as rendered
 *   'sender' => Optional int to identify message sender, may be $user->uid
 *   'sender_account' => Optional user account to use as message sender
 * @param $destinations
 *   Array of destinations for sending.
 *   The element type depends on sending method so it can be a list of e-mail addresses, user accounts, etc
 * @param $message
 *   Message object or array, not rendered
 * @param $method
 *   Sending method. Unlike for messaging_message_send_user() for which the sending method may be user's default
 *   it is not an optional parameter for this function.
 * @param $queue
 *   Optional flag, 0 for normal queueing, 1 to force queueing.
 *   We may want to force queueing for bulk messaging. Otherwise it will depend on the sending method
 *   wether to queue the messages (for pull methods) or not (push methods)
 */
function messaging_message_send($destinations, &$message, $method, $queue = 0) {
  messaging_debug('Sending message', array(
    'destinations' => $destinations,
    'message' => $message,
    'method' => $method,
    'queue' => $queue,
  ));

  // Get default sending method, or default for this user account
  $method = $method ? $method : messaging_method_default(NULL);
  $info = messaging_method_info($method, NULL, array());

  // Convert into an object and add all the information into the message object
  $message = (object) $message;
  $message->method = $method;
  $message->source['type'] = 'outgoing';
  $message->destinations = $destinations;
  $message->queue = $queue;
  $message->sent = $message->queued = 0;

  // Message preprocessing, before sending
  $message->process = TRUE;
  $message = messaging_message_callbacks(array(
    'prepare',
    'render',
    'presend',
  ), $message, $info);

  // If queue / send call the next hooks
  if (!empty($message->test)) {

    // We are doing a test run so we don't send the message
    messaging_log('Emulating message sending (test run)', array(
      'message' => $message,
      'destinations' => $destinations,
    ));
    $message->success = TRUE;
  }
  else {
    if ($message->process) {
      if ($message->queue) {
        $callbacks = array(
          'queue',
          'afterqueue',
        );
      }
      else {
        $callbacks = array(
          'multisend',
          'aftersend',
        );
      }
      $message = messaging_message_callbacks($callbacks, $message, $info);
    }
  }

  // This will return true if the message was sent or queued for delivery
  if (!isset($message->success)) {
    $message->success = $message->sent || $message->queued;
  }
  return $message->success;
}

/**
 * Invoke callbacks
 */
function messaging_message_invoke($callback_list, $message, $info) {
  if (is_string($callback_list)) {
    $callback_list = array(
      $callback_list,
    );
  }

  // Any of the functions can 'swallow' the message and stop processing
  while ($message->process && ($callback = array_shift($callback_list))) {
    $message = _messaging_callback_invoke($callback, $message, $info);
  }
  return $message;
}

/**
 * Message default callback: preprocessing
 * 
 * Decide on queue, log, cron and send options, prepare parameters
 */
function messaging_message_prepare($message, $info) {
  global $user;
  messaging_debug('Preparing message for sending', array(
    'message' => $message,
    'info' => $info,
  ));

  // Set some default values if not already set
  foreach (array(
    'sent' => 0,
    'queue' => 0,
    'cron' => 0,
    'log' => (bool) variable_get('messaging_log', 0),
    'sender' => $user->uid,
  ) as $field => $value) {
    if (!isset($message->{$field})) {
      $message->{$field} = $value;
    }
  }

  // If the messaging method is of type push, cron processing will be enabled
  if ($message->queue && $info['type'] & MESSAGING_TYPE_PUSH) {
    $message->cron = 1;
  }

  // It will be queued always for pull methods, cron disabled though so it will wait till it's pulled
  if (!$message->queue && $info['type'] & MESSAGING_TYPE_PULL) {
    $message->queue = 1;
    $message->cron = 0;
  }

  // Provides a hook for other modules to modify the message before sending
  foreach (module_implements('message_alter') as $module) {
    $function = $module . '_message_alter';
    $function($message, $info);
  }
  return $message;
}

/**
 * Message default callback: send iterating over all destinations
 */
function messaging_message_multisend($message, $info) {
  $success = TRUE;
  foreach ($message->destinations as $to) {

    // Be careful with the order of function && value, so they both are evaluated
    $success = messaging_message_send_out($to, $message, $message->method) && $success;
  }

  // If sent, set time. If failed force logging.
  $success ? $message->sent = time() : ($message->log = 1);
  $message->success = $success;
  return $message;
}

/**
 * Queue message for next delivery
 * 
 * @todo Improve storage so we can store only once but multiple rows for destinations
 */
function messaging_message_queue($message, $info = array()) {
  $message->queued = 1;
  return messaging_store('save', $message);
}

/**
 * Store messages, custom delivery
 */
function messaging_message_store($message, $info = array()) {
  $message->stored = 1;
  if (empty($message->mqid)) {
    $message = messaging_store('save', $message);
  }
  return $message;
}

/**
 * Message default after send callback: store, log, etc..
 */
function messaging_message_aftersend($message, $info) {

  // Depending on parameters and what's happened so far we make the final queue/log decision
  if (empty($message->mqid) && $message->log) {
    messaging_store('save', $message);
  }
  return $message;
}

/**
 * Message logging callback
 */
function messaging_message_log($message, $info) {
  if (empty($message->mqid)) {
    messaging_store('save', $message);
  }
  return $message;
}

/**
 * Send for real, finally invoking method's callback function
 * 
 * This sends messages one by one, so the callback function only need to support a single destination
 * Specific parameters for this message group are processed here too
 * 
 * @param $destination
 *   Single destination, may be email, user account, etc...
 * @param $message
 *   Message object
 * @param $method
 *   Sending method
 * 
 * @return boolean
 *   Message successfully sent
 */
function messaging_message_send_out($destination, $message, $method) {
  if ($callback = messaging_method_info($method, 'send callback')) {

    // Check for specific parameters for this sending method
    $group = messaging_method_info($method, 'group');
    $params = !empty($message->params[$group]) ? $message->params[$group] : array();
    return _messaging_callback_invoke($callback, $destination, $message, $params);
  }
  else {
    watchdog('messaging', 'Message could not be delivered for method %method', array(
      '%method' => $method,
    ), WATCHDOG_ERROR);
    return FALSE;
  }
}

/**
 * Implementation of hook_cron()
 * 
 * Process queued messages for delivery
 */
function messaging_cron() {
  messaging_store('queue_process');
  messaging_store('queue_cleanup');
}

/**
 * Pull pending messages for given methods and user accounts
 *
 * Each returned message will be an array with the following elements
 * - 'uid', destination uid
 * - 'sender', originating uid
 * - 'subject', message subject to be rendered
 * - 'body', message body to be rendered
 * @param $method
 *   Send method
 * @param $users
 *   User id or array of user id's
 * @param $limit
 *   Optional limit for number of messages
 * @param $delete
 *   Optional whether to delete messages when fetching
 * @return array()
 *   Array of pending messages.
 */
function messaging_pull_pending($method, $users = NULL, $limit = 0, $delete = TRUE) {
  $params['method'] = $method;
  $params['queue'] = 1;
  if (!is_null($users)) {
    $params['uid'] = $users;
  }
  $messages = messaging_store('get', $params);

  // Not exactly delete but mark as sent
  if ($messages && $delete) {
    messaging_store('sent', array_keys($messages));
  }
  return $messages;
}

/**
 * Returns list of messaging methods for a type
 *
 * I.e. all messaging methods of pull type
 */
function messaging_method_type($type) {
  $result = array();
  foreach (messaging_method_info() as $method => $info) {
    if ($info['type'] & $type) {
      $result[$method] = $info;
    }
  }
  return $result;
}

/**
 * List sending methods
 *
 * @param $account
 *   Optional user account, for checking permissions against this account
 */
function messaging_method_list($account = NULL) {
  $info = messaging_method_info(NULL, 'name');
  if ($account) {
    foreach (array_keys($info) as $method) {

      // Check access for each method and check destination
      if (!messaging_method_permission($method, $account) || !messaging_user_destination($account, $method)) {
        unset($info[$method]);
      }
      else {
        $info[$method] = messaging_translate("method:{$method}:name", $info[$method]);
      }
    }
  }
  return array_map('check_plain', $info);
}

/**
 * Check permission for method and account
 *
 * @param $method
 *   Sending method id
 * @param $account
 *   User account to check permission
 */
function messaging_method_permission($method, $account = NULL) {
  $info = messaging_method_info($method);

  // This sending method may be disabled
  if (!$info) {
    return FALSE;
  }
  elseif (!empty($info['access'])) {
    return user_access($info['access'], $account);
  }
  else {
    return TRUE;
  }
}

/**
 * Returns default messaging method
 */
function messaging_method_default($account = NULL) {
  if ($account && !empty($account->messaging_default) && messaging_method_permission($account->messaging_default, $account)) {
    return $account->messaging_default;
  }
  elseif ($method = variable_get('messaging_default_method', '')) {
    return $method;
  }
  else {
    return key(messaging_method_info());
  }
}

/**
 * Get setting from user account or get default setting if not available
 * 
 * If first checks for a 'messaging_$name' property in the user account
 * and returns the value of the variable 'messaging_default_$name' if not set
 * 
 * There's an optional variable 'messaging_peruser_$name' that if true will block
 * per user settings and use only global settings.
 * 
 * @param $name
 *   Option name
 * @param $account
 *   Optional account to check setting for
 * @param $default
 *   Default value if no option set
 */
function messaging_user_setting($name, $account = NULL, $default = NULL) {
  $variable = 'messaging_' . $name;
  if ($account && isset($account->{$variable}) && variable_get('messaging_peruser_' . $name, 1)) {
    return $account->{$variable};
  }
  else {
    return variable_get('messaging_default_' . $name, $default);
  }
}

/**
 * Returns parts of messages, that may be formatted for each sending method
 *
 * @ TODO Review logic, optimizations, text pre-fetching
 * @ TODO Glue text in a method-dependent way
 *
 * First checks for message, key, method
 * Then checks for message, key for method 'default'
 * Finally checks default values from modules and hook_messaging()
 *
 * @param $group
 *   String, specified by the module where the message originates. ie 'subscriptions-event'.
 * @param $key
 *   String, key for the desired message part.
 * @param $method
 *   String the mailing method that should be used. OPTIONAL
 * @param $getdefault
 *   Boolean, whether to use the default if a specific message isn't available for the used method. OPTIONAL, Defaults to true.
 *
 * @return
 *   Assembled text of a message part.
 */
function messaging_message_part($group, $key, $method = 'default', $getdefault = TRUE) {
  static $cache;
  if (isset($cache[$group][$key][$method])) {
    $text_part = $cache[$group][$key][$method];
  }
  else {
    if ($method && ($text = db_result(db_query("SELECT message FROM {messaging_message_parts} WHERE type = '%s' AND msgkey = '%s' AND method = '%s'", $group, $key, $method)))) {
      $text_part = $text;
    }
    elseif ($method == 'default' && ($text = messaging_message_info($group, $key))) {

      // Retry with default but also set the cache for this method
      $text_part = $text;
    }
    elseif ($method != 'default' && $getdefault && ($text = messaging_message_part($group, $key, 'default'))) {
      $text_part = $text;
    }
    else {
      $text_part = FALSE;
    }

    // Convert array into plain text
    if ($text_part && is_array($text_part)) {
      $text_part = implode("\n", $text_part);
    }
    $cache[$group][$key][$method] = $text_part;
  }
  return $text_part ? $text_part : '';
}

/**
 * Returns parts of messages, that may be formatted for each sending method
 *
 * @param $group
 *   Message group.
 * @param $key
 *   Optional message key inside the group. Returns all keys if null.
 * @return array()
 *   Depending on parameters, may be all message groups and keys or only a specific one.
 */
function messaging_message_info($group, $key = NULL) {
  static $info;
  if (!isset($info[$group])) {
    $info[$group] = module_invoke_all('messaging', 'messages', $group);
  }
  return _messaging_info($info, $group, $key);
}

/**
 * Returns information about message groups
 *
 * @param $group
 *   Optional message group. Returns all groups if null.
 * @param $key
 *   Optional message key inside the group. Returns all keys if null.
 * @return array()
 *   Depending on parameters, may be all message groups and keys or only a specific one.
 */
function messaging_message_group($group = NULL, $key = NULL) {
  static $info;
  if (!isset($info)) {
    $info = module_invoke_all('messaging', 'message groups');
  }
  return _messaging_info($info, $group, $key);
}

/**
 * Returns messaging methods properties
 *
 * @param $method
 *   Optional, Method to get properties for, none or NULL for all methods
 * @param $property
 *   Optional, Property to get, none or NULL for all properties
 * @param $default
 *   Optional default value to return when there's not that property for the method
 */
function messaging_method_info($method = NULL, $property = NULL, $default = NULL, $refresh = FALSE) {
  static $info, $properties;
  if (!$info || $refresh) {
    $info = module_invoke_all('messaging', 'send methods');

    // Merge settings for each enabled method, just default filter if variable not set
    $default_settings = array(
      'filter' => variable_get('messaging_default_filter', ''),
    );
    foreach (array_keys($info) as $name) {
      $info[$name] = array_merge($info[$name], variable_get('messaging_method_' . $name, $default_settings));
    }

    // Allow altering by other modules
    drupal_alter('messaging_methods', $info);
  }
  return _messaging_info($info, $method, $property, $default);
}

/** Message composition and rendering **/

/**
 * Renders full message with header and body
 * 
 * @param $message
 *   Message object
 * @param $info
 *   Sending method info for rendering (glue and filter options)
 */
function messaging_message_render($message, $info) {
  if (!empty($message->rendered)) {
    return $message;
  }
  messaging_debug('Rendering message', array(
    'message' => $message,
    'info' => $info,
  ));

  // Apply footer prefix if provided and the message has a footer element.
  // Note: If message body is a string the final isset($message['body']['footer']) will be true
  if (!empty($info['footer']) && is_array($message->body) && isset($message->body['footer'])) {
    $message->body['footer'] = array(
      '#prefix' => $info['footer'],
      '#text' => $message->body['footer'],
    );
  }

  // Render separately subject and body info, adding default parameters
  $info += array(
    'glue' => '',
    'subject_glue' => '',
    'filter' => NULL,
  );
  $message->subject = messaging_check_subject(messaging_text_render($message->subject, $info['subject_glue']));
  $message->body = messaging_text_render($message->body, $info['glue'], $info['filter']);
  $message->rendered = 1;
  messaging_debug('Rendering message', array(
    'message' => $message,
    'info' => $info,
  ));
  return $message;
}

/**
 * Composes message from different parts, recursively and applies filter
 * 
 * Filter is applied now only once
 * 
 * @param $text
 *   Simple string or array of message parts
 *   It may have named elements like #prefix and #text
 *   or it may be single strings to render straight forward
 * @param $glue
 *   Text to glue all lines together
 * @param $filter
 *   Input format to apply to the results
 */
function messaging_text_render($text, $glue = '', $filter = NULL) {
  $output = '';
  if (is_array($text)) {
    if (isset($text['#prefix'])) {
      $output .= $text['#prefix'] . $glue;
      unset($text['#prefix']);
    }
    if (isset($text['#text'])) {
      $output .= $text['#text'];
      return $output;
    }
    foreach (element_children($text) as $key) {

      // The filter is not passed along
      $text[$key] = messaging_text_render($text[$key], $glue);
    }
    $output .= implode($glue, $text);
  }
  else {
    $output .= $text;
  }

  // The filter is applied now only once
  if ($filter) {
    $output = check_markup($output, $filter, FALSE);
  }
  return $output;
}

/**
 * Implementation of hook_filter(). Contains a basic set of essential filters.
 * - Plain text:
 *     Strips out all html
 *     Replaces html entities
 * - HTML to text
 *     Same with some text formatting
 *     Relies on html_to_text module
 */
function messaging_filter($op, $delta = 0, $format = -1, $text = '') {
  switch ($op) {
    case 'list':
      $info[0] = t('Plain text');
      $info[1] = t('HTML to text');
      return $info;
    case 'description':
      switch ($delta) {
        case 0:
          return t('Filters out all HTML tags and replaces HTML entities by characters, respects HTML line breaks.');
        case 1:
          return t('Replaces HTML tags and entities with plain text formatting, moving links at the end. This filter is just for text messages and it isn\'t safe for rendering content on a web page.');
      }
    case 'process':
      switch ($delta) {
        case 0:
          return messaging_check_plain($text, "\n");
        case 1:
          return drupal_html_to_text($text);
        default:
          return $text;
      }
    case 'settings':
      return;
    default:
      return $text;
  }
}

/**
 * HTML to text simple filtering.
 * - Replace some tags with line endings: p, br, hr, li, h1, h2, h3, h4
 * Strip out all HTML tags and decode entities
 * 
 * @param $text
 *   Text to clean up
 * @param $break
 *   Optional character to replace tags for line breaks
 */
function messaging_check_plain($text, $break = NULL) {

  // This have to be done before the filtering because tag markers may have been previously parsed with check_plain
  $text = str_replace(array(
    '&lt;',
    '&gt;',
  ), array(
    '<',
    '>',
  ), $text);

  // Clean up the HTML and replace some tags with line endings
  if (isset($break)) {
    $text = _filter_htmlcorrector($text);
    $text = str_replace(array(
      '</p>',
      '<br />',
      '<hr />',
      '</li>',
      '</h1>',
      '</h2>',
      '</h3>',
      '</h4>',
    ), $break, $text);
  }

  // Final text clean up
  return messaging_text_clean($text);
}

/**
 * Converts strings to plain utf-8 single line
 */
function messaging_check_subject($text) {
  $text = messaging_check_plain($text);

  // taken from _sanitizeHeaders() in PEAR mail() : http://pear.php.net/package/Mail
  $text = preg_replace('=((0x0A/%0A|0x0D/%0D|\\n|\\r)\\S).*=i', NULL, $text);
  return $text;
}

/**
 * Entry point for the storage API
 */
function messaging_store() {
  static $include;
  if (!isset($include)) {
    require_once drupal_get_path('module', 'messaging') . '/messaging.store.inc';
    $include = TRUE;
  }
  $args = func_get_args();
  $function = 'messaging_store_' . array_shift($args);
  return call_user_func_array($function, $args);
}

/**
 * Helper user loading function with static caching
 */
function messaging_load_user($uid) {
  static $cache = array();
  if (!array_key_exists($uid, $cache)) {
    $cache[$uid] = user_load(array(
      'uid' => $uid,
    ));
  }
  return $cache[$uid];
}

/**
 * Helper function for message loading from the store
 * 
 * @param $mqid
 *   Message id
 */
function messaging_message_load($mqid, $refresh = FALSE) {
  static $cache;
  if (!isset($cache[$mqid]) || $refresh) {
    if ($message = messaging_store('load', $mqid)) {
      $cache[$mqid] = $message;
    }
    else {
      $cache[$mqid] = FALSE;
    }
  }
  return $cache[$mqid];
}

/**
 * Do token replacement. 
 * 
 * Uses token_logic if enabled, standard token replacement otherwise
 */
function messaging_text_replace($text, $objects) {

  // Add some token types
  $objects['global'] = NULL;

  // Use token_logic if available, http://code.developmentseed.org/token_logic
  // Otherwise use standard contrib token module, http://drupal.org/project/token
  $function = 'token_replace_multiple';
  $param_arr = array(
    $text,
    $objects,
  );
  if (module_exists('token_logic')) {
    $function = 'token_logic_replace_multiple';
  }
  if (is_array($text)) {
    foreach ($text as $part => $line) {
      $param_arr[0] = $line;
      $text[$part] = call_user_func_array($function, $param_arr);
    }
  }
  else {
    $text = call_user_func_array($function, $param_arr);
  }
  return $text;
}

/**
 * Build a simple text with message subject and body
 * 
 * This is useful for methods requiring a simple text instead of header and subject
 * 
 * @param $message
 *   Message object
 * @param $glue
 *   Separator to glue subject and body together
 */
function messaging_text_build($message, $glue = ' ') {
  $parts = array(
    trim($message->subject),
    trim($message->body),
  );
  $parts = array_filter($parts);
  if ($parts) {
    $text = implode($glue, $parts);
    return $text;
  }
  else {
    return '';
  }
}

/**
 * Clean text of HTML stuff and optionally of line endings
 * 
 * @param $text
 *   Dirty HTML text to be filtered
 * @param $newline
 *   Optional string to be used as line ending
 */
function messaging_text_clean($text, $newline = NULL) {

  // HTML entities to plain text conversion.
  $text = decode_entities($text);

  // Filters out all remaining HTML tags
  $text = filter_xss($text, array());

  // Optionally, replace new lines
  if (!is_null($newline)) {
    $text = str_replace("\n", $newline, $text);
  }

  // Trim out remaining beginning/ending spaces
  $text = trim($text);
  return $text;
}

/**
 * Truncate messages to given length.  Adapted from node_teaser() in node.module
 */
function messaging_text_truncate($text, $length) {

  // If we have a short message, return the message
  if (drupal_strlen($text) < $length) {
    return $text;
  }

  // Initial slice.
  $teaser = truncate_utf8($text, $length);
  $position = 0;

  // Cache the reverse of the message.
  $reversed = strrev($teaser);

  // split at paragraph boundaries.
  $breakpoints = array(
    '</p>' => 0,
    '<br />' => 6,
    '<br>' => 4,
    "\n" => 1,
  );

  // We use strpos on the reversed needle and haystack for speed.
  foreach ($breakpoints as $point => $offset) {
    $length = strpos($reversed, strrev($point));
    if ($length !== FALSE) {
      $position = -$length - $offset;
      return $position == 0 ? $teaser : substr($teaser, 0, $position);
    }
  }

  // When even the first paragraph is too long, we try to split at the end of
  // the last full sentence.
  $breakpoints = array(
    '. ' => 1,
    '! ' => 1,
    '? ' => 1,
    ' ' => 0,
  );
  $min_length = strlen($reversed);
  foreach ($breakpoints as $point => $offset) {
    $length = strpos($reversed, strrev($point));
    if ($length !== FALSE) {
      $min_length = min($length, $min_length);
      $position = 0 - $length - $offset;
    }
  }
  return $position == 0 ? $teaser : substr($teaser, 0, $position);
}

/**
 * Implementation of hook_requirements()
 */
function messaging_requirements($phase) {
  $requirements = array();

  // Ensure translations don't break at install time
  $t = get_t();
  if ($phase == 'runtime') {
    $methods = messaging_method_list();

    // Ensure that we have some sending methods available
    if (!$methods) {
      $requirements['messaging'] = array(
        'title' => $t('Messaging sending methods'),
        'value' => $t('No sending method plug-ins available. Please enable some Sending Method on the !admin-modules page.', array(
          '!admin-modules' => l($t('Modules administration'), 'admin/build/modules'),
        )),
        'severity' => REQUIREMENT_ERROR,
      );
    }
  }
  return $requirements;
}

/**
 * Process incoming message. This is the entry point for plug-in modules
 * 
 * This is just a wrapper for handling incoming in messaging_incoming module
 */
function messaging_message_in($method, $channel, $message, $params = array()) {
  if (function_exists('messaging_incoming_post')) {
    return messaging_incoming_post($method, $channel, $message, $params);
  }
  else {
    return FALSE;
  }
}

/**
 * Update messaging method.
 * 
 * When a messaging method is disabled, we need to update current settings for this and other modues 
 */
function messaging_method_disable($method) {
  module_load_include('install', 'messaging');
  if ($replace = messaging_update_method_replace($method)) {
    messaging_update_method_disable($method, $replace);
    drupal_set_message(t('Disabled messaging sending method %method and replaced by %replace', array(
      '%method' => messaging_method_info($method, 'title'),
      '%replace' => messaging_method_info($replace, 'title'),
    )));
  }
  else {

    // It seems all methods are disabled, print warning
    drupal_set_message(t('Disabled messaging sending method but cannot find a replacement. Please, enable some other sending method.'), 'error');
  }
}

/**
 * Build callback structure to be invoked later
 * 
 * A callback structure will be an array containing the function name to invoke
 * and a list of arguments for that function
 * 
 * @param $function
 *   Function name 
 * @param $arg1, $arg1, $arg3
 */
function _messaging_callback() {
  $args = func_get_args();
  if (count($args) > 1) {

    // Array with function name, param1, param2..
    return $args;
  }
  else {

    // Simple string with function name
    return array_shift($args);
  }
}

/**
 * Invoke callback with variable arguments
 * 
 * We don't check whether the function exists so it will crash if it's missing
 * 
 * @param $callback
 *   Function name or array(function, arg1, arg2..)
 * @param $arg1, $arg2...
 *   Variable number of arguments
 */
function _messaging_callback_invoke() {
  $args = func_get_args();
  $callback = array_shift($args);
  if (is_array($callback)) {

    // It is an array: function, arg1, arg2...
    $function = array_shift($callback);
    $params = $callback;
  }
  else {

    // It is just a function name
    $function = $callback;
    $params = array();
  }

  // Merge parameters and go for it
  $params = array_merge($params, $args);
  return call_user_func_array($function, $params);
}

/**
 * Invoke ordered list of callbacks on message
 * 
 * For each callback name this function will search existing callbacks in $info ('[name] callback')
 * and if not existing will try the default callback 'messaging_message_[name]'
 * 
 * @see _messaging_callback() for callback structure
 * 
 * @param $callback_keys
 *   Array of callbacks to invoke, ordered list
 * @param $message
 *   Message object
 * @param $info
 *   Messaging method info which may contain callbacks
 */
function messaging_message_callbacks($callback_keys, $message, $info) {
  while (!empty($message->process) && ($key = array_shift($callback_keys))) {
    if ($callback = _messaging_callback_get($info, $key)) {
      $message = messaging_message_invoke($callback, $message, $info);
    }
    elseif (function_exists('messaging_message_' . $key)) {
      $message = call_user_func('messaging_message_' . $key, $message, $info);
    }
  }
  return $message;
}

/**
 * Implementation of hook_token_list(). Documents the individual
 * tokens handled by the module.
 */
function messaging_token_list($type = 'all') {
  $tokens = array();
  if ($type == 'message' || $type == 'all') {
    $tokens['message']['message-subject'] = t('The message subject.');
    $tokens['message']['message-body'] = t('The message body.');
    $tokens['message']['message-author-name'] = t('The message\'s author name.');
    $tokens['message']['message-method'] = t('The message\'s method name.');
    $tokens['message']['message-date'] = t('The message\'s sending date.');
  }
  return $tokens;
}

/**
 * Implementation of hook_token_values()
 */
function messaging_token_values($type, $object = NULL, $options = array()) {
  switch ($type) {
    case 'message':
      if ($message = $object) {
        $values['message-subject'] = check_plain($message->subject);
        $values['message-body'] = filter_xss($message->body);
        $sender = messaging_load_user($message->sender);
        $values['message-author-name'] = check_plain($sender->name);
        $tokens['message-method'] = messaging_method_info($message->method, 'name');
        $tokens['message-date'] = format_date($message->sent);
        return $values;
      }
      break;
  }
}

/**
 * Implementation of hook_theme()
 */
function messaging_theme() {
  return array(
    'messaging_admin_method_settings' => array(
      'arguments' => array(
        'element' => NULL,
      ),
      'file' => 'messaging.admin.inc',
    ),
  );
}

/**   
 * Wrapper function for 1i8nstrings() if i18nstrings enabled.   
 */
function messaging_translate($name, $string, $langcode = NULL, $textgroup = 'messaging') {
  return function_exists('i18nstrings') ? i18nstrings($textgroup . ':' . $name, $string, $langcode) : $string;
}

/**
 * Add a callback to a callback collection
 * 
 * @param $callback_list
 *   Existing callback list or method info array
 * @param $type
 *   Callback type
 * @param $callback
 *   Callback function name or array('function_name', arg1, arg2...) *   
 */
function _messaging_callback_add(&$callback_list, $type, $callback) {
  $name = $type . ' callback';

  // If the existing callback is a single function name, make it an array
  if (!empty($callback_list[$name]) && !is_array($callback_list[$name])) {
    $callback_list[$name] = array(
      $callback_list[$name],
    );
  }
  $callback_list[$name][] = $callback;
}

/**
 * Get a callback from the information array if present
 * 
 * @param $info
 *   Information array, typically a messaging method info array
 * @param $type
 *   Callback type, the search key will be '[type] callback'
 * @param $default
 *   Default callback to return if not in the $info array
 */
function _messaging_callback_get($info, $type, $default = NULL) {
  if (!empty($info[$type . ' callback'])) {
    return $info[$type . ' callback'];
  }
  else {
    return $default;
  }
}

/**
 * Helper function to get property from an info array
 * 
 * Depending on method and property, returns the full array or a specific property
 */
function _messaging_info($info, $method = NULL, $property = NULL, $default = NULL) {
  if ($method && $property) {
    return isset($info[$method][$property]) ? $info[$method][$property] : $default;
  }
  elseif ($method) {
    return isset($info[$method]) ? $info[$method] : $default;
  }
  elseif ($property) {

    // Get this property as a list indexed by method
    $props = array();
    foreach ($info as $method => $values) {
      if (isset($values[$property])) {
        $props[$method] = $values[$property];
      }
    }
    return $props;
  }
  else {
    return $info;
  }
}

/**
 * Helper function for query builders.
 * 
 * Using schema data get 'field = [placeholder]' and args arrays
 * 
 * @param $table
 *   Table name (to be prefixed)
 * @param $params
 *   Array of field => value conditions
 * @param $table_alias
 *   Optional table alias to prefix fields in conditions
 */
function _messaging_query_conditions($table, $params, $table_alias = NULL) {
  $schema = drupal_get_schema($table);
  $conditions = $args = array();
  foreach ($params as $field => $value) {
    $type = $schema['fields'][$field]['type'];
    $field_name = $table_alias ? "{$table_alias}.{$field}" : $field;

    // For array values, build IN conditions
    if (is_array($value)) {
      $conditions[] = $field_name . ' IN (' . db_placeholders($value, $type) . ')';
      $args = array_merge($args, $value);
    }
    elseif (is_null($value)) {
      $condtions[] = $field_name . ' IS NULL';
    }
    else {
      $conditions[] = $field_name . ' = ' . db_type_placeholder($type);
      $args[] = $value;
    }
  }

  // Return arrwy with conditions and arguments. Also full where clause.
  return array(
    'conditions' => $conditions,
    'args' => $args,
    'where' => implode(' AND ', $conditions),
  );
}

/**
 * Short hand for info logs
 */
function messaging_log($txt = NULL, $variables = NULL) {
  return _messaging_log('info', $txt, $variables);
}

/**
 * Get logs without formatting
 */
function messaging_log_get() {
  if ($logs = _messaging_log('return')) {
    _messaging_log('reset');
    return $logs;
  }
}

/**
 * Init logging system so logs are saved from now on
 */
function messaging_log_start() {
  return _messaging_log('start');
}

/**
 * Short hand for debug logs
 */
function messaging_debug($txt = NULL, $variables = NULL) {
  if (function_exists('messaging_debug_log')) {
    return messaging_debug_log($txt, $variables);
  }
}

/**
 * Format logs
 */
function messaging_log_format($logs) {
  $rows = array();
  foreach ($logs as $log) {
    list($type, $string, $append, $objects) = _messaging_log_format($log);

    // Full objects/arrays are only rendered when debug module is enabled
    if ($objects && function_exists('_messaging_debug_format_log')) {
      $text = _messaging_debug_format_log($type, $string, $append, $objects);
    }
    else {
      $text = $string;
      if ($append) {
        $text .= '<br />' . implode(' ', $append);
      }
    }
    $rows[] = array(
      $type,
      $text,
    );
  }
  return theme('table', array(
    t('Type'),
    t('Message'),
  ), $rows);
}

/**
 * Quick logging for debugging and manual queue processing
 */
function _messaging_log($type, $txt = NULL, $variables = NULL, $severity = WATCHDOG_NOTICE) {
  static $enabled = FALSE;
  switch ($type) {
    case 'info':
    case 'debug':
      if ($enabled) {
        $_SESSION['messaging_log'][] = array(
          $type,
          $txt,
          $variables,
          $severity,
        );
      }
      break;
    case 'return':
      return isset($_SESSION['messaging_log']) ? $_SESSION['messaging_log'] : NULL;
      break;
    case 'reset':
      unset($_SESSION['messaging_log']);
      break;
    case 'start':
      $enabled = TRUE;
      break;
    case 'stop':
      $enabled = FALSE;
      break;
  }
}

/**
 * Format messaging / notifications log as single text line
 */
function _messaging_log_format($log) {
  list($type, $string, $args, $severity) = $log;
  if ($args) {

    // Transform arguments before inserting them.
    $append = $replace = $objects = array();
    foreach ($args as $key => $value) {
      if (is_array($value) || is_object($value)) {
        $objects[$key] = $value;
      }
      else {
        switch ($key[0]) {
          case '@':

            // Escaped only.
            $replace[$key] = check_plain($value);
            break;
          case '%':
            $replace[$key] = theme('placeholder', $value);
            break;
          case '!':

            // Pass-through.
            $replace[$key] = $value;
            break;
          default:

            // Append to string a key value pair, different from watchdog format
            $append[$key] = '<strong>' . $key . '</strong>= ' . check_plain($value);
            break;
        }
      }
    }
    $string = strtr($string, $replace);
  }
  return array(
    $type,
    $string,
    $append,
    $objects,
  );
}

Functions

Namesort descending Description
messaging_check_plain HTML to text simple filtering.
messaging_check_subject Converts strings to plain utf-8 single line
messaging_cron Implementation of hook_cron()
messaging_debug Short hand for debug logs
messaging_filter Implementation of hook_filter(). Contains a basic set of essential filters.
messaging_help Implementation of hook_help().
messaging_load_user Helper user loading function with static caching
messaging_log Short hand for info logs
messaging_log_format Format logs
messaging_log_get Get logs without formatting
messaging_log_start Init logging system so logs are saved from now on
messaging_menu Implementation of hook_menu()
messaging_message_aftersend Message default after send callback: store, log, etc..
messaging_message_callbacks Invoke ordered list of callbacks on message
messaging_message_group Returns information about message groups
messaging_message_in Process incoming message. This is the entry point for plug-in modules
messaging_message_info Returns parts of messages, that may be formatted for each sending method
messaging_message_invoke Invoke callbacks
messaging_message_load Helper function for message loading from the store
messaging_message_log Message logging callback
messaging_message_multisend Message default callback: send iterating over all destinations
messaging_message_part Returns parts of messages, that may be formatted for each sending method
messaging_message_prepare Message default callback: preprocessing
messaging_message_queue Queue message for next delivery
messaging_message_render Renders full message with header and body
messaging_message_send Send message to array of destinations. The message is rendered just once.
messaging_message_send_out Send for real, finally invoking method's callback function
messaging_message_send_user Send message to user represented by account
messaging_message_store Store messages, custom delivery
messaging_method_default Returns default messaging method
messaging_method_disable Update messaging method.
messaging_method_info Returns messaging methods properties
messaging_method_list List sending methods
messaging_method_permission Check permission for method and account
messaging_method_type Returns list of messaging methods for a type
messaging_perm Implementation of hook_perm()
messaging_pull_pending Pull pending messages for given methods and user accounts
messaging_requirements Implementation of hook_requirements()
messaging_store Entry point for the storage API
messaging_text_build Build a simple text with message subject and body
messaging_text_clean Clean text of HTML stuff and optionally of line endings
messaging_text_render Composes message from different parts, recursively and applies filter
messaging_text_replace Do token replacement.
messaging_text_truncate Truncate messages to given length. Adapted from node_teaser() in node.module
messaging_theme Implementation of hook_theme()
messaging_token_list Implementation of hook_token_list(). Documents the individual tokens handled by the module.
messaging_token_values Implementation of hook_token_values()
messaging_translate Wrapper function for 1i8nstrings() if i18nstrings enabled.
messaging_user Implementation of hook_user().
messaging_user_destination Get destination from user account.
messaging_user_setting Get setting from user account or get default setting if not available
_messaging_callback Build callback structure to be invoked later
_messaging_callback_add Add a callback to a callback collection
_messaging_callback_get Get a callback from the information array if present
_messaging_callback_invoke Invoke callback with variable arguments
_messaging_info Helper function to get property from an info array
_messaging_log Quick logging for debugging and manual queue processing
_messaging_log_format Format messaging / notifications log as single text line
_messaging_query_conditions Helper function for query builders.

Constants