From 8ab5a1a303ecf2e1d68a89dc187206b3b30beefe Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros+test@gmail.com>
Date: Sat, 5 Mar 2011 11:57:25 +0100
Subject: [PATCH 1/2] #967164 by te-brian, Berdir: Added initial patch and cleaned up code and UI.

---
 pm_email_notify/pm_email_notify.admin.inc |   46 ++++--
 pm_email_notify/pm_email_notify.install   |   60 +++++++-
 pm_email_notify/pm_email_notify.module    |  257 +++++++++++++++++++++++++----
 privatemsg.api.php                        |    6 +-
 privatemsg.module                         |   60 +++++--
 5 files changed, 364 insertions(+), 65 deletions(-)

diff --git a/pm_email_notify/pm_email_notify.admin.inc b/pm_email_notify/pm_email_notify.admin.inc
index f5de49b..1617098 100644
--- a/pm_email_notify/pm_email_notify.admin.inc
+++ b/pm_email_notify/pm_email_notify.admin.inc
@@ -16,39 +16,53 @@ function pm_email_notify_admin_settings_form() {
     '#collapsible' => FALSE,
     '#collapsed' => FALSE,
   );
-  $form['pm_email']['pm_email_notify_default'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Notify users of new private messages by default'),
-    '#default_value' => variable_get('pm_email_notify_default', TRUE),
+  $form['pm_email']['pm_email_notify_default_level'] = array(
+    '#type' => 'radios',
+    '#title' => t('Default e-mail notification level'),
+    '#options' => array(
+      PM_EMAIL_NOTIFY_LEVEL_DISABLED => t('Never send a notification'),
+      PM_EMAIL_NOTIFY_LEVEL_THREAD => t('Only send a notification when a new discussion thread is created'),
+      PM_EMAIL_NOTIFY_LEVEL_UNREAD_ONCE => t("Only send a notification when the user has not already received a notification since he last viewed a discussion thread."),
+      PM_EMAIL_NOTIFY_LEVEL_ALL => t('Always send a notification'),
+    ),
+    '#default_value' => variable_get('pm_email_notify_default_level', PM_EMAIL_NOTIFY_LEVEL_ALL),
+    '#description' => t('Choose when e-mail notifications will be sent by the system. Users with the appropriate permission may override this setting on the account edit page.'),
     '#weight' => 0,
   );
+  $form['pm_email']['pm_email_notify_only_user'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Only send e-mail notifications to individual users who are addressed directly'),
+    '#default_value' => variable_get('pm_email_notify_only_user', FALSE),
+    '#weight' => 5,
+  );
 
-  $form['pm_email']['pm_email_notify_desc'] = array(
-    '#type' => 'markup',
-    '#value' => t('Customize the email messages sent to users upon receipt of a new private message. <br />Available variables are: !author, !author_uid, !pm_subject, !pm_body, !thread, !site, !login_url, !uri, !uri_brief, !message (URL) and !settings (URL). <br />To translate these messages use the i18nstrings.module and translate them trough the <a href="!translate_url">Translate interface</a> ("Privatemsg Email Notification" text group).', array('!translate_url' => url('admin/build/translate/search'))),
-    '#weight' => 1,
+  $form['pm_email']['pm_email_template'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('E-mail customizations'),
+    '#weight' => 15,
+    '#description' => t('Customize the e-mail notification sent to users. Available variables are: !author, !author_uid, !pm_subject, !pm_body, !thread, !site, !login_url, !uri, !uri_brief, !message (URL) and !settings (URL).</p><p>To translate these messages use the i18nstrings.module and translate them trough the <a href="!translate_url">Translate interface</a> ("Privatemsg Email Notification" text group).</p>', array('!translate_url' => url('admin/build/translate/search'))),
   );
 
-  $form['pm_email']['pm_email_notify_from'] = array(
+  $form['pm_email']['pm_email_template']['pm_email_notify_from'] = array(
     '#type' => 'textfield',
     '#title' => t('From e-mail address for notifications'),
     '#default_value' => variable_get('pm_email_notify_from',''),
-    '#weight' => 2,
+    '#weight' => 10,
     '#description' => t('This is the e-mail address that notifications will come from. Leave blank to use the site default.'),
   );
 
-  $form['pm_email']['pm_email_notify_subject'] = array(
+  $form['pm_email']['pm_email_template']['pm_email_notify_subject'] = array(
     '#type' => 'textfield',
-    '#title' => t('Subject of notification messages'),
+    '#title' => t('Subject'),
     '#default_value' => _pm_email_notify_text('subject', NULL, FALSE),
-    '#weight' => 2,
+    '#weight' => 5,
   );
 
-  $form['pm_email']['pm_email_notify_body'] = array(
+  $form['pm_email']['pm_email_template']['pm_email_notify_body'] = array(
     '#type' => 'textarea',
-    '#title' => t('Body of notification messages'),
+    '#title' => t('Body'),
     '#default_value' => _pm_email_notify_text('body', NULL, FALSE),
-    '#weight' => 3,
+    '#weight' => 10,
   );
   return system_settings_form($form);
 }
\ No newline at end of file
diff --git a/pm_email_notify/pm_email_notify.install b/pm_email_notify/pm_email_notify.install
index 408d843..435bc56 100644
--- a/pm_email_notify/pm_email_notify.install
+++ b/pm_email_notify/pm_email_notify.install
@@ -18,12 +18,19 @@ function pm_email_notify_schema() {
         'not null'      => TRUE,
         'unsigned'      => TRUE,
       ),
-      'email_notify_is_enabled' => array(
-        'description'   => 'Email Notification Status (Enabled=1, Disabled=0)',
+      'email_notify_level' => array(
+        'description'   => 'Email Notification Level',
         'type'          => 'int',
         'not null'      => TRUE,
         'unsigned'      => TRUE,
       ),
+      'email_notify_only_user' => array(
+        'description'   => 'Email Notification \'only user\' setting.',
+        'type'          => 'int',
+        'size'          => 'tiny',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
     ),
     'unique keys' => array(
       'user_id' => array('user_id')
@@ -50,3 +57,52 @@ function pm_email_notify_uninstall() {
   variable_del('pm_email_notify_from');
   drupal_uninstall_schema('pm_email_notify');
 }
+
+/**
+ * Change the 'email_notify_is_enabled' field to 'email_notify_level'.
+ */
+function pm_email_notify_update_6201() {
+  $ret = array();
+
+  // Change the field.
+  $spec = array(
+    'description'   => 'Email Notification Level',
+    'type'          => 'int',
+    'not null'      => TRUE,
+    'unsigned'      => TRUE,
+  );
+  db_change_field($ret, 'pm_email_notify', 'email_notify_is_enabled', 'email_notify_level', $spec);
+
+  // Update existing user settings.
+  db_query("UPDATE {pm_email_notify} SET email_notify_level = %d WHERE email_notify_level = 1", PM_EMAIL_NOTIFY_LEVEL_ALL);
+
+  // Update the site-wide default setting.
+  if (variable_get('pm_email_notify_default', TRUE)) {
+    variable_set('pm_email_notify_default_level', PM_EMAIL_NOTIFY_LEVEL_ALL);
+  }
+  else {
+    variable_set('pm_email_notify_default_level', PM_EMAIL_NOTIFY_LEVEL_DISABLED);
+  }
+  variable_del('pm_email_notify_default');
+
+  return $ret;
+}
+
+/**
+ * Add the 'email_notify_only_user' field.
+ */
+function pm_email_notify_update_6202() {
+  $ret = array();
+  $spec = array(
+    'description'   => 'Email Notification \'only user\' setting.',
+    'type'          => 'int',
+    'size'          => 'tiny',
+    'not null'      => TRUE,
+    'unsigned'      => TRUE,
+  );
+  db_add_field($ret, 'pm_email_notify', 'email_notify_only_user', $spec);
+  return $ret;
+}
+
+
+
diff --git a/pm_email_notify/pm_email_notify.module b/pm_email_notify/pm_email_notify.module
index e3c2e39..bb1cead 100644
--- a/pm_email_notify/pm_email_notify.module
+++ b/pm_email_notify/pm_email_notify.module
@@ -6,6 +6,33 @@
  */
 
 /**
+ * Disable e-mail notifications.
+ */
+define('PM_EMAIL_NOTIFY_LEVEL_DISABLED', 0);
+
+/**
+ * Enable e-mail notifications only for new threads.
+ */
+define('PM_EMAIL_NOTIFY_LEVEL_THREAD', 4);
+
+/**
+ * Enable e-mail notifications only once until a user visits a threads.
+ */
+define('PM_EMAIL_NOTIFY_LEVEL_UNREAD_ONCE', 8);
+
+/**
+ * Enable e-mail notifications for all messages.
+ */
+define('PM_EMAIL_NOTIFY_LEVEL_ALL', 12);
+
+/**
+ * Implements hook_perm().
+ */
+function pm_email_notify_perm() {
+  return array('set privatemsg e-mail notification level', 'change privatemsg e-mail notification for indirect messages');
+}
+
+/**
  * Implements hook_menu().
  */
 function pm_email_notify_menu() {
@@ -23,46 +50,185 @@ function pm_email_notify_menu() {
 }
 
 /**
- * Retrieve notification setting of a user.
+ * Retrieve notification level of a user.
+ *
+ * This function retrieves user's pm notification level from database,
+ * if user preference doesn't exist - it uses default value instead.
+ *
+ * @param $uid
+ *   User ID.
+ *
+ * @return
+ *   Returns a PM_EMAIL_NOTIFY_LEVEL_* constant indicating the notification
+ *   level of the requested user.
+ */
+function _pm_email_notify_user_level($uid) {
+  static $levels = array();
+  // Cache the user's level in case this method is executed in
+  // batched operation.
+  if (!isset($levels[$uid])) {
+    $levels[$uid] = db_result(db_query('SELECT email_notify_level FROM {pm_email_notify} WHERE user_id = %d', $uid));
+    if ($levels[$uid] === FALSE) {
+      // db_result() returns FALSE if result was not found.
+      $levels[$uid] = variable_get('pm_email_notify_default_level', PM_EMAIL_NOTIFY_LEVEL_ALL);
+    }
+  }
+  return $levels[$uid];
+}
+
+/**
+ * Check if user should only be notified if he has been addressed directly.
+ *
+ * @param $uid
+ *   User ID.
+ *
+ * @param
+ *   TRUE if notifications should only be sent for directly addressed
+ *   recipients.
+ */
+function _pm_email_notify_only_user($uid = NULL) {
+  static $cache;
+
+  // The default setting should be honored if it is set.
+  if ($uid) {
+    if (!isset($cache[$uid])) {
+      $cache[$uid] = db_result(db_query("SELECT email_notify_only_user FROM {pm_email_notify} WHERE user_id = %d", $uid));
+      if ($cache[$uid] === FALSE) {
+        $cache[$uid] = variable_get('pm_email_notify_only_user', FALSE);
+      }
+    }
+    $only_user = $cache[$uid];
+  }
+
+  return $only_user;
+}
+
+/**
+ * Retrieve notification setting of a user and check if they should receive
+ * an e-mail notification for a message.
  *
- * This function retrieves user's pm notification preference from database,
- * if user preference doesn't exist - it uses default value instead
+ * Note: This function tries to return as quickly as possible, to avoid extra
+ * processing in batch executions.
  *
  * @param $uid
  *   User uid
+ * @param $message
+ *   Message.
  */
-function _pm_email_notify_is_enabled($uid) {
+function _pm_email_notify_send_check($uid, $message) {
   static $notifications = array();
-  // Cache the result set in case this method is executed in batched operation which will perform many unnecessary repeated selects for the same user
-  if ( !isset($notifications[$uid]) ) {
-    $mail_notification = db_result(db_query('SELECT email_notify_is_enabled FROM {pm_email_notify} WHERE user_id = %d', $uid));
-    if ($mail_notification === FALSE) {
-      // db_result() returns FALSE if result was not found.
-      $mail_notification = variable_get('pm_email_notify_default', TRUE);
+  static $threads = array();
+
+  $mid = $message['mid'];
+  $thread_id = $message['thread_id'];
+  $level = _pm_email_notify_user_level($uid);
+
+  // If the user has notifications disabled, we can skip the rest.
+  if ($level == PM_EMAIL_NOTIFY_LEVEL_DISABLED) {
+    return FALSE;
+  }
+
+  // If the user has all notifications enabled, we can skip the rest.
+  if ($level == PM_EMAIL_NOTIFY_LEVEL_ALL) {
+    return TRUE;
+  }
+
+  // Cache the result set in case this method is executed in batched operation
+  // which will perform many unnecessary repeated processing.
+  if (!isset($notifications[$uid][$mid])) {
+    // Prime the setting to false.
+    $notifications[$uid][$mid] = FALSE;
+
+    if ($level == PM_EMAIL_NOTIFY_LEVEL_THREAD) {
+      // Is this the origin of a thread?
+      $notifications[$uid][$mid] = ($mid == $thread_id);
+    }
+    elseif ($level == PM_EMAIL_NOTIFY_LEVEL_UNREAD_ONCE) {
+      // Is this the origin of a thread?
+      if ($mid == $thread_id) {
+        $notifications[$uid][$mid] = TRUE;
+        return $notifications[$uid][$mid];
+      }
+
+      // Fetch the thread's original message.
+      if (!isset($threads[$thread_id])) {
+        $result = db_query("SELECT * FROM {pm_message} WHERE mid = %d", $thread_id);
+        $threads[$thread_id] = db_fetch_array($result);
+      }
+
+      // Is this user the thread author?
+      if ($uid == $threads[$thread_id]['author']) {
+        $notifications[$uid][$mid] = TRUE;
+        return $notifications[$uid][$mid];
+      }
+
+      // Fetch an array of users who have replied to this thread.
+      if (!isset($threads[$thread_id]['replied'])) {
+        $threads[$thread_id]['replied'] = array();
+        $result = db_query("SELECT pm.author FROM {pm_message} pm INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid WHERE pmi.thread_id = %d", $thread_id);
+        while($recipient = db_fetch_object($result)) {
+          $threads[$thread_id]['replied'][$recipient->author] = $recipient->author;
+        }
+      }
+
+      // Has this user replied to this thread?
+      if (isset($threads[$thread_id]['replied']) && in_array($uid, $threads[$thread_id]['replied'])) {
+        $notifications[$uid][$mid] = TRUE;
+      }
     }
-    $notifications[$uid] = $mail_notification;
   }
-  return $notifications[$uid];
+
+  return $notifications[$uid][$mid];
 }
 
 /**
  * Implements hook_privatemsg_message_insert().
  */
-function pm_email_notify_privatemsg_message_insert($message) {
-  foreach ($message['recipients'] as $recipient) {
-    // check if recipient enabled email notifications
-    if (isset($recipient->uid) && _pm_email_notify_is_enabled($recipient->uid)) {
-      // send them a new pm notification email if they did
-      $params['recipient'] = $recipient;
-      $params['message'] = $message;
-      $from = variable_get('pm_email_notify_from', '');
-      drupal_mail('pm_email_notify', 'notice', $recipient->mail, user_preferred_language($recipient), $params, !empty($from) ? $form : NULL);
+ function pm_email_notify_privatemsg_message_insert($message) {
+   foreach ($message['recipients'] as $recipient) {
+    pm_email_notify_send_mail($recipient, $message);
+  }
+}
+
+/**
+ * Implements hook_privatemsg_message_recipient_changed().
+ *
+ * Notifies users who were added to a message about new Private Messages
+ * via Email.
+ */
+function pm_email_notify_privatemsg_message_recipient_changed($mid, $thread_id, $recipient_id, $type, $added) {
+  $types = array('user');
+
+  // Only send mail if the recipient was added.
+  if ($added) {
+    if ($message = privatemsg_message_load($mid)) {
+      // Check if we should send an email to 'hidden' recipients.
+      if (!_pm_email_notify_only_user($recipient_id)) {
+        $types[] = 'hidden';
+      }
+      if (in_array($type, $types) && _pm_email_notify_send_check($recipient_id) && ($recipient = privatemsg_user_load($recipient_id))) {
+        pm_email_notify_send_mail($recipient, $message);
+      }
     }
   }
 }
 
 /**
- * Implements hook_mail().
+ * Send a pm notification email to a recipient.
+ */
+function pm_email_notify_send_mail($recipient, $message) {
+  // check if recipient enabled email notifications
+  if (isset($recipient->uid) && !empty($recipient->mail) && _pm_email_notify_send_check($recipient->uid, $message)) {
+    // send them a new pm notification email if they did
+    $params['recipient'] = $recipient;
+    $params['message'] = $message;
+    $from = variable_get('pm_email_notify_from', '');
+    drupal_mail('pm_email_notify', 'notice', $recipient->mail, user_preferred_language($recipient), $params, !empty($from) ? $form : NULL);
+  }
+}
+
+/**
+* Implements hook_mail().
  */
 function pm_email_notify_mail($key, &$message, $params) {
   $language = $message['language'];
@@ -148,27 +314,58 @@ function pm_email_notify_user($op, &$edit, &$account, $category = NULL) {
   switch ($op) {
     case 'form':
       if ($category == 'account' && privatemsg_user_access('read privatemsg')) {
-        $form['privatemsg']['pm_send_notifications'] = array(
+        if (privatemsg_user_access('set privatemsg e-mail notification level')) {
+          $form['privatemsg']['pm_email_notify_level'] = array(
+            '#type' => 'radios',
+            '#title' => t('Send me an e-mail notification...'),
+            '#options' => array(
+              PM_EMAIL_NOTIFY_LEVEL_DISABLED => t('Never.'),
+              PM_EMAIL_NOTIFY_LEVEL_THREAD => t('Only for a new conversation'),
+              PM_EMAIL_NOTIFY_LEVEL_UNREAD_ONCE => t("Only once for a conversation until I've read the messages"),
+              PM_EMAIL_NOTIFY_LEVEL_ALL => t('Every time I receive a message'),
+            ),
+            '#default_value' => _pm_email_notify_user_level($account->uid),
+          );
+        }
+        else {
+          // If the user does not have permissions to customize the notification
+          // level, still allow him to disable or enable notifications using the
+          // default level.
+          $form['privatemsg']['pm_email_notify_level'] = array(
+            '#type' => 'checkbox',
+            '#title' => t('Receive email notification for incoming private messages'),
+            '#default_value' => (bool)_pm_email_notify_user_level($account->uid),
+          );
+        }
+        $form['privatemsg']['pm_email_only_user'] = array(
           '#type' => 'checkbox',
-          '#title' => t('Receive email notification for incoming private messages'),
-          '#default_value' => _pm_email_notify_is_enabled($account->uid),
+          '#title' => t("Don't send me e-mail notifications for mass messages."),
+          '#default_value' => _pm_email_notify_only_user($account->uid),
+          '#access' => privatemsg_user_access('change privatemsg e-mail notification for indirect messages'),
         );
       }
       return $form;
 
     case 'submit':
-      if (isset($edit['pm_send_notifications']) && privatemsg_user_access('read privatemsg')) {
-        $pm_email_enabled = $edit['pm_send_notifications'];
-        unset($edit['pm_send_notifications']);
+      if (isset($edit['pm_email_notify_level']) && privatemsg_user_access('read privatemsg')) {
+        $email_notify_level = $edit['pm_email_notify_level'];
+        if (!privatemsg_user_access('set privatemsg e-mail notification level')) {
+          // If the user is not allowed to set the notification level, use the
+          // default if he wants notifications.
+          $email_notify_level *= (int)variable_get('pm_email_notify_default_level', PM_EMAIL_NOTIFY_LEVEL_ALL);
+        }
+        $email_only_user = $edit['pm_email_only_user'];
+        unset($edit['pm_email_notify_level']);
+        unset($edit['pm_email_only_user']);
         // Update database entry with user preference.
         $exists = db_result(db_query("SELECT 1 FROM {pm_email_notify} WHERE user_id = %d", $account->uid));
         if ($exists) {
           // If there is an existing entry, update.
-          db_query("UPDATE {pm_email_notify} SET email_notify_is_enabled = %d WHERE user_id = %d", $pm_email_enabled, $account->uid);
+          db_query("UPDATE {pm_email_notify} SET email_notify_level = %d, email_notify_only_user = %d WHERE user_id = %d", $email_notify_level, $email_notify_only_user, $account->uid);
         }
         else {
           // If not, create a new one.
-          db_query("INSERT INTO {pm_email_notify} (email_notify_is_enabled, user_id) VALUES (%d, %d)", $pm_email_enabled, $account->uid);
+          db_query("INSERT INTO {pm_email_notify} (email_notify_level, email_notify_only_user, user_id) VALUES (%d, %d, %d)", $email_notify_level, $email_notify_only_user, $account->uid);
         }
       }
       break;
diff --git a/privatemsg.api.php b/privatemsg.api.php
index 0922740..8fa36ac 100644
--- a/privatemsg.api.php
+++ b/privatemsg.api.php
@@ -370,14 +370,14 @@ function hook_privatemsg_message_insert($message) {
  *   Id of the message.
  * @param $thread_id
  *   Id of the thread the message belongs to.
- * @param $recipient
+ * @param $recipient_id
  *   Recipient id, a user id if type is user or hidden.
  * @param $type
  *   Type of the recipient.
  * @param $added
  *   TRUE if the recipient is added, FALSE if he is removed.
  */
-function hook_privatemsg_message_recipient_changed($mid, $thread_id, $recipient, $type, $added) {
+function hook_privatemsg_message_recipient_changed($mid, $thread_id, $recipient_id, $type, $added) {
   if ($added && ($type == 'user' || $type == 'hidden')) {
     privatemsg_filter_add_tags(array($thread_id), variable_get('privatemsg_filter_inbox_tag', ''), (object)array('uid' => $recipient));
   }
@@ -669,4 +669,4 @@ function hook_privatemsg_recipient_type_info_alter(&$types) {
 
 /**
  * @}
- */
\ No newline at end of file
+ */
diff --git a/privatemsg.module b/privatemsg.module
index 3b09803..368be8e 100644
--- a/privatemsg.module
+++ b/privatemsg.module
@@ -1868,11 +1868,13 @@ function privatemsg_get_link($recipients, $account = array(), $subject = NULL) {
  * @param $account
  *   For which account the message should be loaded.
  *   Defaults to the current user.
+ * @param $reset
+ *   Reset the static cache.
  *
  * @ingroup api
  */
-function privatemsg_message_load($pmid, $account = NULL) {
-  $messages = privatemsg_message_load_multiple(array($pmid), $account);
+function privatemsg_message_load($pmid, $account = NULL, $reset = FALSE) {
+  $messages = privatemsg_message_load_multiple(array($pmid), $account, $reset);
   return current($messages);
 }
 
@@ -1884,30 +1886,60 @@ function privatemsg_message_load($pmid, $account = NULL) {
  * @param $account
  *   For which account the message should be loaded.
  *   Defaults to the current user.
+ * @param $reset
+ *   Reset the static cache.
  *
  * @ingroup api
  */
-function privatemsg_message_load_multiple($pmids, $account = NULL) {
+function privatemsg_message_load_multiple($pmids, $account = NULL, $reset = FALSE) {
+  static $cache = array();
+  if ($reset) {
+    $cache = array();
+  }
+
   // Avoid SQL error that would happen with an empty pm.mid IN () clause.
   if (empty($pmids)) {
     return array();
   }
 
-  $query = _privatemsg_assemble_query('load', $pmids, $account);
-  $result = db_query($query['query']);
+  // If account is null, we need the current user's uid for caching.
+  if (isset($account->uid)) {
+    $uid = $account->uid;
+  }
+  else {
+    $uid = $GLOBALS['user']->uid;
+  }
+
+  // Collect cached messages and remove any pmids that were found.
   $messages = array();
-  while ($message = db_fetch_array($result)) {
-    // Load author of message.
-    if (!($message['author'] = privatemsg_user_load($message['author']))) {
-      // If user does not exist, load anonymous user.
-      $message['author'] = privatemsg_user_load(0);
+  foreach ($pmids as $key => $pmid) {
+    if (isset($cache[$uid][$pmid])) {
+      $messages[$pmid] = $cache[$uid][$pmid];
+      unset($pmids[$key]);
     }
-    $returned = module_invoke_all('privatemsg_message_load', $message);
-    if (!empty($returned)) {
-      $message = array_merge_recursive($returned, $message);
+  }
+
+  // Load any remaining uncached messages.
+  if (!empty($pmids)) {
+    $query = _privatemsg_assemble_query('load', $pmids, $account);
+    $result = db_query($query['query']);
+    while ($message = db_fetch_array($result)) {
+      // Load author of message.
+      if (!($message['author'] = privatemsg_user_load($message['author']))) {
+        // If user does not exist, load anonymous user.
+        $message['author'] = privatemsg_user_load(0);
+      }
+      $returned = module_invoke_all('privatemsg_message_load', $message);
+      if (!empty($returned)) {
+        $message = array_merge_recursive($returned, $message);
+      }
+
+      // Add to cache and to current result set.
+      $cache[$uid][$message['mid']] = $message;
+      $messages[$message['mid']] = $message;
     }
-    $messages[$message['mid']] = $message;
   }
+
   return $messages;
 }
 
-- 
1.7.4.1


From 86c4765dc380d7a328225ded069963a773859862 Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros+test@gmail.com>
Date: Thu, 10 Mar 2011 22:42:54 +0100
Subject: [PATCH 2/2] Issue #967164 by Berdir: Added tests for all 4 levels and correctly implemented UNREAD_ONCE.

---
 pm_email_notify/pm_email_notify.module   |   59 ++----
 pm_email_notify/pm_email_notify.test     |  300 ++++++++++++++++++++++++++++++
 privatemsg_roles/privatemsg_roles.module |    2 +-
 3 files changed, 318 insertions(+), 43 deletions(-)
 create mode 100644 pm_email_notify/pm_email_notify.test

diff --git a/pm_email_notify/pm_email_notify.module b/pm_email_notify/pm_email_notify.module
index bb1cead..97ac1ac 100644
--- a/pm_email_notify/pm_email_notify.module
+++ b/pm_email_notify/pm_email_notify.module
@@ -77,7 +77,7 @@ function _pm_email_notify_user_level($uid) {
 }
 
 /**
- * Check if user should only be notified if he has been addressed directly.
+ * Check if a user should only be notified when addressed directly.
  *
  * @param $uid
  *   User ID.
@@ -86,19 +86,15 @@ function _pm_email_notify_user_level($uid) {
  *   TRUE if notifications should only be sent for directly addressed
  *   recipients.
  */
-function _pm_email_notify_only_user($uid = NULL) {
+function _pm_email_notify_only_user($uid) {
   static $cache;
-
-  // The default setting should be honored if it is set.
-  if ($uid) {
-    if (!isset($cache[$uid])) {
-      $cache[$uid] = db_result(db_query("SELECT email_notify_only_user FROM {pm_email_notify} WHERE user_id = %d", $uid));
-      if ($cache[$uid] === FALSE) {
-        $cache[$uid] = variable_get('pm_email_notify_only_user', FALSE);
-      }
+  if (!isset($cache[$uid])) {
+    $cache[$uid] = db_result(db_query("SELECT email_notify_only_user FROM {pm_email_notify} WHERE user_id = %d", $uid));
+    if ($cache[$uid] === FALSE) {
+      $cache[$uid] = variable_get('pm_email_notify_only_user', FALSE);
     }
-    $only_user = $cache[$uid];
   }
+  $only_user = $cache[$uid];
 
   return $only_user;
 }
@@ -117,7 +113,6 @@ function _pm_email_notify_only_user($uid = NULL) {
  */
 function _pm_email_notify_send_check($uid, $message) {
   static $notifications = array();
-  static $threads = array();
 
   $mid = $message['mid'];
   $thread_id = $message['thread_id'];
@@ -144,36 +139,16 @@ function _pm_email_notify_send_check($uid, $message) {
       $notifications[$uid][$mid] = ($mid == $thread_id);
     }
     elseif ($level == PM_EMAIL_NOTIFY_LEVEL_UNREAD_ONCE) {
-      // Is this the origin of a thread?
+      // If this is the first message of a thread, always send a notification.
       if ($mid == $thread_id) {
         $notifications[$uid][$mid] = TRUE;
-        return $notifications[$uid][$mid];
-      }
-
-      // Fetch the thread's original message.
-      if (!isset($threads[$thread_id])) {
-        $result = db_query("SELECT * FROM {pm_message} WHERE mid = %d", $thread_id);
-        $threads[$thread_id] = db_fetch_array($result);
-      }
-
-      // Is this user the thread author?
-      if ($uid == $threads[$thread_id]['author']) {
-        $notifications[$uid][$mid] = TRUE;
-        return $notifications[$uid][$mid];
-      }
-
-      // Fetch an array of users who have replied to this thread.
-      if (!isset($threads[$thread_id]['replied'])) {
-        $threads[$thread_id]['replied'] = array();
-        $result = db_query("SELECT pm.author FROM {pm_message} pm INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid WHERE pmi.thread_id = %d", $thread_id);
-        while($recipient = db_fetch_object($result)) {
-          $threads[$thread_id]['replied'][$recipient->author] = $recipient->author;
-        }
-      }
-
-      // Has this user replied to this thread?
-      if (isset($threads[$thread_id]['replied']) && in_array($uid, $threads[$thread_id]['replied'])) {
-        $notifications[$uid][$mid] = TRUE;
+      } else {
+        // Check if this user has more than a single unread message
+        // in that thread. If yes, they already got a notification.
+        // They always have at least one unread message because they just
+        // received one.
+        $unread_count = db_result(db_query("SELECT COUNT(*) FROM {pm_index} WHERE thread_id = %d AND is_new = 1 AND recipient = %d AND type IN ('user', 'hidden')", $thread_id, $uid));
+        $notifications[$uid][$mid] = $unread_count == 1;
       }
     }
   }
@@ -206,7 +181,7 @@ function pm_email_notify_privatemsg_message_recipient_changed($mid, $thread_id,
       if (!_pm_email_notify_only_user($recipient_id)) {
         $types[] = 'hidden';
       }
-      if (in_array($type, $types) && _pm_email_notify_send_check($recipient_id) && ($recipient = privatemsg_user_load($recipient_id))) {
+      if (in_array($type, $types) && _pm_email_notify_send_check($recipient_id, $message) && ($recipient = privatemsg_user_load($recipient_id))) {
         pm_email_notify_send_mail($recipient, $message);
       }
     }
@@ -351,7 +326,7 @@ function pm_email_notify_user($op, &$edit, &$account, $category = NULL) {
         $email_notify_level = $edit['pm_email_notify_level'];
         if (!privatemsg_user_access('set privatemsg e-mail notification level')) {
           // If the user is not allowed to set the notification level, use the
-          // default if he wants notifications.
+          // default.
           $email_notify_level *= (int)variable_get('pm_email_notify_default_level', PM_EMAIL_NOTIFY_LEVEL_ALL);
         }
         $email_only_user = $edit['pm_email_only_user'];
diff --git a/pm_email_notify/pm_email_notify.test b/pm_email_notify/pm_email_notify.test
new file mode 100644
index 0000000..598f386
--- /dev/null
+++ b/pm_email_notify/pm_email_notify.test
@@ -0,0 +1,300 @@
+<?php
+/**
+ * @file
+ * Test file for pm_email_notify.module
+ */
+
+class PrivatemsgEMailNotifyTestCase extends DrupalWebTestCase {
+
+  protected $handledMails = 0;
+
+  /**
+   * Implements getInfo().
+   */
+  function getInfo() {
+    return array
+    (
+      'name' => t('Privatemsg E-Mail notifications.'),
+      'description' => t('Test sending e-mail notifications according to the set configuration.'),
+      'group' => t('Privatemsg'),
+    );
+  }
+
+  /**
+   * Implements setUp().
+   */
+  function setUp() {
+    parent::setUp('privatemsg', 'pm_email_notify', 'privatemsg_roles');
+
+    $this->author = $this->drupalCreateUser(array('write privatemsg', 'read privatemsg', 'write privatemsg to roles'));
+    $this->recipient = $this->drupalCreateUser(array('write privatemsg', 'read privatemsg'));
+    $this->recipient2 = $this->drupalCreateUser(array('write privatemsg', 'read privatemsg'));
+    $this->admin = $this->drupalCreateUser(array('write privatemsg', 'read privatemsg', 'administer privatemsg settings'));
+    $this->handledMails = 0;
+  }
+
+  /**
+   * Test Always configuration.
+   */
+  function testAlwaysConfiguration() {
+    $this->drupalLogin($this->author);
+
+    // Send a new message.
+    $message = array(
+      'recipient' => $this->recipient->name . ', ' . $this->recipient2->name,
+      'subject' => $this->randomName(),
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost('messages/new', $message, t('Send message'));
+    $message['thread_id'] = $this->getCurrentThreadId();
+    $this->verifyMails(array($this->recipient, $this->recipient2), $this->author, $message);
+
+    // Reply to the existing message.
+    $reply = array(
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost(NULL, $reply, t('Send message'));
+    $message['body'] = $reply['body'];
+
+    $this->verifyMails(array($this->recipient, $this->recipient2), $this->author, $message);
+
+    // Send a message to all users.
+    $message = array(
+      'recipient' => 'authenticated user',
+      'subject' => $this->randomName(),
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost('messages/new', $message, t('Send message'));
+    $message['thread_id'] = $this->getCurrentThreadId();
+
+    $recipients = array(user_load(1), $this->author, $this->recipient, $this->recipient2, $this->admin);
+    $this->verifyMails($recipients, $this->author, $message);
+  }
+
+  /**
+   * Test Always configuration.
+   */
+  function testThreadConfiguration() {
+    // Configure to only send notifications for new messages.
+    $this->drupalLogin($this->admin);
+    $edit = array(
+      'pm_email_notify_default_level' => PM_EMAIL_NOTIFY_LEVEL_THREAD,
+    );
+    $this->drupalPost('admin/settings/messages/notify', $edit, t('Save configuration'));
+    $this->drupalLogin($this->author);
+
+    // Send a new message.
+    $message = array(
+      'recipient' => $this->recipient->name . ', ' . $this->recipient2->name,
+      'subject' => $this->randomName(),
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost('messages/new', $message, t('Send message'));
+    $message['thread_id'] = $this->getCurrentThreadId();
+    $this->verifyMails(array($this->recipient, $this->recipient2), $this->author, $message);
+
+    // Reply to the existing message.
+    $reply = array(
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost(NULL, $reply, t('Send message'));
+    $message['body'] = $reply['body'];
+    $this->getNewMails(0);
+
+    // Send a message to all users.
+    $message = array(
+      'recipient' => 'authenticated user',
+      'subject' => $this->randomName(),
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost('messages/new', $message, t('Send message'));
+    $message['thread_id'] = $this->getCurrentThreadId();
+
+    $recipients = array(user_load(1), $this->author, $this->recipient, $this->recipient2, $this->admin);
+    $this->verifyMails($recipients, $this->author, $message);
+  }
+
+  /**
+   * Test Always configuration.
+   */
+  function testUnreadConfiguration() {
+    // Configure to only send notifications for new messages.
+    $this->drupalLogin($this->admin);
+    $edit = array(
+      'pm_email_notify_default_level' => PM_EMAIL_NOTIFY_LEVEL_UNREAD_ONCE,
+    );
+    $this->drupalPost('admin/settings/messages/notify', $edit, t('Save configuration'));
+    $this->drupalLogin($this->author);
+
+    // Send a new message.
+    $message = array(
+      'recipient' => $this->recipient->name . ', ' . $this->recipient2->name,
+      'subject' => $this->randomName(),
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost('messages/new', $message, t('Send message'));
+    $message['thread_id'] = $this->getCurrentThreadId();
+    $this->verifyMails(array($this->recipient, $this->recipient2), $this->author, $message);
+
+    // Reply to the existing message.
+    $reply = array(
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost(NULL, $reply, t('Send message'));
+    $message['body'] = $reply['body'];
+    $this->getNewMails(0);
+
+    // Visit the thread with the first recipient.
+    $this->drupalLogin($this->recipient);
+    $this->drupalGet('messages');
+    $this->clickLink($message['subject']);
+
+    // Back to author, send another message.
+    $this->drupalLogin($this->author);
+    $this->drupalGet('messages/view/' . $message['thread_id']);
+    $reply = array(
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost(NULL, $reply, t('Send message'));
+    $message['body'] = $reply['body'];
+    $this->verifyMails(array($this->recipient), $this->author, $message);
+
+    // Another reply, again, no notifications.
+    $reply = array(
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost(NULL, $reply, t('Send message'));
+    $message['body'] = $reply['body'];
+    $this->getNewMails(0);
+
+
+    // Send a message to all users.
+    $message = array(
+      'recipient' => 'authenticated user',
+      'subject' => $this->randomName(),
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost('messages/new', $message, t('Send message'));
+    $message['thread_id'] = $this->getCurrentThreadId();
+
+    $recipients = array(user_load(1), $this->author, $this->recipient, $this->recipient2, $this->admin);
+    $this->verifyMails($recipients, $this->author, $message);
+  }
+
+  /**
+   * Test that no mails are sent when
+   */
+  function testNeverConfiguration() {
+    // Disable notifications by default.
+    $this->drupalLogin($this->admin);
+    $edit = array(
+      'pm_email_notify_default_level' => PM_EMAIL_NOTIFY_LEVEL_DISABLED,
+    );
+    $this->drupalPost('admin/settings/messages/notify', $edit, t('Save configuration'));
+
+    $this->drupalLogin($this->author);
+
+    // Send a new message.
+    $message = array(
+      'recipient' => $this->recipient->name . ', ' . $this->recipient2->name,
+      'subject' => $this->randomName(),
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost('messages/new', $message, t('Send message'));
+    $this->getNewMails(0);
+
+    // Send a message to all users.
+    $message = array(
+      'recipient' => 'authenticated user',
+      'subject' => $this->randomName(),
+      'body' => $this->randomName(20),
+    );
+    $this->drupalPost('messages/new', $message, t('Send message'));
+    $this->getNewMails(0);
+  }
+
+  /**
+   * Extracts the current thread_id from the URl.
+   *
+   * @return
+   *   Thread id of the current page.
+   */
+  function getCurrentThreadId() {
+    preg_match('|messages\/view\/([0-9]+)|', $this->getURL(), $matches);
+    return $matches[1];
+  }
+
+  /**
+   * Return new mails.
+   *
+   * Returns all mails which were sent since the last call to this function.
+   *
+   * @param $expectedNumber
+   *   Number of new mails that should be there.
+   */
+  function getNewMails($expectedNumber) {
+    // Load all mails of the current test.
+    $mails = $this->drupalGetMails();
+
+    // Slice of the new ones.
+    $mails_slice = array_slice($mails, $this->handledMails);
+
+    // Update amount of already handled mails.
+    $this->handledMails = count($mails);
+
+    if ($expectedNumber !== FALSE) {
+      $this->assertEqual(count($mails_slice), $expectedNumber, t('@expected notification mails were sent.', array('@expected' => $expectedNumber)));
+    }
+    return $mails_slice;
+  }
+
+  /**
+   * Verify multiple notification mails
+   *
+   * @param $recipients
+   *   Array of recipients in the order in which they received their mail.
+   * @param $author
+   *   Author user object.
+   * @param $message
+   *   Message array, containing the keys thread_id, body and subject.
+   */
+  function verifyMails($recipients, $author, $message) {
+    $mails = $this->getNewMails(count($recipients));
+    foreach ($recipients as $id => $recipient) {
+      $this->verifyMail($mails[$id], $recipient, $author, $message);
+    }
+  }
+
+  /**
+   * Verify a notification mail.
+   *
+   * @param $mail
+   *   Mail array as returned by DrupalWebTestCase::drupalGetMails().
+   * @param $recipient
+   *   Recipient user object.
+   * @param $author
+   *   Author user object.
+   * @param $message
+   *   Message array, containing the keys thread_id, body and subject.
+   */
+  function verifyMail($mail, $recipient, $author, $message) {
+    $this->assertEqual($mail['id'], 'pm_email_notify_notice', t('Correct notification id.'));
+    $this->assertEqual($mail['to'], $recipient->mail, t('Correct recipient e-mail address.'));
+    $params = array(
+      '!author_uid' => $author->uid,
+      '!author' => privatemsg_recipient_format($author, array('plain' => TRUE)),
+      '!pm_subject' => trim(drupal_html_to_text(check_plain($message['subject']))),
+      '!pm_body' => trim(drupal_html_to_text(check_markup($message['body'], NULL, FALSE))),
+      '!thread' => $message['thread_id'],
+      '!user_uid' => $recipient->uid,
+      '!message' => url(privatemsg_get_dynamic_url_prefix($recipient->uid) . '/view/' . $message['thread_id'], array('absolute' => TRUE)),
+      '!settings' => url('user/' . $recipient->uid . '/edit', array('absolute' => TRUE)),
+      '!site' => variable_get('site_name', 'Drupal'),
+      '!username' => privatemsg_recipient_format($recipient, array('plain' => TRUE)),
+    );
+    $subject = strtr(_pm_email_notify_text('subject'), $params);
+    $body = drupal_wrap_mail(strtr(_pm_email_notify_text('body'), $params));
+    $this->assertEqual($mail['subject'], $subject, t('Correct subject'));
+    $this->assertEqual($mail['body'], $body, t('Correct body'));
+  }
+}
\ No newline at end of file
diff --git a/privatemsg_roles/privatemsg_roles.module b/privatemsg_roles/privatemsg_roles.module
index 1559f45..f63b65d 100644
--- a/privatemsg_roles/privatemsg_roles.module
+++ b/privatemsg_roles/privatemsg_roles.module
@@ -76,7 +76,7 @@ function privatemsg_roles_load_recipients($recipient, $limit, $offset) {
   $recipients = array();
   $rid = isset($recipient->recipient) ? $recipient->recipient : $recipient->rid;
   if ($rid == DRUPAL_AUTHENTICATED_RID) {
-    $result = db_query_range('SELECT uid FROM {users} ORDER BY uid ASC', $offset, $limit);
+    $result = db_query_range('SELECT uid FROM {users} WHERE uid > 0 ORDER BY uid ASC', $offset, $limit);
   }
   else {
     $result = db_query_range('SELECT uid FROM {users_roles} WHERE rid = %d ORDER BY uid ASC', $rid, $offset, $limit);
-- 
1.7.4.1

