You are here

messaging.module in Messaging 6.4

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 from queue by the plug-in
define('MESSAGING_TYPE_PULL', 2);

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

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

// Disable queue for this method, when sending is more 'expensive' than queueing (like for simple method)
define('MESSAGING_TYPE_NOQUEUE', 16);

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

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

// Limit the size of static caches
define('MESSAGING_CACHE_LIMIT', variable_get('messaging_cache_limit', 1000));

// Limit the max % of cron time this module can use (this can be overridden by some modules)
define('MESSAGING_DEFAULT_CRON_PERCENT', variable_get('messaging_default_cron_percent', 80));

/**
 * Implementation of hook_autoload_info().
 */
function messaging_autoload_info() {
  return array(
    'Messaging_User_Object' => array(
      'file' => 'includes/messaging_object.class.inc',
    ),
    'Messaging_Object' => array(
      'file' => 'includes/messaging_object.class.inc',
    ),
    'Messaging_Cached_Object' => array(
      'file' => 'includes/messaging_object.class.inc',
    ),
    'Messaging_Destination' => array(
      'file' => 'includes/messaging_destination.class.inc',
    ),
    'Messaging_Message' => array(
      'file' => 'includes/messaging_message.class.inc',
    ),
    'Messaging_Send_Method' => array(
      'file' => 'includes/messaging_method.class.inc',
    ),
    'Messaging_Store' => array(
      'file' => 'includes/messaging_store.class.inc',
    ),
    'Messaging_None' => array(
      'file' => 'includes/messaging_object.class.inc',
    ),
  );
}

/**
 * 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;
    case 'admin/messaging/settings/method/filters':
      $output = '<p>' . t('These are the filters for the message body. They should depend on the content and the tokens you are using for messages. This is important for getting the right formatting and also for security.') . '</p>';
      $oubput .= t('If using raw tokens for templates, possibly you\'ll need some additional formatting here.');
      $items[] = t('You can set up Input formats for specific message parts on the <a href="@message_templates">Message templates pages</a>. These will be run first on each piece of text.', array(
        '@message_templates' => url('admin/messaging/template'),
      ));
      $items[] = t('Once the message body is built you can set a <strong>Format filter</strong> on this page to format and filter it. Set up the filters you need using the <a href="@input_formats">Input formats</a> page', array(
        '@input_formats' => url('admin/settings/filters'),
      ));
      $items[] = t('Last, the <strong>Final filter</strong> will be run for adjusting the text to the format required by each Send method.');
      $output .= theme('item_list', $items);
      return $output;
  }
}

/**
 * Implementation of hook_menu()
 */
function messaging_menu() {
  $items['admin/messaging'] = array(
    'title' => 'Messaging',
    'access arguments' => array(
      'administer site configuration',
    ),
    '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 site configuration',
    ),
    '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 site configuration',
    ),
    'weight' => -10,
    'file' => 'messaging.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/messaging/settings/method/overview'] = array(
    'title' => 'Options',
    'description' => 'General settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/messaging/settings/method/filters'] = array(
    'title' => 'Filters',
    'description' => 'Filters and formatting',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'messaging_admin_method_filters',
    ),
    'access arguments' => array(
      'administer filters',
    ),
    'file' => 'messaging.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );

  // Test tab, moving from messaging_debug()
  $items['admin/messaging/settings/test'] = array(
    'title' => 'Test',
    'description' => 'Test message sending',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'messaging_admin_test_post_form',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'messaging.admin.inc',
    'weight' => -10,
    'type' => MENU_LOCAL_TASK,
  );
  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 'delete':

      // Delete user data from tables
      Messaging_Destination::delete_multiple(array(
        'uid' => $user->uid,
      ));
      messaging_store()
        ->delete_multiple(array(
        'uid' => $user->uid,
      ));
      break;
    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 **/

/**
 * Get send method object
 * 
 * @param $method
 *   Method name or Messaging_Method object
 * @param $method_info
 *   Array of properties to build a custom send method or override existing ones
 */
function messaging_send_method($method, $method_info = array()) {
  $send_methods =& messaging_static(__FUNCTION__);
  if (is_object($method)) {
    return $method;
  }
  if (!isset($send_methods[$method])) {

    // This will init the method list if not done before
    if ($info = messaging_method_info($method)) {
      $class = !empty($info['class']) ? $info['class'] : 'Messaging_Send_Method';
      $send_methods[$method] = new $class($method, $method_info + $info);
    }
    else {

      // No info available from modules. This is a custom method or a disabled one
      $send_methods[$method] = new Messaging_Send_Method($method, $method_info);

      // Add to static cache so it can be found later
      $static_info =& messaging_info('send methods');
      $static_info[$method] = $method_info;
    }
  }
  return $send_methods[$method];
}

/**
 * Build message object from object or array
 */
function messaging_message_build($message) {
  return messaging_check_object($message, 'Messaging_Message', TRUE);
}

/**
 * Send message to array of destinations. The message is rendered just once.
 *
 * @param $destinations
 *   Single destination or 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 of message parts.
 * @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 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 = FALSE) {
  messaging_debug('Sending message', array(
    'destinations' => $destinations,
    'message' => $message,
    'method' => $method,
    'queue' => $queue,
  ));

  // Convert into an object and add all the information into the message object
  $message = messaging_message_build($message);
  $message->method = $method;
  if (is_array($destinations)) {
    $message
      ->set_multiple($destinations);
  }
  else {
    $message
      ->set_destination($destinations);
  }

  // This will return true if the message was sent or queued for delivery
  return $queue ? $message
    ->queue() : $message
    ->send();
}

/**
 * Send message to destination object
 *
 * @param $destination
 *   Messaging Destination object to recieve message.
 * @param $message
 *   Message object or array of message parts.
 * @param $queue
 *   Optional send method. Defaults to the user account predefined method
 */
function messaging_message_send_destination($method, $destination, $message, $queue = FALSE) {
  messaging_debug('Sending message to destination', array(
    'destination' => $destination,
    'message' => $message,
  ));

  // Build array of parameters so they can be overridden by callbacks
  $message = messaging_message_build($message);
  $message->method = $method;
  $message
    ->set_destination($destination);
  return $queue ? $message
    ->queue() : $message
    ->send();
}

/**
 * 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 = FALSE) {
  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 = messaging_message_build($message);
  if (isset($method)) {
    $message->method = $method;
  }

  // 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 ($message
    ->set_user($account)) {
    messaging_debug('Found destination for user, sending message', array(
      'method' => $message->method,
      'destination' => $message->destination,
    ));
    return $queue ? $message
      ->queue() : $message
      ->send();
  }
  else {
    watchdog('messaging', 'Cannot send message to user !username. @message', array(
      '!username' => theme('username', $account),
      '@message' => (string) $message,
    ), WATCHDOG_WARNING);
    return FALSE;
  }
}

/**
 * Send message to multiple users. For each user it will use the preferred method
 * 
 * @param $users
 *   User id, array of user ids, user account or array of user accounts
 * @param $message
 *   Message object to use as template or array with 'subject' and 'body' texts
 *   
 * @return array
 *   Array with sending result, indexed by user id
 */
function messaging_message_bulk_send($users, $message, $queue = FALSE) {

  // Convert users to array of user account objects
  $users = is_array($users) ? $users : array(
    $users,
  );
  $users = array_map('messaging_user_object', $users);
  $template = messaging_message_build($message);
  $results = array();
  foreach ($users as $account) {
    $message = clone $template;
    $results[$account->uid] = messaging_message_send_user($account, $message, NULL, $queue);
  }
  return $results;
}

/**
 * Build destination object from user account (and create destination if not exists)
 */
function messaging_account_build_destination($account, $method = NULL, $address = NULL) {
  if ($account->uid) {
    if ($method && !$address) {
      $address = messaging_user_destination($account, $method);
    }
    if ($method && $address) {
      return Messaging_Destination::create_method($method, $address, $account->uid);
    }
    elseif (($fallback = messaging_method_default($account)) && $fallback != $method) {

      // Retry with new method
      return messaging_account_build_destination($account, $fallback);
    }
  }
  elseif ($method && $address) {

    // Anonymous users
    // @todo check the address doesn't belong to any user ???
    return Messaging_Destination::create_method($method, $address, 0);
  }
}

/**
 * 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) {
  return messaging_send_method($method)
    ->get_user_address($account);
}

/**
 * Test sending, just log message
 */
function messaging_message_test($message, $destinations = array()) {
  $message
    ->prepare();
  $message
    ->render();
  messaging_log('Emulating message sending (test run)', array(
    'summary' => (string) $message,
    'message' => $message,
    'destinations' => $destinations,
  ));
  return $message->success = TRUE;
}

/**
 * Implementation of hook_cron()
 * 
 * Process queued messages for delivery, update, do clean up...
 */
function messaging_cron() {
  if (variable_get('messaging_queue_process_cron', TRUE)) {
    messaging_store()
      ->cron_process();
  }
}

/**
 * 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
 *   If no account passed it is a list for the administrator
 */
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) {
  return messaging_method_info($method, 'enabled') && messaging_send_method($method)
    ->user_access($account);
}

/**
 * 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 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) {
  static $info;
  if (!isset($info)) {

    // Collect method info without 'alter', we do it later
    $info =& messaging_info('send methods', NULL, FALSE, FALSE);

    // Get list of enabled methods. All will be enabled by default.
    $enabled = variable_get('messaging_method_enabled', array());

    // Merge settings from variable for each enabled method
    foreach (array_keys($info) as $name) {
      $info[$name] = array_merge($info[$name], variable_get('messaging_method_' . $name, array()), variable_get('messaging_filters_' . $name, array()));

      // If not set enabled flag, the method will be enabled by default
      $info[$name]['enabled'] = isset($enabled[$name]) ? $enabled[$name] : TRUE;
    }

    // Allow altering by other modules after we've set the variables
    drupal_alter('messaging_send_methods', $info);
  }
  return messaging_array_info($info, $method, $property, $default);
}

/**
 * Returns messaging address properties
 */
function messaging_address_info($type = NULL, $property = NULL, $default = NULL) {
  $info =& messaging_info('address types');
  return messaging_array_info($info, $type, $property, $default);
}

/**
 * Implementation of hook_messaging()
 */
function messaging_messaging($op, $type = NULL) {
  switch ($op) {
    case 'text filters':
      messaging_include('text.inc');
      return _messaging_text_filter_info();
    case 'address types':

      // Get some built in address types
      $types['user'] = array(
        'name' => t('User name'),
        // Name of the address for this method
        'format callback' => 'messaging_user_format_name',
        // Format address for display
        'account_property' => 'uid',
        // Property on user account
        'field_name' => 'uid',
        // Field on which this address is stored
        'field_table' => 'users',
      );
      return $types;
  }
}

/**
 * Map mail to uid
 */
function messaging_address_get_uid($address, $property = 'mail') {
  return db_result(db_query("SELECT uid FROM {users} WHERE {$property} = '%s'", $address));
}

/**
 * Entry point for the storage and queueing API
 * 
 * Default methods are implemented by Messaging_Store class
 * 
 * This API can be replaced by setting a new class name in the 'messaging_store' variable
 * Or the whole thing can be skipped by settings 'messaging_store' to 'Messaging_None'
 */
function messaging_store() {
  static $messaging_store;
  if (!isset($messaging_store)) {
    $class = variable_get('messaging_store', 'Messaging_Store');
    $messaging_store = new $class();
  }
  return $messaging_store;
}

/**
 * Helper user loading function with static caching
 */
function messaging_load_user($uid) {
  $cache =& messaging_static(__FUNCTION__);
  if (!$cache || !array_key_exists($uid, $cache)) {
    if ($uid) {
      $cache[$uid] = user_load($uid);
    }
    else {
      $cache[0] = drupal_anonymous_user();

      // This will prevent some notice from token module
      $cache[0]->name = variable_get('anonymous', 'Anonymous');
    }
  }
  return $cache[$uid];
}

/**
 * Helper function for message loading from the store
 * 
 * @param $mqid
 *   Message id
 */
function messaging_message_load($mqid, $refresh = FALSE) {
  return messaging_store()
    ->message_load($mqid, $refresh);
}

/**
 * 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
 * 
 * @param $method
 *   Method to disable
 * @param $replace
 *   Optional replacement method suggested by the disabled one.
 */
function messaging_method_disable($method, $replace = NULL) {
  module_load_include('install', 'messaging');
  $replace = isset($replace) ? $replace : messaging_update_method_replace($method, TRUE);
  messaging_update_method_disable($method, $replace);
  if ($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);
}

/**
 * Check that a parameter is an instance of a given class
 */
function messaging_check_object($object, $class, $create = FALSE) {
  if ($object && is_object($object) && is_a($object, $class)) {
    return $object;
  }
  elseif ($create) {
    return new $class($object);
  }
}

/**
 * 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.');
  }
  if ($type == 'destination' || $type == 'all') {
    $tokens['destination']['destination-address'] = t('Destination address.');
    $tokens['destination']['destination-type'] = t('Destination address type.');
  }
  return $tokens;
}

/**
 * Implementation of hook_token_values()
 */
function messaging_token_values($type, $object = NULL, $options = array()) {
  $language = isset($options['language']) ? $options['language'] : $GLOBALS['language'];
  switch ($type) {
    case 'message':
      if ($message = messaging_check_object($object, 'Messaging_Message')) {
        $values['message-subject'] = check_plain($message
          ->get_subject());
        $values['message-body'] = filter_xss($message
          ->get_body());
        $values['message-author-name'] = check_plain($message
          ->get_sender_name());
        $values['message-method'] = messaging_method_info($message->method, 'name');
        $timezone = isset($options['timezone']) ? $options['timezone'] : variable_get('date_default_timezone', 0);
        $values['message-date'] = format_date($message->sent, 'medium', '', $timezone, $language->language);
        return $values;
      }
      break;
    case 'destination':

      // Messaging destinations
      if ($destination = messaging_check_object($object, 'Messaging_Destination')) {
        $values['destination-address'] = $destination
          ->format_address(FALSE);
        $values['destination-type'] = $destination
          ->address_name();
        return $values;
      }
      break;
  }
}

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

/**
 * 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_array_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) {
  $query = _messaging_query_where($table, $params, $table_alias);

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

/**
 * Query builder: Build where conditions and arguments using schema data
 * 
 * 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
 *   
 * @return array()
 *   Array with 'where' (array of where conditions) and 'args' (array of query arguments)
 */
function _messaging_query_where($table, $params, $table_alias = NULL) {
  $schema = drupal_get_schema($table);
  $conditions = $args = array();
  foreach ($params as $field => $value) {

    // If not a field we ignore the parameter
    if (isset($schema['fields'][$field])) {
      $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(
    'where' => $conditions,
    'args' => $args,
  );
}

/**
 * Build the SQL statement from query elements
 * 
 * It will build INSERT + SELECT or SELECT queries from its elements
 * 
 * @param $query
 *   Array of query parameters
 * @param $execute
 *   Whether to execute the query right away or return parameters
 * @return mixed
 *   If not $execute, list($sql, $args)
 *   If execute, query result
 */
function messaging_query_sql($query, $execute = FALSE) {
  $sql = !empty($query['sql']) ? $query['sql'] : '';
  if (!empty($query['insert'])) {
    $sql .= 'INSERT INTO ' . $query['into'] . ' (' . implode(', ', $query['insert']) . ') ';
  }
  if (!empty($query['select'])) {
    $sql .= !empty($query['distinct']) ? 'SELECT DISTINCT ' : 'SELECT ';
    $sql .= implode(', ', $query['select']);
    $sql .= ' FROM ' . implode(', ', $query['from']);
  }
  if (!empty($query['join'])) {
    $sql .= ' ' . implode(' ', $query['join']);
  }
  if (!empty($query['where'])) {
    $sql .= ' WHERE (' . implode(') AND (', $query['where']) . ')';
  }
  if (!empty($query['group'])) {
    $sql .= ' GROUP BY ' . implode(', ', $query['group']);
  }
  if (!empty($query['having'])) {
    $sql .= ' HAVING ' . implode(' AND ', $query['having']);
  }

  // Merge all args, start with generic ones for subscription queries, then other groups
  $args = !empty($query['args']) ? $query['args'] : array();
  foreach (array(
    'select',
    'join',
    'where',
    'having',
  ) as $key) {
    if (!empty($query[$key . ' args'])) {
      $args = array_merge($args, $query[$key . ' args']);
    }
  }

  // Add order by
  if (!empty($query['order'])) {
    $sql .= ' ORDER BY ' . implode(', ', $query['order']);
  }

  // Return parameters or execute query
  if (!$execute) {
    return array(
      $sql,
      $args,
    );
  }
  elseif (!empty($query['pager'])) {
    return pager_query($sql, $query['limit'], $query['pager'], NULL, $args);
  }
  elseif (!empty($query['limit'])) {
    return db_query_range($sql, $args, 0, $query['limit']);
  }
  else {
    return db_query($sql, $args);
  }
}

/**
 * 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;
  $append = $replace = $objects = array();
  if ($args) {

    // Transform arguments before inserting them.
    foreach ($args as $key => $value) {
      if (is_numeric($value) || is_string($value)) {
        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;
        }
      }
      elseif (is_array($value) || is_object($value)) {
        $objects[$key] = $value;
      }
    }
    $string = strtr($string, $replace);
  }
  return array(
    $type,
    $string,
    $append,
    $objects,
  );
}

/**   
 * 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;
}

/**
 * Implementation of hook_locale().
 */
function messaging_locale($op = 'groups') {
  switch ($op) {
    case 'groups':
      return array(
        'messaging' => t('Messaging'),
      );
    case 'info':
      $info['messaging']['refresh callback'] = 'messaging_locale_refresh';
      $info['messaging']['format'] = TRUE;

      // Strings may have format
      return $info;
  }
}

/**
 * Refresh strings for translation system
 */
function messaging_locale_refresh() {
  foreach (messaging_method_info() as $name => $info) {
    i18nstrings_update("messaging:method:{$name}:name", $info['name']);
  }
  return TRUE;
}

/**
 * Set data on static cache, checking whether the cache is under limits.
 */
function messaging_static_cache_set($cache_name, $key, $value) {
  $cache =& messaging_static($cache_name);
  if ($cache && MESSAGING_CACHE_LIMIT && count($cache) > MESSAGING_CACHE_LIMIT) {
    $cache = array();
  }
  $cache[$key] = $value;
}

/**
 * Get data from static cache
 */
function messaging_static_cache_get($cache_name, $key) {
  $cache =& messaging_static($cache_name);
  return isset($cache[$key]) ? $cache[$key] : NULL;
}

/**
 * Central static variable storage, Drupal 7 core backport
 * 
 * See http://api.drupal.org/api/function/drupal_static/7
 */
function &messaging_static($name, $default_value = NULL, $reset = FALSE) {
  static $data = array(), $default = array();
  if (!isset($name)) {

    // All variables are reset. This needs to be done one at a time so that
    // references returned by earlier invocations of drupal_static() also get
    // reset.
    foreach ($default as $name => $value) {
      $data[$name] = $value;
    }

    // As the function returns a reference, the return should always be a
    // variable.
    return $data;
  }
  if ($reset) {

    // The reset means the default is loaded.
    if (array_key_exists($name, $default)) {
      $data[$name] = $default[$name];
    }
    else {

      // Reset was called before a default is set and yet a variable must be
      // returned.
      return $data;
    }
  }
  elseif (!array_key_exists($name, $data)) {

    // Store the default value internally and also copy it to the reference to
    // be returned.
    $default[$name] = $data[$name] = $default_value;
  }
  return $data[$name];
}

/**
 * Reset static variable. Drupal 7 core backport.
 * 
 * See http://api.drupal.org/api/function/drupal_static_reset/7
 * 
 * @param $name
 *   Name of the static variable to reset. Omit to reset all variables.
 */
function messaging_static_reset($name = NULL) {
  messaging_static($name, NULL, TRUE);
}

/**
 * Load destination from key. Menu autoloading.
 */
function messaging_destination_load($mdid) {
  return Messaging_Destination::load($mdid);
}

/**
 * Include module files as necessary.
 * 
 * The files must be in an 'includes' subfolder inside the module folder. 
 */
function messaging_include($file, $module = 'messaging') {
  static $included = array();
  if (!isset($included[$module][$file])) {
    require_once './' . drupal_get_path('module', $module) . '/includes/' . $file;
    $included[$module][$file] = TRUE;
  }
}

/**
 * Invoke hook_notifications($name) on all modules
 * 
 * This is like module_invoke all with some differences:
 * - The results are just merged (not recursively)
 * - The module name is added to each resulting array
 * - We cache all the results 
 */
function &messaging_info($name, $param = NULL, $refresh = FALSE, $alter = TRUE) {
  $info =& messaging_static('messaging_info_' . $name);
  if (!isset($info) || $refresh) {
    $info = messaging_module_invoke_all('messaging', $name, $param);

    // Provide alter hook
    if ($alter) {
      drupal_alter('messaging_' . strtr($name, ' ', '_'), $info);
    }
  }
  return $info;
}

/**
 * Invoke hook on all modules
 * 
 * This is like module_invoke_all with some differences:
 * - The results are just merged (not recursively)
 * - The module name is added to each resulting array
 * 
 * @param $hook
 *   The name of the hook to invoke.
 * @param ...
 *   Arguments to pass to the hook.
 * @return
 *   An array of return values of the hook implementations. If modules return
 *   arrays from their implementations, those are merged into one array.
 */
function messaging_module_invoke_all() {
  $args = func_get_args();
  $hook = $args[0];
  unset($args[0]);
  $return = array();
  foreach (module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    $result = call_user_func_array($function, $args);
    if (isset($result) && is_array($result)) {
      foreach ($result as $key => &$value) {
        if (is_array($value)) {
          $value += array(
            'module' => $module,
          );
        }
      }
      $return = array_merge($return, $result);
    }
    else {
      if (isset($result)) {
        $return[] = $result;
      }
    }
  }
  return $return;
}

/**
 * Helper function, get uid from account parameter (object or uid)
 */
function messaging_user_uid($account) {
  return is_object($account) ? $account->uid : (int) $account;
}

/**
 * Helper function, get object from account parameter (object or uid)
 */
function messaging_user_object($account) {
  return is_object($account) ? $account : messaging_load_user((int) $account);
}

/**
 * Helper function, get user object property from account parameter (object or uid)
 */
function messaging_user_property($account, $property) {
  $account = messaging_user_object($account);
  return $account && isset($account->{$property}) ? $account->{$property} : NULL;
}

/**
 * Callback for printing user names
 */
function messaging_user_format_name($account, $html = FALSE) {
  $account = messaging_user_object($account);
  return $html ? theme('username', $account) : check_plain($account->name);
}

Functions

Namesort descending Description
messaging_account_build_destination Build destination object from user account (and create destination if not exists)
messaging_address_get_uid Map mail to uid
messaging_address_info Returns messaging address properties
messaging_array_info Helper function to get property from an info array
messaging_autoload_info Implementation of hook_autoload_info().
messaging_check_object Check that a parameter is an instance of a given class
messaging_cron Implementation of hook_cron()
messaging_debug Short hand for debug logs
messaging_destination_load Load destination from key. Menu autoloading.
messaging_help Implementation of hook_help().
messaging_include Include module files as necessary.
messaging_info Invoke hook_notifications($name) on all modules
messaging_load_user Helper user loading function with static caching
messaging_locale Implementation of hook_locale().
messaging_locale_refresh Refresh strings for translation system
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_build Build message object from object or array
messaging_message_bulk_send Send message to multiple users. For each user it will use the preferred method
messaging_message_in Process incoming message. This is the entry point for plug-in modules
messaging_message_load Helper function for message loading from the store
messaging_message_send Send message to array of destinations. The message is rendered just once.
messaging_message_send_destination Send message to destination object
messaging_message_send_user Send message to user represented by account
messaging_message_test Test sending, just log message
messaging_messaging Implementation of hook_messaging()
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_module_invoke_all Invoke hook on all modules
messaging_perm Implementation of hook_perm()
messaging_query_sql Build the SQL statement from query elements
messaging_requirements Implementation of hook_requirements()
messaging_send_method Get send method object
messaging_static Central static variable storage, Drupal 7 core backport
messaging_static_cache_get Get data from static cache
messaging_static_cache_set Set data on static cache, checking whether the cache is under limits.
messaging_static_reset Reset static variable. Drupal 7 core backport.
messaging_store Entry point for the storage and queueing API
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_format_name Callback for printing user names
messaging_user_object Helper function, get object from account parameter (object or uid)
messaging_user_property Helper function, get user object property from account parameter (object or uid)
messaging_user_setting Get setting from user account or get default setting if not available
messaging_user_uid Helper function, get uid from account parameter (object or uid)
_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_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.
_messaging_query_where Query builder: Build where conditions and arguments using schema data

Constants