From 3ba3e8168ae1d9003d2caf873a649f81b477f0d5 Mon Sep 17 00:00:00 2001 From: Bob Vincent Date: Sat, 21 May 2011 05:22:49 -0400 Subject: [PATCH 1/2] Issue #800434 by bar.hanssens, pillarsdotnet: Allow hook_mail_alter implementations to cancel mail. --- includes/mail.inc | 50 +++++++++++++++++++++++++++------- modules/simpletest/simpletest.module | 13 +++++++++ modules/simpletest/tests/mail.test | 24 ++++++++++++++-- modules/system/system.api.php | 15 +++++++++- 4 files changed, 88 insertions(+), 14 deletions(-) diff --git a/includes/mail.inc b/includes/mail.inc index be2df923427ec363f671132771e9c97ee490c090..60705bfb5f28628d5b5d7929114abf3be17e434c 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -57,6 +57,12 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER * user_mail_tokens($variables, $data, $options); * switch($key) { * case 'notice': + * // If the recipient can receive such notices by instant-message, + * // do not send by email. + * if (example_im_send($key, $message, $params)) { + * $message['send'] = FALSE; + * break; + * } * $langcode = $message['language']->language; * $message['subject'] = t('Notification from !site', $variables, array('langcode' => $langcode)); * $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array('langcode' => $langcode)); @@ -65,6 +71,23 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER * } * @endcode * + * Another example, which uses drupal_mail() to format a message, then sends it + * to multiple recipients. + * + * @code + * function example_notify($accounts) { + * global $language; + * $message = drupal_mail('example', 'notice', '<>', $language, $accounts, 'reply@example.com', FALSE); + * // Make sure that message sending was not canceled. + * if ($message['send']) { + * $system = drupal_mail_system(); + * foreach ($accounts as $account) { + * $message['headers']['To'] = mime_header_encode($account->mail); + * $system->mail($message); + * } + * } + * @endcode + * * @param $module * A module name to invoke hook_mail() on. The {$module}_mail() hook will be * called to complete the $message structure which will already contain common @@ -86,8 +109,10 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER * @param $from * Sets From to this value, if given. * @param $send - * Send the message directly, without calling drupal_mail_system()->mail() - * manually. + * If TRUE, drupal_mail will deliver the message by calling + * drupal_mail_system()->mail() and store the result in $message['result']. + * Modules implementing hook_mail_alter() may cancel sending by setting + * $message['send'] to FALSE. * * @return * The $message array structure containing all details of the @@ -108,6 +133,7 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N 'from' => isset($from) ? $from : $default_from, 'language' => $language, 'params' => $params, + 'send' => $send, 'subject' => '', 'body' => array() ); @@ -148,12 +174,16 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N // Optionally send e-mail. if ($send) { - $message['result'] = $system->mail($message); - - // Log errors - if (!$message['result']) { - watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), LOG_ERR); - drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error'); + if (empty($message['send'])) { + $message['result'] = FALSE; + } + else { + $message['result'] = $system->mail($message); + // Log errors + if (!$message['result']) { + watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), E_ERROR); + drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error'); + } } } @@ -179,7 +209,7 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N * DefaultMailSystem implementation. * * The selection of a particular implementation is controlled via the variable - * 'mail_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 @@ -288,7 +318,7 @@ interface MailSystemInterface { * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for * smart plain text wrapping. * - headers: Associative array containing all additional mail headers not - * defined by one of the other parameters. PHP's mail() looks for Cc + * defined by one of the other parameters. PHP's mail() looks for Cc * and Bcc headers and sends the mail to addresses in these headers too. * * @return diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module index b992fd2a09a80f17a0c93dce57b8ac90cd63b2b5..2306525dde4fa46d37f2d7a2cb05e6d92d8026f1 100644 --- a/modules/simpletest/simpletest.module +++ b/modules/simpletest/simpletest.module @@ -502,3 +502,16 @@ function simpletest_clean_results_table($test_id = NULL) { } return 0; } + +/** + * Implements hook_mail_alter(). + * + * Aborts sending of messages with id 'simpletest_cancel_test'. + * + * @see MailTestCase::testCancelMessage() + */ +function simpletest_mail_alter(&$message) { + if ($message['id'] == 'simpletest_cancel_test') { + $message['send'] = FALSE; + } +} diff --git a/modules/simpletest/tests/mail.test b/modules/simpletest/tests/mail.test index 8a7b152d9d32eee7ae47c9ef8b5fb9c77f4e0cf1..4a65b4bfdf72ab05c4dcc14f3d2081835d0644da 100644 --- a/modules/simpletest/tests/mail.test +++ b/modules/simpletest/tests/mail.test @@ -21,7 +21,7 @@ class MailTestCase extends DrupalWebTestCase implements MailSystemInterface { } function setUp() { - parent::setUp(); + parent::setUp(array('simpletest')); // Set MailTestCase (i.e. this class) as the SMTP library variable_set('mail_system', array('default-system' => 'MailTestCase')); @@ -34,10 +34,28 @@ class MailTestCase extends DrupalWebTestCase implements MailSystemInterface { global $language; // Use MailTestCase for sending a message. - $message = drupal_mail('simpletest', 'mail_test', 'testing@drupal.org', $language); + $message = drupal_mail('simpletest', 'mail_test', 'testing@example.com', $language); // Assert whether the message was sent through the send function. - $this->assertEqual(self::$sent_message['to'], 'testing@drupal.org', t('Pluggable mail system is extendable.')); + $this->assertEqual(self::$sent_message['to'], 'testing@example.com', t('Pluggable mail system is extendable.')); + } + + /** + * Test that message sending may be canceled. + * + * @see simpletest_mail_alter() + */ + function testCancelMessage() { + global $language; + + // Reset the class variable holding a copy of the last sent message. + self::$sent_message = NULL; + + // Send a test message that simpletest_mail_alter should cancel. + $message = drupal_mail('simpletest', 'cancel_test', 'cancel@example.com', $language); + + // Assert that the message was not actually sent. + $this->assertNull(self::$sent_message, 'Message cancellation works:'); } /** diff --git a/modules/system/system.api.php b/modules/system/system.api.php index c2a613e9ea7012efb45f5827d72d3c1af32661ca..e73f4765eb71fccee4fa53e1a37a6d7a9dd42f42 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -1797,7 +1797,7 @@ function hook_image_toolkits() { * invoke hook_mail_alter(). For example, a contributed module directly * 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 mandatory in contributed modules. + * messaging; it is best practice but not mandatory in contributed modules. * * @param $message * An array containing the message data. Keys in this array include: @@ -1826,11 +1826,24 @@ function hook_image_toolkits() { * - 'language': * The language object used to build the message before hook_mail_alter() * is invoked. + * - 'send': + * Set to FALSE to abort sending this email message. * * @see drupal_mail() */ function hook_mail_alter(&$message) { if ($message['id'] == 'modulename_messagekey') { + if ($message['send']) { + // If this message is actually being sent, not just formatted for + // sending, check the recipient. + if (!example_notifications_optin($message['to'], $message['id'])) { + // If the recipient has opted to not receive such messages, cancel + // sending. This kind of check can be done once in a hook_mail_alter() + // implementation rather than patching all possible sending modules. + $message['send'] = FALSE; + return; + } + } $message['body'][] = "--\nMail sent out from " . variable_get('sitename', t('Drupal')); } } -- 1.7.4.1 From 55fa49f4eb3828c1ce6750859f583d7b38bb10ad Mon Sep 17 00:00:00 2001 From: Bob Vincent Date: Sat, 21 May 2011 10:55:27 -0400 Subject: [PATCH 2/2] Issue #131737 by scor, v1nce, AmrMostafa, Pancho, mrfelton, nvanhove, malc0mn, dhthwy: Return-Path overwritten by the PHP mail() function. --- modules/system/system.mail.inc | 40 ++++++++++++++++++++++------------------ 1 files changed, 22 insertions(+), 18 deletions(-) diff --git a/modules/system/system.mail.inc b/modules/system/system.mail.inc index ef50642c55a9db8c36ceb9a976f5f0d78096a3fc..8d439c528c654b07b42dc361db56c420b607ae12 100644 --- a/modules/system/system.mail.inc +++ b/modules/system/system.mail.inc @@ -65,26 +65,30 @@ class DefaultMailSystem implements MailSystemInterface { // For headers, PHP's API suggests that we use CRLF normally, // but some MTAs incorrectly replace LF with CRLF. See #234403. $mail_headers = join("\n", $mimeheaders); + $mail_additional = NULL; if (isset($message['Return-Path']) && !ini_get('safe_mode')) { - $mail_result = mail( - $message['to'], - $mail_subject, - $mail_body, - $mail_headers, - // Pass the Return-Path via sendmail's -f command. - '-f ' . $message['Return-Path'] - ); + if (isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE) { + // On Windows, PHP will use the value of sendmail_from for the + // Return-Path header. + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $message['Return-Path']); + } + else { + // On non-Windows systems, the "-f" option to the sendmail command + // is used to set the Return-Path. + $mail_additional = '-f' . $message['Return-Path']; + } } - else { - // The optional $additional_parameters argument to mail() is not allowed - // if safe_mode is enabled. Passing any value throws a PHP warning and - // makes mail() return FALSE. - $mail_result = mail( - $message['to'], - $mail_subject, - $mail_body, - $mail_headers - ); + $mail_result = @mail( + $message['to'], + $mail_subject, + $mail_body, + $mail_headers, + $mail_additional + ); + if (isset($old_from)) { + // Set sendmail_from back to its previous value. + ini_set('sendmail_from', $old_from); } return $mail_result; } -- 1.7.4.1