Index: modules/contact/contact.module =================================================================== RCS file: /cvs/drupal/drupal/modules/contact/contact.module,v retrieving revision 1.86 diff -u -r1.86 contact.module --- modules/contact/contact.module 31 May 2007 12:03:18 -0000 1.86 +++ modules/contact/contact.module 2 Jun 2007 16:44:06 -0000 @@ -363,41 +363,40 @@ global $user; $account = user_load(array('uid' => arg(1), 'status' => 1)); - // Compose the body: - $message[] = "$account->name,"; - $message[] = t("!name (!name-url) has sent you a message via your contact form (!form-url) at !site.", array('!name' => $user->name, '!name-url' => url("user/$user->uid", array('absolute' => TRUE)), '!form-url' => url($_GET['q'], array('absolute' => TRUE)), '!site' => variable_get('site_name', 'Drupal'))); - $message[] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE)))); - $message[] = t('Message:'); - $message[] = $form_values['message']; + + // Format the subject + $message['subject'] = text_t('[!site_name] !subject'); + // Compose the body + $message['body'][] = text("!name-to,"); + $message['body'][] = text_t("!name-from (!name-from-url) has sent you a message via your contact form (!form-url) at !site_name."); + $message['body'][] = text_t("If you don't want to receive such e-mails, you can change your settings at !url."); + $message['body'][] = text_t('Message:'); + $message['body'][] = $form_values['message']; + + // Prepare variables for replacement. User mail tokens will be + // added automatically in user_mail, so they're not needed here. + $message['variables'] = array( + '!name-from' => $user->name, + '!name-to' => $account->name, + '!name-from-url' => array('#type' => 'url', 'url' => "user/$user->uid", 'options' => array('absolute' => TRUE)), + '!form-url' => array('#type' => 'url', 'url' => $_GET['q'], 'options' => array('absolute' => TRUE)), + '!url' => array('#type' => 'url', 'url' => "user/$account->uid", 'options' => array('absolute' => TRUE)), + '!subject' => $form_values['subject'], + ); + + // Send the e-mail + $message['#mail_id'] = 'user-contact-mail'; + $message['#from'] = $user->mail; + user_mail($account, $message); - // Tidy up the body: - foreach ($message as $key => $value) { - $message[$key] = wordwrap($value); - } - - // Prepare all fields: - $to = $account->mail; - $from = $user->mail; - - // Format the subject: - $subject = '['. variable_get('site_name', 'Drupal') .'] '. $form_values['subject']; - - // Prepare the body: - $body = implode("\n\n", $message); - - // Send the e-mail: - drupal_mail('contact-user-mail', $to, $subject, $body, $from); - - // Send a copy if requested: + // Send a copy to the user, if requested if ($form_values['copy']) { - drupal_mail('contact-user-copy', $from, $subject, $body, $from); + $message['#mail_id'] = 'user-contact-copy'; + user_mail($user, $message); } - // Log the operation: flood_register_event('contact'); watchdog('mail', '%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name)); - - // Set a status message: drupal_set_message(t('The message has been sent.')); // Jump to the user's profile page: @@ -509,40 +508,54 @@ * Process the site-wide contact page form submission. */ function contact_mail_page_submit($form, &$form_state, $form_values) { - + global $language; + // E-mail address of the sender: as the form field is a text field, // all instances of \r and \n have been automatically stripped from it. $from = $form_values['mail']; - // Compose the body: - $message[] = t("!name sent a message using the contact form at !form.", array('!name' => $form_values['name'], '!form' => url($_GET['q'], array('absolute' => TRUE)))); - $message[] = $form_values['message']; - - // Tidy up the body: - foreach ($message as $key => $value) { - $message[$key] = wordwrap($value); - } - // Load the category information: $contact = db_fetch_object(db_query("SELECT * FROM {contact} WHERE cid = %d", $form_values['cid'])); - // Format the category: - $subject = t('[!category] !subject', array('!category' => $contact->category, '!subject' => $form_values['subject'])); - - // Prepare the body: - $body = implode("\n\n", $message); - - // Send the e-mail to the recipients: - drupal_mail('contact-page-mail', $contact->recipients, $subject, $body, $from); + // Compose the mail message: + $message['subject'] = text_t('[!category] !subject'); + $message['body'][] = text_t("!name sent a message using the contact form at !form."); + $message['body'][] = $form_values['message']; + + // Collect variables for later replacement + $message['variables'] = array( + '!category' => $contact->category, + '!subject' => $form_values['subject'], + '!name' => $form_values['name'], + '!form' => array('#type' => 'url', 'url' => $_GET['q'], 'options' => array('absolute' => TRUE)), + ); + + // Send the e-mail to the recipients using the default site language: + $message['#mail_id'] = 'contact-page-mail'; + $message['#to'] = $contact->recipients; + $message['#from'] = $from; + $message['#langcode'] = language_default('language'); + + drupal_mail($message); - // If the user requests it, send a copy. + // If the user requests it, send a copy using current language. if ($form_values['copy']) { - drupal_mail('contact-page-copy', $from, $subject, $body, $from); + $message['#mail_id'] = 'contact-page-copy'; + $message['#to'] = $from; + $message['#langcode'] = $language->language; + + drupal_mail($message); } // Send an auto-reply if necessary: if ($contact->reply) { - drupal_mail('contact-page-autoreply', $from, $subject, wordwrap($contact->reply), $contact->recipients); + $message['#mail_id'] = 'contact-page-autoreply'; + $message['#to'] = $from; + $message['body'][] = $contact->reply; + $message['#from'] = $contact->recipients; + $message['#langcode'] = $language->language; + + drupal_mail($message); } // Log the operation: Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.650 diff -u -r1.650 common.inc --- includes/common.inc 1 Jun 2007 09:49:11 -0000 1.650 +++ includes/common.inc 2 Jun 2007 16:44:06 -0000 @@ -2276,30 +2276,57 @@ * Send an e-mail message, using Drupal variables and default settings. * More information in the * PHP function reference for mail() - * @param $mailkey + * + * This function automatically renders the 'subject' and 'body' elements + * if present, using text rendering functions. + * + * @see drupal_render_text + * + * @see drupal_render_mail + * + * The mail data is an array that should have the following elements: + * + * #mail_id * A key to identify the mail sent, for altering. - * @param $to + * #to * The mail address or addresses where the message will be send to. The * formatting of this string must comply with RFC 2822. Some examples are: * user@example.com * user@example.com, anotheruser@example.com * User * User , Another User - * @param $subject + * #subject or 'subject' * Subject of the e-mail to be sent. This must not contain any newline * characters, or the mail may not be sent properly. - * @param $body + * If the element 'subject' exists it will be rendered before sending + * the email and '#subject' will be populated from that + * #body or 'body' * Message to be sent. Drupal will format the correct line endings for you. - * @param $from + * If the element 'body' exists it will be rendered before sending the email + * and '#body' will be populated from that + * + * Optional elements + * + * #from * Sets From, Reply-To, Return-Path and Error-To to this value, if given. - * @param $headers + * #headers * Associative array containing the headers to add. This is typically * used to add extra headers (From, Cc, and Bcc). - * When sending mail, the mail must contain a From header. + * When sending mail, the mail must contain a From header. + * #langcode + * Language code to localize some parts of the e-mail + * 'variables' + * Tokens for replacement when rendering the body or the header + * + * @param $message + * An array containing all the mail data. It supports text rendering for parts + * of the e-mail. + * * @return Returns TRUE if the mail was successfully accepted for delivery, * FALSE otherwise. */ -function drupal_mail($mailkey, $to, $subject, $body, $from = NULL, $headers = array()) { +function drupal_mail(&$mail) { + // Add default headers $defaults = array( 'MIME-Version' => '1.0', 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed', @@ -2310,17 +2337,30 @@ // Return-Path headers should have a domain authorized to use the originating // SMTP server. Errors-To is redundant, but shouldn't hurt. $default_from = variable_get('site_mail', ini_get('sendmail_from')); + // Add default properties + $mail += array( + '#from' => $default_from, + '#headers' => array(), + ); + if ($default_from) { - $defaults['From'] = $defaults['Reply-To'] = $defaults['Sender'] = $defaults['Return-Path'] = $defaults['Errors-To'] = $default_from; - } - if ($from) { - $defaults['From'] = $defaults['Reply-To'] = $from; + $defaults['Sender'] = $defaults['Return-Path'] = $defaults['Errors-To'] = $default_from; } - $headers = array_merge($defaults, $headers); + $defaults['From'] = $defaults['Reply-To'] = $mail['#from']; - // Bundle up the variables into a structured array for altering. - $message = array('#mail_id' => $mailkey, '#to' => $to, '#subject' => $subject, '#body' => $body, '#from' => $from, '#headers' => $headers); + $mail['#headers'] += $defaults; + + // Alter before text rendering + drupal_alter('mail_text', $mail); + + // Render different parts of the mail. From here on, whe don't use the by-reference $mail + // variable anymore but the resulting array with just known mail properties. + // However, the last header and body rendered will be in $mail['#header'] and $mail['#body'] + $message = drupal_render_mail($mail); + + // Alter after text rendering drupal_alter('mail', $message); + $mailkey = $message['#mail_id']; $to = $message['#to']; $subject = $message['#subject']; @@ -2599,6 +2639,300 @@ } /** + * Returns text lines ready for later rendering. + * + * This function outputs arrays containing text information + * ready for rendering with drupal_text_render() + * + * @see drupal_text_render() + * + * @param $value + * String containing the text to be localized or replaced and rendered + * @param $replace + * Whether to do text replacement with provided tokens + * @param $localize + * Whether to run the string through localization system + * @param $type + * Optional type of element, will default to 'string' + * @prefix + * Prefix for the string after rendering. Useful to compose html text. + * @sufix + * Sufix for the string after rendering. Useful to compose html text. + */ +function text($value, $replace = TRUE, $localize = FALSE, $type = 'string', $prefix = '', $sufix = '') { + return array( + '#type' => $type, + '#value' => $value, + '#replace' => $replace, + '#localize' => $localize, + '#prefix' => $prefix, + '#sufix' => $sufix + ); +} + +/** + * Shorthand function for translatable text lines + * + * This is only a wrapper for text() function for quick writing + * and for marking the strings to be localized for the locale extractor. + * + * @see text() + * + * @param $value + * String to be localized and then replaced with tokens + */ +function text_t($value) { + return text($value, TRUE, TRUE); +} + +/** + * Render different types of text arrays into strings. + * + * This function should be used for composing complex texts or email texts. Texts are + * composed using arrays, similar to forms, with some parameters, and then rendered using + * this function. It supports localization of single text lines and also + * has some internal caching allowing quick localization of a single text to multiple + * languages. + * + * Example to print the same message in all available languages: + * + * @code + * // Delayed localization with parameter substitution + * + * // There are other types of arrays that may be rendered so we have to state the type. + * // Type 'text' arrays will be rendered as an array of strings glued together at the end. + * $text['#type'] = 'text'; + * + * $text[] = text_t('Hello !username,'); + * + * // This text will be localized and replaced with variables + * $text[] = text_t('A new !content_type has been posted to !site_name:'); + * // Simple string, won't be localized nor replaced with variables + * $text[] = $node->title.'.'; + * // This text will be replaced but not localized + * $text[] = text('!site_slogan'); + * + * // Special formats for the text + * $text['#glue'] = '
'; // Will be used for gluing the lines together + * + * // Variables for replacement + * $args['!site_name'] => variable_get('site_name', 'Drupal'); // Simple text, won't be localized + * $args['!content_type'] => text_t($node->type); // The variable will be localized + * $args['!username'] = $user->name; + * + * // This one is more tricky. A variable whose default will be localized when rendering + * $args['!site_slogan'] => array( + * '#type' => 'variable', + * 'name' => 'site_slogan', + * 'default'=> text_t('Drupal rocks!'), // We can nest arguments and text arrays !! + * ); + * + * // Localize for all languages + * $output = ''; + * foreach (language_list() as $language) { + * $output .= $language->name .':'. drupal_render_text($text, $args, $language->language); + * } + * + * @endcode + * + * @see text() + * + * @see text_t() + * + * @param $text + * Text to be rendered in array form + * @param $langcode + * Optional language code, for language different to current one + * @param $variables + * Arguments ready for replacement + * @param $defaults + * Internal use only + * @param $debug + * Developer facility, for debugging + * @return + * Plain text or array of plain texts, depending on text type. + */ +function drupal_render_text(&$text, $langcode = NULL, $variables = array(), $defaults = array(), $debug = FALSE) { + global $language; + static $text_defaults = array( // Defaults for each type of content, 'all' for all types + 'all' => array('#value' => '', '#replace' => FALSE, '#localize' => FALSE, '#wordwrap' => 0, '#glue' => '', + '#param' => array(), '#rendered' => array(), '#prefix' => '', '#sufix' => '', '#cache' => TRUE + ), + 'variable' => array('#callback' => 'variable_get'), + 'date' => array('#callback' => 'format_date'), + 'interval' => array('#callback' => 'format_interval'), + 'plural' => array('#callback' => 'format_plural'), + 'url' => array('#callback' => 'url', '#language' => 'options'), // Language in array 'options' + 'link' => array('#callback' => 'l', '#language' => 'options'), // Language in array 'options' + ); + static $callback_parameters = array( // Parameters for known callbacks + 'url' => array('url' => NULL, 'options' => array()), + 'l' => array('text' => NULL, 'path' => NULL, 'options' => array()), + 'variable_get' => array('name' => NULL, 'default' => NULL), + 'format_date' => array('timestamp' => NULL, 'type' => 'medium', 'format' => '', 'timezone' => NULL, 'langcode' => array('#type' => 'langcode')), + 'format_interval' => array('timestamp' => NULL, 'granularity' => 2, 'langcode' => array('#type' => 'langcode')), + 'format_plural' => array('count' => NULL, 'singular' => NULL, 'plural' => NULL, 'args' => array(), 'langcode' => array('#type' => 'langcode')), + ); + static $language_list; // We cache the language list too + if (empty($language_list)) { + $language_list = language_list(); + } + $langcode = $langcode ? $langcode : $language->language; + + // Add defaults. If plain text, convert to string array + if (!is_array($text)) { + $text = array('#value' => $text, '#type' => 'string'); + } + // If no type, it is a value. May be a parameter for callbacks + if (empty($text['#type'])) { + $text = array('#type' => 'value', '#value' => $text); + } + + // If already rendered for this language, just return value + if (isset($text['#rendered'][$langcode])) { + return $text['#rendered'][$langcode]; + } + + // Add defaults before processing, in the right order for overriding + $text += $defaults; + $text += $text_defaults['all']; + // We should already have a type property. Set defaults for this type + $type = $text['#type']; + if (isset($text_defaults[$type])) { + $text += $text_defaults[$type]; + } + + if($debug) drupal_set_message(print_r($text, TRUE)); + // So we need some rendering, first we process variables for replacement + + // Get value depending on type + switch ($text['#type']) { + case 'langcode': + // This is a langcode parameter of a callback + $value = $langcode; + break; + case 'text': + // Array of text lines, render each line and merge + foreach (element_children($text) as $index) { + $value[] = drupal_render_text($text[$index], $langcode, $variables, $defaults, $debug); + } + // Now we glue the text together + $value = implode($text['#glue'], $value); + break; + case 'arguments': + // Render variables for replacement + // We pass the full variables array, to the next + foreach (element_children($text) as $name) { + $value[$name] = drupal_render_text($text[$name], $langcode, $variables); + switch ($name[0]) { + case '@': + $value[$name] = check_plain($value[$name]); + break; + case '%': + $value[$name] = theme('placeholder', $value[$name]); + break; + case '!': + // Do nothing + } + // Add to variables to pass over to the next + $variables[$name] = $value[$name]; + } + break; + case 'value': + case 'string': + default: + // Prepare callbacks and parameters + if (isset($text['#callback']) && ($function = $text['#callback']) && function_exists($function)) { + // Prepare parameters + if(isset($text['#parameters'])) { + // It's the first one of this type, should bring information about parameters + // We get it and store it in the static variable + $parameters = $callback_parameters[$function] = $text['#parameters']; + } elseif (isset($callback_parameters[$function])) { + $parameters = $callback_parameters[$function]; + } + // Collect parameters. We render them with drupal_render_text to support complex parameters + foreach ($parameters as $name => $value) { + $params[$name] = isset($text[$name]) ? drupal_render_text($text[$name], $langcode, $variables, $defaults) : drupal_render_text($parameters[$name]); + } + // Replace language for functions needing this parameter + if (isset($text['#language'])) { + $params[$text['#language']]['language'] = $language_list[$langcode]; + } + $value = call_user_func_array($function, $params); + + } else { + $value = $text['#value']; + } + break; + } + + // Process optional parameters. Localize, replace, wordwrap.... + // Make sure we are not screwing with arrays, just text + if (!is_array($value)) { + if ($text['#localize']) { + $value = t($value, array(), $langcode); + } + if ($text['#replace'] && $variables) { + $value = strtr($value, $variables); + } + if ($text['#wordwrap']) { + $value = wordwrap($value, $text['#wordwrap']); + } + $value = $text['#prefix'] . $value . $text['#sufix']; + $output = $value; + } else { + $output = print_r($value, TRUE); + } + // For now, this function supports debugging + + if ($debug) drupal_set_message("Text rendering: type=".$text['#type']."value=".$text['#value']." return= $output"); + + // We cache this text for this language unless specified otherwise + if ($text['#cache']) { + $text['#rendered'][$langcode] = $value; + } + return $value; +} + +/** + * Render a mail array doing text rendering for known parts of the mail + * + * @see drupal_mail() + */ +function drupal_render_mail(&$mail) { + global $language; + + $langcode = isset($mail['#langcode']) ? $mail['#langcode'] : $language->language; + + // This is an e-mail. Has some known parts like + // $mail['body'] and $mail['subject'] which are the texts before rendering + + // First, compute variables for substitution + $variables = isset($mail['variables']) ? drupal_render_text($mail['variables'] += array('#type' => 'arguments'), $langcode, array(), array('#type' => 'value')) : array(); + + // Subject and body mail be properties for rendering or they may be fixed values + // passed in '#subject' and '#body' elements + if (isset($mail['subject'])) { + $mail['subject'] = is_array($mail['subject']) ? $mail['subject'] + array('#type' => 'text') : array('#type' => 'string', '#value' => $mail['subject']); + $mail['#subject'] = drupal_render_text($mail['subject'], $langcode, $variables); + } + + if (isset($mail['body'])) { + $mail['body'] = is_array($mail['body']) ? $mail['body'] + array('#type' => 'text', '#glue' => "\n\n") : array('#type' => 'string', '#value' => $mail['body']); + $mail['#body'] = drupal_render_text($mail['body'], $langcode, $variables, array('#wordwrap' => 75)); + } + + // Bundle up the variables into a structured array for altering removing non-mail ones. + // This will produce at least some notice if any property is missing. + foreach (array('#mail_id', '#to', '#subject', '#body', '#from', '#headers') as $key) { + $message[$key] = $mail[$key]; + } + // The return value is the whole array + return $message; +} + +/** * Function used by uasort in drupal_render() to sort structured arrays * by weight. */ Index: modules/locale/locale.module =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v retrieving revision 1.177 diff -u -r1.177 locale.module --- modules/locale/locale.module 30 May 2007 08:08:58 -0000 1.177 +++ modules/locale/locale.module 2 Jun 2007 16:44:07 -0000 @@ -186,10 +186,9 @@ * Implementation of hook_user(). */ function locale_user($type, $edit, &$user, $category = NULL) { - if ($type == 'form' && $category == 'account' && variable_get('language_count', 1) > 1 && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) == LANGUAGE_NEGOTIATION_PATH) { - $languages = language_list('enabled'); + if (variable_get('language_count', 1) > 1 && ($type == 'register' && user_access('administer users') || $type == 'form' && $category == 'account' )) { $languages = language_list('enabled'); $languages = $languages['1']; - if ($user->language == '') { + if ($user && empty($user->language)) { $user->language = language_default('language'); } $names = array(); @@ -202,9 +201,9 @@ ); $form['locale']['language'] = array('#type' => 'radios', '#title' => t('Language'), - '#default_value' => $user->language, + '#default_value' => user_language($user), '#options' => $names, - '#description' => t('Selecting a different locale will change the interface language of the site.'), + '#description' => t('Selecting a locale will set the default site interface and mail language for this account.'), ); return $form; } Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.788 diff -u -r1.788 user.module --- modules/user/user.module 30 May 2007 08:08:59 -0000 1.788 +++ modules/user/user.module 2 Jun 2007 16:44:11 -0000 @@ -1205,6 +1205,67 @@ } /** + * Send an e-mail message to a user account or a destination mail address. + * + * This is basically a wrapper for drupal_mail(), but some predefined variables + * are added and when the destination mail address belongs to a user, some properties + * are pulled from the user account. + * + * @see drupal_mail() + * + * @param $destination + * User account or e-mail address to which the e-mail will be sent. + * @param $message + * Message array. + * @return + * Returns TRUE if the mail was successfully accepted for + * delivery, FALSE otherwise. + */ +function user_mail($destination, &$message) { + + // Now we get the destination mail or other properties + if ($account = is_object($destination) ? $destination : user_load(array('mail' => $destination))) { + // Destination was a user account or is resolved to a user account. + $mail = $account->mail; + $langcode = user_language($account); + $variables = user_mail_tokens($account); + } + else { + // Destination is an e-mail address and it doesn't belong to a known user. + $mail = $destination; + $variables = array('!mailto' => $mail) + user_mail_tokens(); + } + + // Add parameters to $message array. These may need to be overridden from one email + // to the next when sending an e-mail to different users. + // We add some extra properties that may be useful for mail_text_alter() + $message = array_merge($message, array( + '#to' => $mail, + '#langcode' => $account ? user_language($account) : language_default('language'), + '#account' => $account, + 'variables' => isset($message['variables']) ? $variables + $message['variables'] : $variables, + )); + + return drupal_mail($message); +} + +/** + * Returns the code of the language preferred by the + * user if set or the default language code. + * + * @param $account + * Optional user account. Falls back to the current $user. + */ +function user_language($account = NULL) { + global $user; + + $account = isset($account) ? $account : $user; + + // The user language will be the site default if no language is set for the account. + return (isset($account->language) && $account->language) ? $account->language : language_default('language'); +} + +/** * Menu callback; process one time login link and redirects to the user page on success. */ function user_pass_reset($uid, $timestamp, $hashed_pass, $action = NULL) { @@ -1386,7 +1447,7 @@ } else if (!variable_get('user_email_verification', TRUE) && $account->status && !$admin) { // No e-mail verification is required, create new user account, and login user immediately. - _user_mail_notify('register_no_approval_required', $account, $pass); + _user_mail_notify('register_no_approval_required', $account, array('!password' => $pass)); user_authenticate($account->name, trim($pass)); $form_state['redirect'] = ''; return; @@ -1394,7 +1455,7 @@ else if ($account->status || $notify) { // Create new user account, no administrator approval required. $op = $notify ? 'register_admin_created' : 'register_no_approval_required'; - _user_mail_notify($op, $account, $pass); + _user_mail_notify($op, $account, array('!password' => $pass)); if ($notify) { drupal_set_message(t('Password and further instructions have been e-mailed to the new user %user.', array('%user' => $name))); } @@ -1406,9 +1467,8 @@ } else { // Create new user account, administrator approval required. - _user_mail_notify('register_pending_approval', $account, $pass); + _user_mail_notify('register_pending_approval', $account, array('!password' => $pass)); drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.
In the meantime, your password and further instructions have been sent to your e-mail address.')); - } } } @@ -1658,45 +1718,63 @@ /*** Administrative features ***********************************************/ -function _user_mail_text($messageid, $variables = array()) { - - // Check if an admin setting overrides the default string. - if ($admin_setting = variable_get('user_mail_'. $messageid, FALSE)) { - return strtr($admin_setting, $variables); - } - // No override, return with default strings. - else { - switch ($messageid) { - case 'register_no_approval_required_subject': - return t('Account details for !username at !site', $variables); - case 'register_no_approval_required_body': - return t("!username,\n\nThank you for registering at !site. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n-- !site team", $variables); - case 'register_admin_created_subject': - return t('An administrator created an account for you at !site', $variables); - case 'register_admin_created_body': - return t("!username,\n\nA site administrator at !site has created an account for you. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n-- !site team", $variables); - case 'register_pending_approval_subject': - return t('Account details for !username at !site (pending admin approval)', $variables); - case 'register_pending_approval_body': - return t("!username,\n\nThank you for registering at !site. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details.\n\n\n-- !site team", $variables); - case 'password_reset_subject': - return t('Replacement login information for !username at !site', $variables); - case 'password_reset_body': - return t("!username,\n\nA request to reset the password for your account has been made at !site.\n\nYou may now log in to !uri_brief clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once. It expires after one day and nothing will happen if it's not used.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.", $variables); - case 'status_activated_subject': - return t('Account details for !username at !site (approved)', $variables); - case 'status_activated_body': - return "!username,\n\nYour account at !site has been activated.\n\nYou may now log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\nOnce you have set your own password, you will be able to log in to !login_uri in the future using the following username:\n\nusername: !username\n"; - case 'status_blocked_subject': - return t('Account details for !username at !site (blocked)', $variables); - case 'status_blocked_body': - return "!username,\n\nYour account on !site has been blocked."; - case 'status_deleted_subject': - return t('Account details for !username at !site (deleted)', $variables); - case 'status_deleted_body': - return "!username,\n\nYour account on !site has been deleted."; - } +/** + * Generate text for the built in emails sent in user.module. + * + * @param $messageid + * Message identifier as used in user_mail(). + * @param $variables + * Values to replace placeholders in the text with. + */ +function _user_mail_text($messageid) { + // Get default text. We use text_t() to mark text for the locale extractor and because + // the text will be localized later using drupal_render_text(). + + switch ($messageid) { + case 'register_no_approval_required_subject': + $default = text_t('Account details for !username at !site_name'); + break; + case 'register_no_approval_required_body': + $default = text_t("!username,\n\nThank you for registering at !site_name. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n-- !site_name team"); + break; + case 'register_admin_created_subject': + $default = text_t('An administrator created an account for you at !site_name'); + break; + case 'register_admin_created_body': + $default = text_t("!username,\n\nA site administrator at !site_name has created an account for you. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n-- !site_name team"); + break; + case 'register_pending_approval_subject': + $default = text_t('Account details for !username at !site_name (pending admin approval)'); + break; + case 'register_pending_approval_body': + $default = text_t("!username,\n\nThank you for registering at !site_name. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details.\n\n\n-- !site_name team"); + break; + case 'password_reset_subject': + $default = text_t('Replacement login information for !username at !site_name'); + break; + case 'password_reset_body': + $default = text_t("!username,\n\nA request to reset the password for your account has been made at !site_name.\n\nYou may now log in to !uri_brief clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once. It expires after one day and nothing will happen if it's not used.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password."); + break; + case 'status_activated_subject': + $default = text_t('Account details for !username at !site_name (approved)'); + break; + case 'status_activated_body': + $default = text_t("!username,\n\nYour account at !site_name has been activated.\n\nYou may now log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\nOnce you have set your own password, you will be able to log in to !login_uri in the future using the following username:\n\nusername: !username\n"); + break; + case 'status_blocked_subject': + $default = text_t('Account details for !username at !site_name (blocked)'); + break; + case 'status_blocked_body': + $default = text_t("!username,\n\nYour account on !site_name has been blocked."); + break; + case 'status_deleted_subject': + $default = text_t('Account details for !username at !site_name (deleted)'); + break; + case 'status_deleted_body': + $default = text_t("!username,\n\nYour account on !site_name has been deleted."); + break; } + return array('#type' => 'variable', 'name' => 'user_mail_'. $messageid, 'default' => $default); } function user_admin_check_user() { @@ -2450,7 +2528,7 @@ ); // These email tokens are shared for all settings, so just define // the list once to help ensure they stay in sync. - $email_token_help = t('Available variables are:') .' !username, !site, !password, !uri, !uri_brief, !mailto, !date, !login_uri, !edit_uri, !login_url.'; + $email_token_help = t('Available variables are:') .' !username, !site_name, !password, !uri_full, !uri_brief, !mailto, !date, !login_uri, !edit_uri, !login_url.'; $form['email']['admin_created'] = array( '#type' => 'fieldset', @@ -3074,28 +3152,31 @@ * Return an array of token to value mappings for user e-mail messages. * * @param $account - * The user object of the account being notified. Must contain at - * least the fields 'uid', 'name', and 'mail'. - * @param $password - * Optional string containing the user's current password (if known). + * Optional user object of the account being notified. Must contain + * at least the 'uid', 'name', and 'mail' fields. If not provided, + * the function will only return site-wide mappings. * @return - * Array of mappings from token names to values (for use with strtr()). + * Array of mappings from token names to values. */ -function user_mail_tokens($account, $password = NULL) { +function user_mail_tokens($account = NULL) { global $base_url; + + // Site-wide tokens $tokens = array( - '!username' => $account->name, - '!site' => variable_get('site_name', 'Drupal'), - '!login_url' => user_pass_reset_url($account), - '!uri' => $base_url, + '!site_name' => variable_get('site_name', 'Drupal'), + '!uri_full' => $base_url, '!uri_brief' => substr($base_url, strlen('http://')), - '!mailto' => $account->mail, - '!date' => format_date(time()), - '!login_uri' => url('user', array('absolute' => TRUE)), - '!edit_uri' => url('user/'. $account->uid .'/edit', array('absolute' => TRUE)), + '!date' => array('#type' => 'date', 'timestamp' =>time()), ); - if (!empty($password)) { - $tokens['!password'] = $password; + + // User dependent values + if ($account) { + $tokens += array( + '!username' => $account->name, + '!login_url' => user_pass_reset_url($account), + '!mailto' => $account->mail, + '!edit_uri' => array('#type' => 'url', 'url' => 'user/'. $account->uid .'/edit', 'options' => array('absolute' => TRUE)), + ); } return $tokens; } @@ -3104,50 +3185,57 @@ * Conditionally create and send a notification email when a certain * operation happens on the given user account. * + * @see user_mail() * @see user_mail_tokens() * @see drupal_mail() * * @param $op - * The operation being performed on the account. Possible values: - * 'register_admin_created': Welcome message for user created by the admin - * 'register_no_approval_required': Welcome message when user self-registers - * 'register_pending_approval': Welcome message, user pending admin approval - * 'password_reset': Password recovery request - * 'status_activated': Account activated - * 'status_blocked': Account blocked - * 'status_deleted': Account deleted + * The operation being performed on the account. Possible values: + * 'register_admin_created': Welcome message for user created by the admin + * 'register_no_approval_required': Welcome message when user self-registers + * 'register_pending_approval': Welcome message, user pending admin approval + * 'password_reset': Password recovery request + * 'status_activated': Account activated + * 'status_blocked': Account blocked + * 'status_deleted': Account deleted * * @param $account - * The user object of the account being notified. Must contain at - * least the fields 'uid', 'name', and 'mail'. + * The user object of the account being notified. Must contain at + * least the fields 'uid', 'name', and 'mail'. * - * @param $password - * Optional string containing the user's current password (if known). + * @param $variables + * Optional variables for text replacement * * @return - * The return value from drupal_mail(), if ends up being called. + * The return value from drupal_mail(), if ends up being called. */ -function _user_mail_notify($op, $account, $password = NULL) { +function _user_mail_notify($op, $account, $variables) { $mail_id = 'user-'. strtr($op, '_', '-'); + if ($op == 'register_pending_approval') { // Special case, since we need to distinguish what we send to the // user and what we send to the administrator, handled below. $mail_id .= '-user'; } + // By default, we always notify except for deleted and blocked. $default_notify = ($op != 'status_deleted' && $op != 'status_blocked'); $notify = variable_get('user_mail_'. $op .'_notify', $default_notify); $result = NULL; + if ($notify) { - $from = variable_get('site_mail', ini_get('sendmail_from')); - $variables = user_mail_tokens($account, $password); - $subject = _user_mail_text($op .'_subject', $variables); - $body = _user_mail_text($op .'_body', $variables); - $result = drupal_mail($mail_id, $account->mail, $subject, $body, $from); + $message['#mail_id'] = $mail_id; + $message['subject'] = _user_mail_text($op .'_subject'); + $message['body'][] = _user_mail_text($op .'_body'); + $message['variables'] = $variables; + $result = user_mail($account, $message); if ($op == 'register_pending_approval') { // If a user registered requiring admin approval, notify the admin, too. - drupal_mail('user-register-approval-admin', $from, $subject, t("!username has applied for an account.\n\n!edit_uri", $variables), $from); + $message['#mail_id'] = 'user-register-approval-admin'; + $message['body'][] = text_t("!username has applied for an account.\n\n!edit_uri"); + user_mail(variable_get('site_mail', ini_get('sendmail_from')), $message); } } + return $result; }