Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.641 diff -u -r1.641 common.inc --- includes/common.inc 15 May 2007 20:19:47 -0000 1.641 +++ includes/common.inc 26 May 2007 11:32:58 -0000 @@ -25,6 +25,11 @@ define('SAVED_DELETED', 3); /** + * Language not defined yet + */ +define('UNKNOWN_LANGUAGE', 'UNKNOWN_LANGUAGE'); + +/** * Set content for a specified region. * * @param $region @@ -705,6 +710,9 @@ global $language; static $custom_strings; + if ($langcode === UNKNOWN_LANGUAGE){ + return drupal_callback('create', 't', $string, $args, UNKNOWN_LANGUAGE); + } $langcode = isset($langcode) ? $langcode : $language->language; // First, check for an array of customized strings. If present, use the array @@ -722,12 +730,142 @@ elseif (function_exists('locale') && $langcode != 'en') { $string = locale($string, $langcode); } + // We use $langcode as default parameter for callbacks with t function, if any. + return $args ? drupal_replace($string, $args, $langcode) : $string; +} + +/** + * Text replacement with deferred localization. + * + * Can be used to localize the same base text to multiple languages + * or when the language is not yet known when composing the text. + * + * This function supports callback objects with arguments. These callbacks + * will be executed before text replacement with an aditional $langcode argument. + * + * Security note: Avoid user supplied input into the main text for replacement, + * because it may allow a user to include predefined variables, which may be + * sensitive data, into the resulting text, which may be an e-mail. + * This user supplied input must be in a variable for replacement itself. + * + * @code + * // INCORRECT; someone may post 'email !destination' to + * // expose this variable in the resulting text. + * $text[] = $form_values['message']; // User input + * $args = array('!destination' => 'mail@example.com'); + * drupal_format_text($text, $args); + * + * // CORRECT; when composing and email, HTML encoding the + * // input would be problematic, so !message is correct. + * $text[] = '!message'; + * $args = array('!destination' => 'mail@example.com', '!message' => $form_values['message']); + * drupal_format_text($text, $args); + * @endcode + * + * Example to print the same message in all available languages: + * + * @code + * // Delayed localization with parameter substitution + * $text[] = t'Hello !username,', array('!username' => $user->name), UNKNOWN_LANGUAGE); + * // Delayed localization for later parameter substitution + * $text[] = t('A new !content_type has been posted to !site_name:', UNKNOWN_LANGUAGE); + * // Simple string, won't be localized + * $text[] = $node->title + * + * // Variables for replacement + * $args['!site_name'] => variable_get('site_name', 'Drupal'); // Simple text, won't be localized + * $args['!content_type'] => t($node->type, 0, UNKNOWN_LANGUAGE); // The variable will be localized + * + * // Localize for all languages + * $output = ''; + * foreach (language_list() as $language) { + * $output .= $language->name .':'. drupal_format_text($text, $args, $language->language); + * } + * @endcode + * + * Note that the first parameter may be a single callback object + * + * @param $text + * Plain text, callback, or array of text components. Each text component + * can be plain text or a callback array. + * @param $args + * Optional associative array of replacements to perform after + * localization. See drupal_replace() for more details. + * @param $langcode + * Language code for localization. + * @param $wrap + * Optional, TRUE for wrapping each text line. Can be used + * to prepare text for email. + * @param $glue + * Optional string to use to concatenate the string results of + * text components at the end. + * @return + * Text localized and formatted with variable replacement. + */ +function drupal_format_text($text, $args = array(), $langcode = NULL, $wrap = FALSE, $glue = "\n") { + // We run callback execution first, just in case it is just a callback + $text = drupal_callback('execute', $text, array(UNKNOWN_LANGUAGE => $langcode)); + // If plain text, convert to array for processing + $text = is_array($text) ? $text : array($text); + // Each line can be a plain string or a callback object needing $langcode parameter + foreach ($text as $key => $value) { + // Vale may be a callback so running it through drupal_callback will execute it + // If not a callback, the value will be just returned + $value = drupal_callback('execute', $value, array(UNKNOWN_LANGUAGE => $langcode)); + $text[$key] = drupal_replace($value, $args); + if ($wrap) { + // Wrapping to use to format email text. + $text[$key] = wordwrap($text[$key]); + } + } + return join($glue, $text); +} + +/** + * Performs replacement of placeholders with values in the given string. + * + * The value can be a simple string to use as replacement or a callback + * array. See drupal_callback() for more details. + * + * This function is used by t() to perform replacements. See the + * documentation of t() for more examples on the usage of placeholders. + * + * Examples: + * + * @code + * // Simple string replacement + * $string = drupal_replace('Welcome @username', array('@username' => $user-name)); + * // Replacement with callback array + * $string = drupal_replace('Thank you for posting a !name', array('!name' => array('t', 'story'))); + * @endcode + * + * @param $string + * Text with placeholders to replace. + * @param $args + * An associative array of replacements to perform after localization. + * Incidences of any key in this array are replaced with the corresponding value. + * Based on the first character of the key, the value is escaped and/or themed: + * - !variable: inserted as is + * - @variable: escape plain text to HTML (with check_plain()) + * - %variable: escape text and theme as a placeholder for user-submitted + * content (check_plain() + theme_placeholder()) + * The value can be a simple string for replacement or an array, which indicates + * a callback. See drupal_callback() for more details on callbacks. + * @param $langcode + * Optional language parameter to be used for callbacks + */ +function drupal_replace($string, $args = NULL, $langcode = NULL) { if (!$args) { + // Nothing to replace. return $string; } else { - // Transform arguments before inserting them + // Transform arguments before inserting them. foreach ($args as $key => $value) { + // Some of the values may need to be rendered to plain text + if (!is_string($value)) { + $value = drupal_callback('execute', $value, array(UNKNOWN_LANGUAGE => $langcode)); + } switch ($key[0]) { // Escaped only case '@': @@ -740,9 +878,60 @@ break; // Pass-through case '!': + $args[$key] = $value; } } return strtr($string, $args); + } +} + +/** + * Creates or executes a callback element + * + * A callback is an special Drupal structure that is produced by certain functions + * like t() with some special parameters. It allows to pass arguments that are actually executable functions. + * + * Example: + * + * @code + * // The t() function will produce a callback object when the languae code is UNKNOWN_LANGUAGE. + * $callback = t('Welcome to !site_name', array('!site_name' => variable_get('site_name', 'Drupal')), UNKNOWN_LANGUAGE); + * drupal_callback('execute', $callback, array('UNKNOWN_LANGUAGE', $langcode); + * + * // The result will be the same as + * t('Welcome to !site_name', array('!site_name' => variable_get('site_name', 'Drupal')), $langcode); + * @endcode + * + * @param $op + * Callback operation: + * - 'create' for creating the callback, followed by the function name and arguments + * - 'execute' for executing the callback, followed by the callback structure and arguments + */ +function drupal_callback() { + $args = func_get_args(); + $op = array_shift($args); + switch ($op) { + case 'create': + $callback = array_shift($args); + return array('#callback' => $callback, '#params' => $args); + case 'execute': + $callback = array_shift($args); + if (is_array($callback) && isset($callback['#callback']) && is_callable($callback['#callback'])) { + // The rest of the arguments may be replacement parameters + // Just replace the parameters with the right value + $params = isset($callback['#params']) ? $callback['#params'] : array(); + if ($params && $replace = array_shift($args)) { + foreach ($replace as $key => $value) { + if (($index = array_search($key, $params, TRUE)) !== FALSE ) { + $params[$index] = $value; + } + } + } + return call_user_func_array($callback['#callback'], $params); + } else { + // It was not a callback so just return + return $callback; + } } } @@ -960,27 +1149,30 @@ * @return * A translated string. */ -function format_plural($count, $singular, $plural, $args = array()) { +function format_plural($count, $singular, $plural, $args = array(), $langcode = NULL) { + if ($langcode === UNKNOWN_LANGUAGE) { + return drupal_callback('create', 'format_plural', $count, $singular, $plural, $args, UNKNOWN_LANGUAGE); + } if ($count == 1) { - return t($singular, $args); + return t($singular, $args, $langcode); } $args['@count'] = $count; // get the plural index through the gettext formula - $index = (function_exists('locale_get_plural')) ? locale_get_plural($count) : -1; + $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, $langcode) : -1; if ($index < 0) { // backward compatibility return t($plural, $args); } else { switch ($index) { case "0": - return t($singular, $args); + return t($singular, $args, $langcode); case "1": - return t($plural, $args); + return t($plural, $args, $langcode); default: unset($args['@count']); $args['@count['. $index .']'] = $count; - return t(strtr($plural, array('@count' => '@count['. $index .']')), $args); + return t(strtr($plural, array('@count' => '@count['. $index .']')), $args, $langcode); } } } @@ -1036,10 +1228,16 @@ * The length of the interval in seconds. * @param $granularity * How many different units to display in the string. + * @param $langcode + * Optional language code to translate to a language other than + * what is used to display the page. * @return * A translated string representation of the interval. */ -function format_interval($timestamp, $granularity = 2) { +function format_interval($timestamp, $granularity = 2, $langcode = NULL) { + if ($langcode == UNKNOWN_LANGUAGE) { + return drupal_callback('create', 'format_interval', $timestamp, $granularity, UNKNOWN_LANGUAGE); + } $units = array('1 year|@count years' => 31536000, '1 week|@count weeks' => 604800, '1 day|@count days' => 86400, '1 hour|@count hours' => 3600, '1 min|@count min' => 60, '1 sec|@count sec' => 1); $output = ''; foreach ($units as $key => $value) { @@ -1078,7 +1276,10 @@ * @return * A translated date string in the requested format. */ -function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL) { +function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) { + if ($langcode == UNKNOWN_LANGUAGE) { + return drupal_callback('create', 'format_date', $timestamp, $type, $format, $timezone, UNKNOWN_LANGUAGE); + } if (!isset($timezone)) { global $user; if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) { @@ -1166,7 +1367,10 @@ * When creating links in modules, consider whether l() could be a better * alternative than url(). */ -function url($path = NULL, $options = array()) { +function url($path = NULL, $options = array(), $langcode = NULL) { + if ($langcode === UNKNOWN_LANGUAGE) { + return drupal_callback('create', 'url', $path, $options, UNKNOWN_LANGUAGE); + } // Merge in defaults $options += array( 'fragment' => '', @@ -1177,7 +1381,7 @@ // May need language dependant rewriting if language.inc is present if (function_exists('language_url_rewrite')) { - language_url_rewrite($path, $options); + language_url_rewrite($path, $options, $langcode); } if ($options['fragment']) { $options['fragment'] = '#'. $options['fragment']; Index: includes/language.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/language.inc,v retrieving revision 1.4 diff -u -r1.4 language.inc --- includes/language.inc 18 Apr 2007 21:56:18 -0000 1.4 +++ includes/language.inc 26 May 2007 11:32:58 -0000 @@ -93,14 +93,19 @@ * Rewrite URL's with language based prefix. Parameters are the same * as those of the url() function. */ -function language_url_rewrite(&$path, &$options) { +function language_url_rewrite(&$path, &$options, &$langcode) { global $language; // Only modify relative (insite) URLs. if (!$options['absolute']) { // Language can be passed as an option, or we go for current language. - $path_language = isset($options['language']) ? $options['language'] : $language; + if ($langcode) { + $language_list = language_list(); + $path_language = $language_list[$langcode]; + } else { + $path_language = $language; + } switch (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) { case LANGUAGE_NEGOTIATION_NONE: Index: modules/contact/contact.module =================================================================== RCS file: /cvs/drupal/drupal/modules/contact/contact.module,v retrieving revision 1.84 diff -u -r1.84 contact.module --- modules/contact/contact.module 14 May 2007 13:43:35 -0000 1.84 +++ modules/contact/contact.module 26 May 2007 11:32:59 -0000 @@ -363,41 +363,38 @@ 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 + $subject = t('[!site_name] !subject', 0, UNKNOWN_LANGUAGE); + // Compose the body + $message[] = "!name-to,"; + $message[] = t("!name-from (!name-from-url) has sent you a message via your contact form (!form-url) at !site_name.", 0, UNKNOWN_LANGUAGE); + $message[] = t("If you don't want to receive such e-mails, you can change your settings at !url.", 0, UNKNOWN_LANGUAGE); + $message[] = t('Message:', 0, UNKNOWN_LANGUAGE); + $message[] = '!message'; + + // Prepare variables for replacement. User mail tokens will be + // added automatically in user_mail, so they're not needed here. + $variables = array( + '!name-from' => $user->name, + '!name-to' => $account->name, + '!name-from-url' => url("user/$user->uid", array('absolute' => TRUE), UNKNOWN_LANGUAGE), + '!form-url' => url($_GET['q'], array('absolute' => TRUE), UNKNOWN_LANGUAGE), + '!url' => url("user/$account->uid", array('absolute' => TRUE), UNKNOWN_LANGUAGE), + '!subject' => $form_values['subject'], + '!message' => $form_values['message'] + ); + + // Send the e-mail + user_mail('contact-mail', $account, $subject, $message, $variables, $user->mail); - // 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); + user_mail('contact-copy', $user, $subject, $message, $variables, $user->mail); } - // 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,51 +506,54 @@ * Process the site-wide contact page form submission. */ function contact_mail_page_submit($form_values, $form, &$form_state) { - + global $user; + 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: + // Format the subject $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: + $subject_text = t('[!category] !subject', 0, UNKNOWN_LANGUAGE); + // Compose the body + $message[] = t("!name sent a message using the contact form at !form.", 0, UNKNOWN_LANGUAGE); + $message[] = '!message'; + + // Prepare variables for replacement + $variables = array( + '!name' => $form_values['name'], + '!form' => url($_GET['q'], array('absolute' => TRUE), UNKNOWN_LANGUAGE), + '!message' => $form_values['message'], + '!category' => $contact->category, + '!subject' => $form_values['subject'] + ); + + // Send the e-mail to the recipients using the site default language + $default = language_default(); + $body = drupal_format_text($message, $variables, $default->language, TRUE, "\n\n"); + $subject = drupal_format_text($subject_text, $variables, $default->language); drupal_mail('contact-page-mail', $contact->recipients, $subject, $body, $from); - // If the user requests it, send a copy. + // The subject for the following emails will be in the current page language + $subject = drupal_format_text($subject_text, $variables, $language->language); + + // If the user requests it, send a copy using the current page language if ($form_values['copy']) { + $body = drupal_format_text($message, $variables, $language->language, NULL, TRUE, "\n\n"); drupal_mail('contact-page-copy', $from, $subject, $body, $from); } - // Send an auto-reply if necessary: + // Send an auto-reply if necessary if ($contact->reply) { drupal_mail('contact-page-autoreply', $from, $subject, wordwrap($contact->reply), $contact->recipients); } - // Log the operation: flood_register_event('contact'); watchdog('mail', '%name-from sent an e-mail regarding %category.', array('%name-from' => $form_values['name'] ." [$from]", '%category' => $contact->category)); - - // Update user: drupal_set_message(t('Your message has been sent.')); // Jump to home page rather than back to contact page to avoid contradictory messages if flood control has been activated. $form_state['redirect'] = ''; return; } - Index: modules/locale/locale.module =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v retrieving revision 1.174 diff -u -r1.174 locale.module --- modules/locale/locale.module 22 May 2007 07:42:37 -0000 1.174 +++ modules/locale/locale.module 26 May 2007 11:32:59 -0000 @@ -186,10 +186,11 @@ * 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) { + // Show language field when user is editing own account and administrator is creating account + 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 || $user->language == '') { $default = language_default(); $user->language = $default->language; } @@ -203,9 +204,9 @@ ); $form['locale']['language'] = array('#type' => 'radios', '#title' => t('Language'), - '#default_value' => $user->language, + '#default_value' => $user ? $user->language : $default->language, '#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; } @@ -368,25 +369,28 @@ * * The index is computed from the formula of this language. */ -function locale_get_plural($count) { +function locale_get_plural($count, $langcode = NULL) { global $language; static $locale_formula, $plurals = array(); - if (!isset($plurals[$count])) { - if (!isset($locale_formula)) { - $locale_formula = $language->formula; + $langcode = $langcode ? $langcode : $language->language; + + if (!isset($plurals[$langcode][$count])) { + if (!isset($locale_formula[$langcode])) { + $language_list = language_list(); + $locale_formula[$langcode] = $language_list[$langcode]->formula; } - if ($locale_formula) { + if ($locale_formula[$langcode]) { $n = $count; - $plurals[$count] = @eval("return intval($locale_formula);"); - return $plurals[$count]; + $plurals[$langcode][$count] = @eval("return intval($locale_formula[$langcode]);"); + return $plurals[$langcode][$count]; } else { - $plurals[$count] = -1; + $plurals[$langcode][$count] = -1; return -1; } } - return $plurals[$count]; + return $plurals[$langcode][$count]; } Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.785 diff -u -r1.785 user.module --- modules/user/user.module 22 May 2007 05:52:17 -0000 1.785 +++ modules/user/user.module 26 May 2007 11:33:03 -0000 @@ -1215,6 +1215,76 @@ } /** + * Send an e-mail message to a user account, automatically providing + * subjects and bodies for user e-mails defined in user.module. + * + * @param $key + * A key to identify the mail sent. The mailkey for altering + * will be 'user-'. $key. + * @param $destination + * User account or e-mail address to which the e-mail will be sent. + * @param $subject + * Subject of the e-mail to be passed through localization and + * text replacement before sending. This must not contain any + * newline characters, or the mail may not be sent properly. + * @param $body + * Message to be sent to be passed through localization and + * text replacement before sending. Thus, this should be a + * simple string or an array of text components. Drupal will + * format the correct line endings for you. + * @param $from + * Optional e-mail address to set From, Reply-To, Return-Path + * and Error-To to this value. + * @param $headers + * Optional 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. + * @return + * Returns TRUE if the mail was successfully accepted for + * delivery, FALSE otherwise. + */ +function user_mail($key, $destination, $subject, $body, $variables = array(), $from = NULL, $headers = array()) { + 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); + } + else { + // Destination is an e-mail address and it doesn't belong to a known user. + $mail = $destination; + $default = language_default(); + $langcode = $default->language; + $variables += array('!mailto' => $mail); + } + // Add standard variables. + $variables += user_mail_tokens($account); + $from = $from ? $from : variable_get('site_mail', ini_get('sendmail_from')); + + // Run subject and body through localization and text replacement. + $subject = drupal_format_text($subject, $variables, $langcode); + $body = drupal_format_text($body, $variables, $langcode, TRUE, "\n\n"); + + return drupal_mail('user-'. $key, $mail, $subject, $body, $from, $headers); +} + +/** + * 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; + $default = language_default(); + + // The user language will be the site default if no language is set for the account. + return (isset($account->language) && $account->language) ? $account->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) { @@ -1396,7 +1466,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; @@ -1404,7 +1474,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))); } @@ -1416,9 +1486,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.')); - } } } @@ -1668,43 +1737,51 @@ /*** Administrative features ***********************************************/ -function _user_mail_text($messageid, $variables = array()) { +/** + * 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, $variables = array(), $langcode = UNKNOWN_LANGUAGE) { - // Check if an admin setting overrides the default string. if ($admin_setting = variable_get('user_mail_'. $messageid, FALSE)) { - return strtr($admin_setting, $variables); + // An admin setting overrides the default string. + return drupal_replace($admin_setting, $variables, $langcode); } - // No override, return with default strings. else { + // No override, return array with callback to localize default strings. switch ($messageid) { case 'register_no_approval_required_subject': - return t('Account details for !username at !site', $variables); + return t('Account details for !username at !site_name', $variables, $langcode); 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); + return 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", $variables, $langcode); case 'register_admin_created_subject': - return t('An administrator created an account for you at !site', $variables); + return t('An administrator created an account for you at !site_name', $variables, $langcode); 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); + return 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", $variables, $langcode); case 'register_pending_approval_subject': - return t('Account details for !username at !site (pending admin approval)', $variables); + return t('Account details for !username at !site_name (pending admin approval)', $variables, $langcode); 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); + return 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", $variables, $langcode); case 'password_reset_subject': - return t('Replacement login information for !username at !site', $variables); + return t('Replacement login information for !username at !site_name', $variables, $langcode); 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); + return 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.", $variables, $langcode); case 'status_activated_subject': - return t('Account details for !username at !site (approved)', $variables); + return t('Account details for !username at !site_name (approved)', $variables, $langcode); 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"; + return 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", $variables, $langcode); case 'status_blocked_subject': - return t('Account details for !username at !site (blocked)', $variables); + return t('Account details for !username at !site_name (blocked)', $variables, $langcode); case 'status_blocked_body': - return "!username,\n\nYour account on !site has been blocked."; + return t("!username,\n\nYour account on !site_name has been blocked.", $variables, $langcode); case 'status_deleted_subject': - return t('Account details for !username at !site (deleted)', $variables); + return t('Account details for !username at !site_name (deleted)', $variables, $langcode); case 'status_deleted_body': - return "!username,\n\nYour account on !site has been deleted."; + return t("!username,\n\nYour account on !site_name has been deleted.", $variables, $langcode); } } } @@ -2460,7 +2537,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', @@ -3084,28 +3161,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' => format_date(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' => url('user/'. $account->uid .'/edit', array('absolute' => TRUE)), + ); } return $tokens; } @@ -3114,50 +3194,54 @@ * 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) { - $mail_id = 'user-'. strtr($op, '_', '-'); +function _user_mail_notify($op, $account, $variables) { + $mail_id = 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); + $result = user_mail($mail_id, $account, $subject, $body, $variables); 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); + $body[] = t("!username has applied for an account.\n\n!edit_uri", UNKNOWN_LANGUAGE); + user_mail('register-approval-admin', variable_get('site_mail', ini_get('sendmail_from')), $subject, $body, $variables); } } + return $result; }