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 $methods = messaging_method_list(); $output .= theme('box', t('Sending methods'), theme('item_list', $methods)); // 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)) { $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('item_list', $tokens), ); } $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] = "[$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, 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 $info = messaging_method_info(); $form['messaging_methods'] = array('#tree' => TRUE); foreach (filter_formats() as $format) { $format_options[$format->format] = $format->name; } foreach ($info as $method => $options) { // This will preserve settings for disabled modules if (empty($options['name'])) { $form['messaging_methods'][$method]['filter'] = array('#type' => 'value', '#value' => $options['filter']); } else { $form['messaging_methods'][$method] = array( '#type' => 'fieldset', '#title' => t('!name settings', array('!name' => $options['name'])), '#collapsible' => TRUE, ); // Output filter $form['messaging_methods'][$method]['filter'] = array( '#type' => 'radios', '#title' => t('Message filter'), '#default_value' => $options['filter'], '#options' => $format_options, ); } } 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) { $method = $method ? $method : messaging_method_default($account); // Renders subject and body applying filters in the process $info = messaging_method_info($method); if (!empty($info['render'])) { $message = call_user_func($info['render'], $message, $info); } else { $message['subject'] = messaging_message_render($message['subject'], $info); $message['body'] = messaging_message_render($message['body'], $info); } // Check for debug option enabled if (variable_get('messaging_debug', 0) && function_exists('messaging_debug_send')) { return messaging_debug_send($account, $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($account, $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; } } /** Plug-in management **/ /** * 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 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 **/ /** * Composes message from different parts, recursively * * @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 $info * Messaging method info. It passes information about glue characters, filters to be used, etc... */ function messaging_message_render($text, $info) { $output = ''; $glue = !empty($info['glue']) ? $info['glue'] : ''; if (!empty($info['filter'])) { $text = messaging_message_filter($text, $info['filter']); // Unset filter so it's not applied again unset($info['filter']); } 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) { $text[$key] = messaging_message_render($text[$key], $info); } $output .= implode($glue, $text); } else { $output .= $text; } 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 */ function messaging_filter($op, $delta = 0, $format = -1, $text = '') { switch ($op) { case 'list': return array(0 => t('Plain text filter')); case 'no cache': return TRUE; // No caching at all case 'description': switch ($delta) { case 0: return t('Filters out all html tags and replaces html entities by characters.'); } case 'process': switch ($delta) { case 0: $text = filter_xss($text, array()); $text = htmlspecialchars_decode($text, ENT_QUOTES); return $text; default: return $text; } case 'settings': return; default: return $text; } } /** * Log facility for debugging */ function messaging_log($txt = NULL) { static $logs; if ($txt) { $logs[] = $txt; } else { return $logs; } }