From 3ba3e8168ae1d9003d2caf873a649f81b477f0d5 Mon Sep 17 00:00:00 2001
From: Bob Vincent <bobvin@pillars.net>
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 <bobvin@pillars.net>
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

