You are here

messaging.module in Messaging 5

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);

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

/**
 * Implementation of hook_help().
 */
function messaging_help($section) {
  switch ($section) {
    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/template':
      $output = '<p>' . t('Configure the templates for different types of messages. Each message group is defined by other modules using the Messaging Framework.') . '</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' && arg(2) == 'template' && arg(3) == 'edit' && ($group = arg(4))) {
          $output = '<p>' . t('These are the message parts for %group. Leave blank to use the default texts or use \'%empty\' for an empty message part, preventing fallback to default message texts.', array(
            '%group' => messaging_message_group($group, 'name'),
            '%empty' => MESSAGING_EMPTY,
          )) . '</p>';
          if ($help = messaging_message_group($group, 'help')) {
            $output .= '<p>' . $help . '</p>';
          }
          return $output;
        }
      }
  }
}

/**
 * Implementation of hook_menu()
 */
function messaging_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'title' => t('Messaging'),
      'path' => 'admin/messaging',
      'callback' => 'messaging_admin_overview_page',
      'access' => user_access('administer messaging'),
      'description' => t('Administration of messages and sending methods'),
    );
    $items[] = array(
      'title' => t('Messaging methods'),
      'description' => t('Configuration of sending methods'),
      'path' => 'admin/messaging/settings',
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'messaging_admin_settings',
      ),
    );
    $items[] = array(
      'title' => t('Messaging templates'),
      'callback' => 'messaging_admin_template_overview',
      'description' => t('Configuration of message templates'),
      'path' => 'admin/messaging/template',
    );
    $items[] = array(
      'title' => t('Messaging templates'),
      'path' => 'admin/messaging/template/edit',
      'callback' => 'messaging_admin_message_edit',
      'type' => MENU_CALLBACK,
    );
  }
  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;
  }
}

/**
 * Admin overview page
 */
function messaging_admin_overview_page() {
  $menu = menu_get_item(NULL, 'admin/messaging');
  $content = system_admin_menu_block($menu);
  $output = theme('admin_block_content', $content);
  return $output;
}

/**
 * Menu callback. Admin overview page.
 */
function messaging_admin_page($group = NULL, $msgkey = NULL) {
  if ($group) {
    return messaging_admin_page_group($group, $msgkey);
  }
  else {
    return messaging_admin_page_overview();
  }
}

/**
 * Overview page for message templates
 */
function messaging_admin_template_overview() {
  $output = '';

  // List message groups
  $groups = module_invoke_all('messaging', 'message groups');
  $rows = array();
  foreach ($groups as $group => $group_info) {
    $rows[] = array(
      l($group_info['name'], 'admin/messaging/template/edit/' . $group),
      !empty($group_info['description']) ? $group_info['description'] : '',
    );
  }
  $output .= theme('box', t('Message groups'), theme('table', NULL, $rows));

  // List sending methods
  $rows = array();
  messaging_method_list();
  foreach (messaging_method_info() as $method => $info) {
    $rows[] = array(
      '<strong>' . $info['name'] . '</strong>',
      !empty($info['description']) ? $info['description'] : '',
    );
  }
  $output .= theme('box', t('Sending methods'), theme('table', NULL, $rows));
  return $output;
}

/**
 * Message groups edit page
 */
function messaging_admin_message_edit($group) {
  $output = '';
  $groups = module_invoke_all('messaging', 'message groups');
  if (isset($groups[$group])) {
    drupal_set_title($groups[$group]['name']);
    $output .= drupal_get_form('messaging_admin_message_form', $group, $groups[$group]);
  }
  return $output;
}

/**
 * Edit message formats
 */
function messaging_admin_message_form($group, $group_info) {
  $form['group'] = array(
    '#type' => 'value',
    '#value' => $group,
  );
  $keylist = module_invoke_all('messaging', 'message keys', $group);
  $send_methods = array(
    'default' => t('Default'),
  );
  $send_methods += messaging_method_list();
  $form['messages'] = array(
    '#tree' => TRUE,
  );
  foreach ($keylist as $key => $keyname) {
    $form['messages'][$key] = array(
      '#type' => 'fieldset',
      '#title' => $keyname,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
    );
    foreach ($send_methods as $method => $methodname) {
      $text = messaging_message_part($group, $key, $method, FALSE);
      $form['messages'][$key][$method] = array(
        '#title' => $methodname,
        '#type' => 'textarea',
        '#default_value' => $text,
        // Adjust size to actual number of lines
        '#rows' => count(explode("\n", $text)),
      );
    }
  }

  // Tokens for text replacement
  if ($tokens = messaging_tokens_get_list($group)) {
    $headers = array(
      t('Token'),
      t('Replacement value'),
    );
    $rows = array();
    foreach ($tokens as $token => $token_description) {
      $row = array();
      $row[] = '[' . $token . ']';
      $row[] = $token_description;
      $rows[] = $row;
    }
    $form['tokens'] = array(
      '#title' => t('Available tokens'),
      '#type' => 'fieldset',
      '#description' => t('These special strings will be replaced by their real value at run time.'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
    );
    $form['tokens']['list'] = array(
      '#value' => theme('table', $headers, $rows, array(
        'class' => 'description',
      )),
    );
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}

/**
 * Get list of tokens for text replacement
 *
 * @param $group
 *   Message group to get tokens for
 * @param $tokens
 *
 */
function messaging_tokens_get_list($group) {

  // First compile token types for this message group
  $type_list = module_invoke_all('messaging', 'tokens', $group);

  // Now get token list from token module for each type
  $return = array();
  foreach ($type_list as $type) {
    if ($list = token_get_list($type)) {
      foreach ($list as $category => $tokens) {
        foreach ($tokens as $token => $description) {
          $return[$token] = $description;
        }
      }
    }
  }
  return $return;
}

/**
 * Process and save message parts
 */
function messaging_admin_message_form_submit($form_id, $form_values) {
  $group = $form_values['group'];
  foreach ($form_values['messages'] as $key => $messages) {
    foreach ($messages as $method => $text) {
      db_query("DELETE FROM {messaging_message_parts} WHERE type = '%s' AND msgkey = '%s' AND method = '%s'", $group, $key, $method);
      if ($text = trim($text)) {
        db_query("INSERT INTO {messaging_message_parts} (type, msgkey, method, module, message) VALUES('%s', '%s', '%s', '', '%s')", $group, $key, $method, $text);
      }
    }
  }
  drupal_set_message('The messages have been updated');
}

/**
 * Admin settings form
 */
function messaging_admin_settings() {

  // Get plug-in information and produce big warning if none enabled.
  $methods = messaging_method_list();
  if (!$methods) {
    drupal_set_message(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'),
    )), 'error');
  }
  $form['general'] = array(
    '#type' => 'fieldset',
    '#title' => t('General settings'),
  );
  $form['general']['messaging_default_method'] = array(
    '#title' => t('Default send method'),
    '#type' => 'radios',
    '#options' => $methods,
    '#default_value' => variable_get('messaging_default_method', ''),
  );

  // Logging settings
  $period = array(
    0 => t('Disabled'),
  ) + drupal_map_assoc(array(
    3600,
    10800,
    21600,
    32400,
    43200,
    86400,
    172800,
    259200,
    604800,
    1209600,
    2419200,
    4838400,
    9676800,
  ), 'format_interval');
  $form['general']['messaging_log'] = array(
    '#title' => t('Logging'),
    '#type' => 'select',
    '#options' => $period,
    '#default_value' => variable_get('messaging_log', 0),
    '#description' => t('If enabled all messages will be logged and kept for the specified time after they\'re sent.'),
  );

  // Sending methods settings
  $form['messaging_methods'] = array(
    '#type' => 'fieldset',
    '#title' => t('Settings for messaging methods'),
    '#collapsible' => TRUE,
    '#description' => t('Depending on your content format and the tokens you are using for messages it is important that you use the right filtering for the message body.') . ' ' . t('Set up the filters you need using the !input_formats page', array(
      '!input_formats' => l('Input Formats', 'admin/settings/filters'),
    )),
  );
  if ($info = messaging_method_info()) {
    foreach (filter_formats() as $format) {
      $format_options[$format->format] = $format->name;
    }

    // We add this last for it not bo be default
    $format_options[0] = t('None (Insecure)');
    foreach ($info as $method => $options) {
      $key = 'messaging_method_' . $method;

      // This will preserve settings for disabled modules
      $form['messaging_methods'][$key] = array(
        '#type' => 'fieldset',
        '#title' => t('!name settings', array(
          '!name' => $options['name'],
        )),
        '#tree' => TRUE,
      );

      // Output filter applied to message body
      $form['messaging_methods'][$key]['filter'] = array(
        '#type' => 'select',
        '#title' => t('Message body filter'),
        '#default_value' => isset($options['filter']) ? $options['filter'] : variable_get('messaging_default_filter', ''),
        '#options' => $format_options,
      );
    }
  }
  else {
    $form['messaging_methods']['warning'] = array(
      '#value' => t('You should enable some messaging method plug-ins for this to work.'),
    );
  }

  // Processing limits
  $limit = variable_get('messaging_process_limit', array(
    'message' => 0,
    'percent' => 0,
    'time' => 0,
  ));
  $form['messaging_process_limit'] = array(
    '#type' => 'fieldset',
    '#title' => t('Limits for queue processing'),
    '#tree' => TRUE,
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => t('These are the limits for each cron run on queue processing. The process will stop when it first meets any of them. Set to 0 for no limit.'),
  );
  $form['messaging_process_limit']['message'] = array(
    '#title' => t('Number of messages sent'),
    '#type' => 'textfield',
    '#size' => 10,
    '#default_value' => $limit['message'],
  );
  $form['messaging_process_limit']['time'] = array(
    '#title' => t('Time (seconds)'),
    '#type' => 'textfield',
    '#size' => 10,
    '#default_value' => $limit['time'],
  );
  $form['messaging_process_limit']['percent'] = array(
    '#title' => t('Time (% of cron time)'),
    '#type' => 'textfield',
    '#size' => 10,
    '#default_value' => $limit['percent'],
    '#description' => t('Maximum percentage of cron time the process may use.'),
  );
  return system_settings_form($form);
}

/** 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
 * @param $queue
 *   1 to force queueing instead of sending right away
 */
function messaging_message_send_user($account, $message, $method = NULL, $queue = 0) {

  // Get default sending method, or default for this user account
  $method = $method ? $method : messaging_method_default($account);
  $message['account'] = $account;

  // If debug module and option enabled, hand over to messaging_debug
  if (variable_get('messaging_debug', 0) && function_exists('messaging_debug_send_user')) {
    return messaging_debug_send_user($account, $message, $method);
  }

  // The sending method may override this function
  if (($function = messaging_method_info($method, 'send_user')) && function_exists($function)) {
    return $function($account, $message, $method);
  }

  // Get destination property from user account, or pass the account itself
  if ($property = messaging_method_info($method, 'destination')) {
    $destination[] = $account->{$property};
  }
  else {
    $destination[] = $account;
  }
  return messaging_message_send($destination, $message, $method, $queue);
}

/**
 * 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 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) {

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

  // 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, $method);
  }

  // Renders subject and body applying filters in the process
  if (!empty($info['render'])) {
    $message = call_user_func($info['render'], $message, $info);
  }
  else {
    $message = messaging_message_render($message, $info);
  }

  // Decide on queue, log, cron and send options, prepara parameters
  $sent = $cron = $log = 0;

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

  // It will be queued always for pull methods
  if ($queue || $info['type'] & MESSAGING_TYPE_PULL) {
    $queue = 1;
  }

  // And it will be kept as log if logging or debug enabled
  if (variable_get('messaging_log', 0)) {
    $log = 1;
  }

  // Send if not for queueing or not debugging enabled
  if (variable_get('messaging_debug', 0) && function_exists('messaging_debug_send_msg')) {
    $log = 1;
    $cron = 0;
    $method = 'debug';
  }
  if (!$queue) {
    $success = TRUE;
    foreach ($destinations as $to) {

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

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

  // Depending on parameters and what's happened so far we make the final queue/log decision
  if ($queue || $log) {
    messaging_store('save', $method, $destinations, $message, $sent, $queue, $log, $cron);
    $sent = TRUE;
  }

  // This will return true if the message was sent or queued for delivery
  return $sent || $queue;
}

/**
 * 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 array
 * @param $method
 *   Sending method
 * 
 * @return boolean
 *   Message successfully sent
 */
function messaging_message_send_out($destination, $message, $method) {
  if (($function = messaging_method_info($method, 'send')) && function_exists($function)) {

    // Check for specific parameters for this sending method
    $group = messaging_method_info($method, 'group');
    $params = !empty($message['params'][$group]) ? $message['params'][$group] : array();
    return $function($destination, $message, $params);
  }
  else {
    watchdog('messaging', t('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, $limit = 0, $delete = TRUE) {
  return messaging_store('pull_pending', $method, $users, $limit, $delete);
}

/**
 * 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
      if (!messaging_method_permission($method, $account)) {
        unset($info[$method]);
      }
    }
  }
  return $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 && $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());
  }
}

/**
 * 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);
  }
  if ($key) {
    return isset($info[$group][$key]) ? $info[$group][$key] : NULL;
  }
  elseif ($group) {
    return isset($info[$group]) ? $info[$group] : array();
  }
  else {
    return $info;
  }
}

/**
 * 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');
  }
  if ($key) {
    return isset($info[$group][$key]) ? $info[$group][$key] : NULL;
  }
  elseif ($group) {
    return isset($info[$group]) ? $info[$group] : array();
  }
  else {
    return $info;
  }
}

/**
 * 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
    foreach (array_keys($info) as $name) {
      $info[$name] = array_merge($info[$name], variable_get('messaging_method_' . $name, array()));
    }

    /*
    if ($settings = variable_get('messaging_methods', array())) {
      $info = array_merge_recursive($info, $settings);
    }
    */
  }
  if ($method && $property) {
    return isset($info[$method][$property]) ? $info[$method][$property] : $default;
  }
  elseif ($method) {
    return isset($info[$method]) ? $info[$method] : array();
  }
  elseif ($property) {
    if (!isset($properties[$property])) {
      $properties[$property] = array();
      foreach ($info as $method => $values) {
        if (isset($values[$property])) {
          $properties[$property][$method] = $values[$property];
        }
      }
    }
    return $properties[$property];
  }
  else {
    return $info;
  }
}

/** Message composition and rendering **/

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

  // 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' => '',
  );
  $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;
  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 filter');
      if (function_exists('drupal_html_to_text')) {
        $info[1] = t('HTML to text');
      }
      return $info;
    case 'no cache':
      return TRUE;

    // No caching at all, at least for development
    case 'description':
      switch ($delta) {
        case 0:
          return t('Filters out all HTML tags and replaces HTML entities by characters.');
        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);
        case 1:
          return messaging_html_to_text($text);
        default:
          return $text;
      }
    case 'settings':
      return;
    default:
      return $text;
  }
}

/**
 * HTML to text conversion
 * 
 * Uses html_to_text facility if available or simple filtering otherwise
 */
function messaging_html_to_text($text) {
  if (function_exists('drupal_html_to_text')) {
    return drupal_html_to_text($text);
  }
  else {
    return messaging_check_plain($text);
  }
}

/**
 * HTML to text simple filtering.
 * 
 * Just strip out all HTML tags and decode entities
 */
function messaging_check_plain($text) {

  // 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);

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

  // HTML entities to plain text conversion.
  $text = decode_entities($text);
  return $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);
}

/**
 * Log facility for debugging
 */
function messaging_log($txt = NULL) {
  static $logs;
  if ($txt) {
    $logs[] = $txt;
  }
  else {
    return $logs;
  }
}

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

/**
 * Implementation of hook_simpletest().
 */
function messaging_simpletest() {
  $dir = drupal_get_path('module', 'messaging') . DIRECTORY_SEPARATOR . 'tests';
  require_once $dir . DIRECTORY_SEPARATOR . 'messaging_testcase.inc';
  $tests = file_scan_directory($dir, '\\.test');
  return array_keys($tests);
}

/**
 * Helper function for mail methods
 * 
 * This is the only non method agnostic functionality in this module. As there are several plug-ins
 * for mail sending, we add this helper function here so its available for all them
 */
function messaging_mail_params($message, $params) {

  // The message 'from' will depend on message sender if present
  if (empty($params['from'])) {
    if (!empty($message['sender_account']) && !empty($message['sender_account']->mail)) {
      $params['from'] = check_plain($message['sender_account']->name) . ' <' . $message['sender_account']->mail . '>';
    }
    elseif (!empty($message['sender_name']) && ($default_from = variable_get('site_mail', ini_get('sendmail_from')))) {
      $params['from'] = check_plain($message['sender_name']) . ' <' . $default_from . '>';
    }
  }

  // Fill in params with default values if not present
  $params += array(
    'from' => NULL,
    'headers' => array(),
    'mailkey' => 'message-' . $message['type'],
  );
  return $params;
}

Functions

Namesort descending Description
messaging_admin_message_edit Message groups edit page
messaging_admin_message_form Edit message formats
messaging_admin_message_form_submit Process and save message parts
messaging_admin_overview_page Admin overview page
messaging_admin_page Menu callback. Admin overview page.
messaging_admin_settings Admin settings form
messaging_admin_template_overview Overview page for message templates
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_filter Implementation of hook_filter(). Contains a basic set of essential filters.
messaging_help Implementation of hook_help().
messaging_html_to_text HTML to text conversion
messaging_load_user Helper user loading function with static caching
messaging_log Log facility for debugging
messaging_mail_params Helper function for mail methods
messaging_menu Implementation of hook_menu()
messaging_message_group Returns information about message groups
messaging_message_info Returns parts of messages, that may be formatted for each sending method
messaging_message_part Returns parts of messages, that may be formatted for each sending method
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_method_default Returns default 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_simpletest Implementation of hook_simpletest().
messaging_store Entry point for the storage API
messaging_text_render Composes message from different parts, recursively and applies filter
messaging_tokens_get_list Get list of tokens for text replacement
messaging_user Implementation of hook_user().

Constants