Index: includes/mail.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/mail.inc,v retrieving revision 1.25 diff -u -p -r1.25 mail.inc --- includes/mail.inc 1 Sep 2009 17:40:27 -0000 1.25 +++ includes/mail.inc 15 Oct 2009 05:58:51 -0000 @@ -14,7 +14,7 @@ * appropriate places in the template. Processed e-mail templates are * requested from hook_mail() from the module sending the e-mail. Any module * can modify the composed e-mail message array using hook_mail_alter(). - * Finally drupal_mail_sending_system()->mail() sends the e-mail, which can + * Finally drupal_mail_system()->mail() sends the e-mail, which can * be reused if the exact same composed e-mail is to be sent to multiple * recipients. * @@ -78,8 +78,8 @@ * @param $from * Sets From to this value, if given. * @param $send - * Send the message directly, without calling - * drupal_mail_sending_system()->mail() manually. + * Send the message directly, without calling drupal_mail_system()->mail() + * manually. * @return * The $message array structure containing all details of the * message. If already sent ($send = TRUE), then the 'result' element @@ -93,6 +93,8 @@ function drupal_mail($module, $key, $to, // Bundle up the variables into a structured array for altering. $message = array( 'id' => $module . '_' . $key, + 'module' => $module, + 'key' => $key, 'to' => $to, 'from' => isset($from) ? $from : $default_from, 'language' => $language, @@ -129,12 +131,15 @@ function drupal_mail($module, $key, $to, // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail. drupal_alter('mail', $message); - // Concatenate and wrap the e-mail body. - $message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']); + // Retrieve the responsible implementation for this message. + $system = drupal_mail_system($module, $key); + + // Format the message body. + $message = $system->format($message); // Optionally send e-mail. if ($send) { - $message['result'] = drupal_mail_sending_system($module, $key)->mail($message); + $message['result'] = $system->mail($message); // Log errors if (!$message['result']) { @@ -149,11 +154,23 @@ function drupal_mail($module, $key, $to, /** * Returns an object that implements the MailSystemInterface. * - * Allows for one or more custom mail backends to send mail messages + * Allows for one or more custom mail backends to format and send mail messages * composed using drupal_mail(). * + * An implementation needs to implement the following methods: + * - format: Allows to preprocess, format, and postprocess a mail + * message before it is passed to the sending system. By default, all messages + * may contain HTML and are converted to plain-text by the DefaultMailSystem + * implementation. For example, an alternative implementation could override + * the default implementation and additionally sanitize the HTML for usage in + * a MIME-encoded e-mail, but still invoking the DefaultMailSystem + * implementation to generate an alternate plain-text version for sending. + * - mail: Sends a message through a custom mail sending engine. + * By default, all messages are sent via PHP's mail() function by the + * DefaultMailSystem implementation. + * * The selection of a particular implementation is controlled via the variable - * 'mail_sending_system', which is a keyed array. The default implementation + * 'mail_system', which is a keyed array. The default implementation * is the class whose name is the value of 'default-system' key. A more specific * match first to key and then to module will be used in preference to the * default. To specificy a different class for all mail sent by one module, set @@ -195,11 +212,12 @@ function drupal_mail($module, $key, $to, * A key to identify the e-mail sent. The final e-mail ID for the e-mail * alter hook in drupal_mail() would have been {$module}_{$key}. */ -function drupal_mail_sending_system($module, $key) { +function drupal_mail_system($module, $key) { $instances = &drupal_static(__FUNCTION__, array()); $id = $module . '_' . $key; - $configuration = variable_get('mail_sending_system', array('default-system' => 'DefaultMailSystem')); + + $configuration = variable_get('mail_system', array('default-system' => 'DefaultMailSystem')); // Look for overrides for the default class, starting from the most specific // id, and falling back to the module name. @@ -230,7 +248,18 @@ function drupal_mail_sending_system($mod */ interface MailSystemInterface { /** - * Send an e-mail message composed by drupal_mail(). + * Format a message composed by drupal_mail() prior sending. + * + * @param $message + * A message array, as described in hook_mail_alter(). + * + * @return + * The formatted $message. + */ + public function format(array $message); + + /** + * Send a message composed by drupal_mail(). * * @param $message * Message array with at least the following elements: @@ -452,9 +481,10 @@ function drupal_html_to_text($string, $a } // Process blocks of text. else { - // Convert inline HTML text to plain text. - $value = trim(preg_replace('/\s+/', ' ', decode_entities($value))); - if (strlen($value)) { + // Convert inline HTML text to plain text; not removing line-breaks or + // white-space, since that breaks newlines when sanitizing plain-text. + $value = trim(decode_entities($value)); + if (drupal_strlen($value)) { $chunk = $value; } } @@ -466,7 +496,7 @@ function drupal_html_to_text($string, $a $chunk = $casing($chunk); } // Format it and apply the current indentation. - $output .= drupal_wrap_mail($chunk, implode('', $indent)) . "\n"; + $output .= drupal_wrap_mail($chunk, implode('', $indent)); // Remove non-quotation markers from indentation. $indent = array_map('_drupal_html_to_text_clean', $indent); } Index: modules/contact/contact.test =================================================================== RCS file: /cvs/drupal/drupal/modules/contact/contact.test,v retrieving revision 1.37 diff -u -p -r1.37 contact.test --- modules/contact/contact.test 11 Oct 2009 18:34:10 -0000 1.37 +++ modules/contact/contact.test 15 Oct 2009 20:47:07 -0000 @@ -174,7 +174,7 @@ class ContactSitewideTestCase extends Dr // We are testing the auto-reply, so there should be one e-mail going to the sender. $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'foo@example.com')); $this->assertEqual(count($captured_emails), 1, t('Auto-reply e-mail was sent to the sender for category "foo".'), t('Contact')); - $this->assertEqual($captured_emails[0]['body'], $foo_autoreply, t('Auto-reply e-mail body is correct for category "foo".'), t('Contact')); + $this->assertEqual($captured_emails[0]['body'], drupal_html_to_text($foo_autoreply), t('Auto-reply e-mail body is correct for category "foo".'), t('Contact')); // Test the auto-reply for category 'bar'. $email = $this->randomName(32) . '@example.com'; @@ -183,7 +183,7 @@ class ContactSitewideTestCase extends Dr // Auto-reply for category 'bar' should result in one auto-reply e-mail to the sender. $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'bar@example.com')); $this->assertEqual(count($captured_emails), 1, t('Auto-reply e-mail was sent to the sender for category "bar".'), t('Contact')); - $this->assertEqual($captured_emails[0]['body'], $bar_autoreply, t('Auto-reply e-mail body is correct for category "bar".'), t('Contact')); + $this->assertEqual($captured_emails[0]['body'], drupal_html_to_text($bar_autoreply), t('Auto-reply e-mail body is correct for category "bar".'), t('Contact')); // Verify that no auto-reply is sent when the auto-reply field is left blank. $email = $this->randomName(32) . '@example.com'; Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.160 diff -u -p -r1.160 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 13 Oct 2009 07:14:26 -0000 1.160 +++ modules/simpletest/drupal_web_test_case.php 15 Oct 2009 04:05:07 -0000 @@ -1106,7 +1106,7 @@ class DrupalWebTestCase extends DrupalTe $language = language_default(); // Use the test mail class instead of the default mail handler class. - variable_set('mail_sending_system', array('default-system' => 'TestingMailSystem')); + variable_set('mail_system', array('default-system' => 'TestingMailSystem')); // Use temporary files directory with the same prefix as the database. $public_files_directory = $this->originalFileDirectory . '/' . $db_prefix; Index: modules/simpletest/simpletest.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.test,v retrieving revision 1.35 diff -u -p -r1.35 simpletest.test --- modules/simpletest/simpletest.test 9 Oct 2009 07:48:06 -0000 1.35 +++ modules/simpletest/simpletest.test 15 Oct 2009 04:05:31 -0000 @@ -339,7 +339,7 @@ class SimpleTestMailCaptureTestCase exte $this->assertEqual(count($captured_emails), 0, t('The captured e-mails queue is empty.'), t('E-mail')); // Send the e-mail. - $response = drupal_mail_sending_system('simpletest', 'drupal_mail_test')->mail($message); + $response = drupal_mail_system('simpletest', 'drupal_mail_test')->mail($message); // Ensure that there is one e-mail in the captured e-mails array. $captured_emails = $this->drupalGetMails(); @@ -360,7 +360,7 @@ class SimpleTestMailCaptureTestCase exte 'to' => $this->randomName(32) . '@example.com', 'body' => $this->randomString(512), ); - drupal_mail_sending_system('drupal_mail_test', $index)->mail($message); + drupal_mail_system('drupal_mail_test', $index)->mail($message); } // There should now be 6 e-mails captured. @@ -377,7 +377,7 @@ class SimpleTestMailCaptureTestCase exte // Send the last e-mail again, so we can confirm that the drupalGetMails-filter // correctly returns all e-mails with a given property/value. - drupal_mail_sending_system('drupal_mail_test', $index)->mail($message); + drupal_mail_system('drupal_mail_test', $index)->mail($message); $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test_4')); $this->assertEqual(count($captured_emails), 2, t('All e-mails with the same id are returned when filtering by id.'), t('E-mail')); } Index: modules/simpletest/tests/mail.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/mail.test,v retrieving revision 1.1 diff -u -p -r1.1 mail.test --- modules/simpletest/tests/mail.test 31 Aug 2009 18:30:26 -0000 1.1 +++ modules/simpletest/tests/mail.test 15 Oct 2009 04:05:54 -0000 @@ -25,7 +25,7 @@ class MailTestCase extends DrupalWebTest parent::setUp(); // Set MailTestCase (i.e. this class) as the SMTP library - variable_set('mail_sending_system', array('default-system' => 'MailTestCase')); + variable_set('mail_system', array('default-system' => 'MailTestCase')); } /** @@ -42,6 +42,21 @@ class MailTestCase extends DrupalWebTest } /** + * Concatenate and wrap the e-mail body for plain-text mails. + * + * @see DefaultMailSystem + */ + public function format(array $message) { + // Join the body array into one string. + $message['body'] = implode("\n\n", $message['body']); + // Convert any HTML to plain-text. + $message['body'] = drupal_html_to_text($message['body']); + // Wrap the mail body for sending. + $message['body'] = drupal_wrap_mail($message['body']); + return $message; + } + + /** * Send function that is called through the mail system. */ public function mail(array $message) { Index: modules/system/mail.sending.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/mail.sending.inc,v retrieving revision 1.1 diff -u -p -r1.1 mail.sending.inc --- modules/system/mail.sending.inc 31 Aug 2009 18:30:27 -0000 1.1 +++ modules/system/mail.sending.inc 15 Oct 2009 03:51:52 -0000 @@ -3,21 +3,40 @@ /** * @file - * Drupal core implementations of the DrupalMailSendingInterface. + * Drupal core implementations of MailSystemInterface. */ /** - * The default Drupal mail sending library using PHP's mail function. + * The default Drupal mail backend using PHP's mail function. */ class DefaultMailSystem implements MailSystemInterface { /** + * Concatenate and wrap the e-mail body for plain-text mails. + * + * @param $message + * A message array, as described in hook_mail_alter(). + * + * @return + * The formatted $message. + */ + public function format(array $message) { + // Join the body array into one string. + $message['body'] = implode("\n\n", $message['body']); + // Convert any HTML to plain-text. + $message['body'] = drupal_html_to_text($message['body']); + // Wrap the mail body for sending. + $message['body'] = drupal_wrap_mail($message['body']); + return $message; + } + + /** * Send an e-mail message, using Drupal variables and default settings. - * @see http://php.net/manual/en/function.mail.php the PHP function reference - * for mail(). - * @see drupal_mail() for information on how $message is composed. + * + * @see http://php.net/manual/en/function.mail.php + * @see drupal_mail() * * @param $message - * Message array as described by DrupalMailSendingInterface. + * A message array, as described in hook_mail_alter(). * @return * TRUE if the mail was successfully accepted, otherwise FALSE. */ @@ -44,8 +63,7 @@ class DefaultMailSystem implements MailS * * This class is for running tests or for development. */ -class TestingMailSystem implements MailSystemInterface { - +class TestingMailSystem extends DefaultMailSystem implements MailSystemInterface { /** * Accept an e-mail message and store it in a variable. * Index: modules/system/system.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v retrieving revision 1.90 diff -u -p -r1.90 system.api.php --- modules/system/system.api.php 15 Oct 2009 17:55:55 -0000 1.90 +++ modules/system/system.api.php 15 Oct 2009 20:25:42 -0000 @@ -700,7 +700,7 @@ function hook_image_toolkits() { * * Email messages sent using functions other than drupal_mail() will not * invoke hook_mail_alter(). For example, a contributed module directly - * calling the drupal_mail_sending_system()->mail() or PHP mail() function + * calling the drupal_mail_system()->mail() or PHP mail() function * will not invoke this hook. All core modules use drupal_mail() for * messaging, it is best practice but not manditory in contributed modules. * Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.815 diff -u -p -r1.815 system.module --- modules/system/system.module 15 Oct 2009 17:55:55 -0000 1.815 +++ modules/system/system.module 15 Oct 2009 20:25:43 -0000 @@ -2698,7 +2698,7 @@ function system_mail($key, &$message, $p $body = token_replace($context['message'], $context); $message['subject'] .= str_replace(array("\r", "\n"), '', $subject); - $message['body'][] = drupal_html_to_text($body); + $message['body'][] = $body; } function system_message_action_form($context) { Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.1064 diff -u -p -r1.1064 user.module --- modules/user/user.module 15 Oct 2009 11:47:25 -0000 1.1064 +++ modules/user/user.module 15 Oct 2009 20:25:43 -0000 @@ -2910,8 +2910,7 @@ function user_preferred_language($accoun * @param $language * Optional language to use for the notification, overriding account language. * @return - * The return value from drupal_mail_sending_system()->mail(), if ends up - * being called. + * The return value from drupal_mail_system()->mail(), if ends up being called. */ function _user_mail_notify($op, $account, $language = NULL) { // By default, we always notify except for canceled and blocked.