'.t('The messaging module will allow sending messages using different methods, depending on the plug-ins you have enabled.').'
'; return $output; case 'admin/help#filters': return $output; default: if (arg(0) == 'admin' && arg(1) == 'settings') { if (arg(2) == 'filters') { return ''.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.').'
'; } if (arg(2) == 'messaging' && arg(3) == 'edit' && ($group = arg(4))) { if ($help = messaging_message_group($group, 'help')) { return $help; } } } } } /** * Implementation of hook_menu() */ function messaging_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'title' => t('Messaging'), 'path' => 'admin/settings/messaging', 'callback' => 'messaging_admin_page', 'access' => user_access('administer messaging'), 'description' => t('Administration of messages and sending methods'), ); $items[] = array( 'title' => t('Messages'), 'path' => 'admin/settings/messaging/manage', 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items[] = array( 'title' => t('Messages'), 'path' => 'admin/settings/messaging/edit', 'callback' => 'messaging_admin_message_edit', 'type' => MENU_CALLBACK, ); $items[] = array( 'title' => t('Settings'), 'path' => 'admin/settings/messaging/settings', 'callback' => 'drupal_get_form', 'callback arguments' => array('messaging_admin_settings'), '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 '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' => 'radios', '#title' => t('Default messaging'), '#default_value' => messaging_method_default($user), '#options' => $list, '#description' => t('Default sending method for getting messages from this system.'), ); return $form; } break; } } /** * 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(); } } function messaging_admin_page_overview() { $output = ''; // List sending methods $rows = array(); messaging_method_list(); foreach (messaging_method_info() as $method => $info) { $rows[] = array( ''.$info['name'].'', !empty($info['description']) ? $info['description'] : '' ); } $output .= theme('box', t('Sending methods'), theme('table', NULL, $rows)); // List message groups $groups = module_invoke_all('messaging', 'message groups'); foreach ($groups as $group => $group_info) { $list[] = l($group_info['name'], 'admin/settings/messaging/edit/'.$group); } $output .= theme('box', t('Message groups'), theme('item_list', $list)); 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) { $form['messages'][$key][$method] = array( '#title' => $methodname, '#type' => 'textarea', '#default_value' => messaging_message_part($group, $key, $method, FALSE), ); } } // 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) { if ($text = trim($text)) { // Save db_query("DELETE FROM {messaging_message_parts} WHERE type = '%s' AND msgkey = '%s' AND method = '%s'", $group, $key, $method); 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() { $form['general'] = array( '#type' => 'fieldset', '#title' => t('General settings'), ); $form['general']['messaging_default_method'] = array( '#title' => t('Default send method'), '#type' => 'radios', '#options' => messaging_method_list(), '#default_value' => variable_get('messaging_default_method', ''), ); // 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' => $options['filter'], '#options' => $format_options, ); } } else { $form['messaging_methods']['warning'] = array('#value' => t('You should enable some messaging method plug-ins for this to work.')); } 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 $message * Array of message parts that will be compiled depending on send method. * Mandatory message parts, which may have nexted parts are: * - '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) { // Get default sending method, or default for this user account $method = $method ? $method : messaging_method_default($account); // Check for debug option enabled 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); } /** * Send message to array of destinations. The message is rendered just once. * * @param $destination * Array of destinations for sending * @param $message * Message array, not rendered */ function messaging_message_send($destination, $message, $method = NULL) { // 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); } // Check for debug option enabled if (variable_get('messaging_debug', 0) && function_exists('messaging_debug_send')) { return messaging_debug_send($destination, $message); } elseif (($function = messaging_method_info($method, 'send')) && function_exists($function)) { messaging_log("Message for user: $account->uid, method: $method, subject: ".$message['subject']); return $function($destination, $message); } else { watchdog('messaging', t('Message could not be delivered for method %method', array('%method' => $method)), WATCHDOG_ERROR); return FALSE; } } /** * Pull pending messages for given methods and user accounts * * Each returned message will be an array with the following elements * - 'to', destination uid * - 'from', 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) { // Collect messages checking limit after each module $messages = array(); foreach (module_implements('messaging') as $module) { if ($return = module_invoke($module, 'messaging', 'pull', $method, $users, $limit, $delete)) { $messages = array_merge($messages, $return); // Check limit after each fetching if ($limit) { if (count($return) >= $limit) { break; } else { $limit -= count($return); } } } } 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 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) { if ($access = messaging_method_info($method, 'access')) { return user_access($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 : ''; } /** * Replaces variables in text * * Obsoleted. */ function messaging_text_vars($text, $variables) { // Transform arguments before inserting them foreach ($variables as $key => $value) { switch ($key[0]) { // Escaped only case '@': $args[$key] = check_plain($value); break; // Escaped and placeholder case '%': default: $args[$key] = theme('placeholder', $value); break; // Pass-through case '!': } } foreach ($text as $key => $line) { $text[$key] = strtr($line, $args); } return $text; } /** * 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) { static $info, $properties; if (!$info) { $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 */ function messaging_message_render($message, $info) { // Apply footer prefix if provided if (!empty($info['footer']) && 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']); 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; } /** * Filters message parts before final formatting */ function messaging_message_filter($text, $format = FILTER_FORMAT_DEFAULT) { if (is_array($text)) { foreach (element_children($text) as $key) { $text[$key] = messaging_message_filter($text[$key], $format); } // Don't forget #text element if (!empty($text['#text'])) { $text['#text'] = messaging_message_filter($text['#text'], $format); } } else { $text = check_markup($text, $format, FALSE); } return $text; } /** * 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.'); } case 'process': switch ($delta) { case 0: return messaging_html_to_text($text); case 1: return messaging_check_plain($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('<', '>'), 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; } /** * Log facility for debugging */ function messaging_log($txt = NULL) { static $logs; if ($txt) { $logs[] = $txt; } else { return $logs; } }