Index: privatemsg.api.php
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg.api.php,v
retrieving revision 1.2
diff -u -p -r1.2 privatemsg.api.php
--- privatemsg.api.php	30 Nov 2009 17:37:15 -0000	1.2
+++ privatemsg.api.php	19 Jun 2010 10:10:39 -0000
@@ -8,8 +8,7 @@
 
 /**
  * @mainpage Privatemsg API Documentation
- * This is the (currently inofficial) API documentation of the privatemsg 6.x
- * API.
+ * This is the API documentation of Privatemsg.
  *
  * - Topics:
  *  - @link api API functions @endlink
@@ -17,6 +16,7 @@
  *  - @link message_hooks Message hooks @endlink
  *  - @link generic_hooks Generic hooks @endlink
  *  - @link theming Theming @endlink
+ *  - @link types Types of recipients @endlink
  */
 
 /**
@@ -182,7 +182,7 @@ function hook_privatemsg_sql_messages_al
 }
 
 /**
- * Load the participants of a thread.
+ * Alter the query that loads the participants of a thread.
  *
  * @param $fragments
  *   Query fragments
@@ -241,6 +241,8 @@ function hook_privatemsg_sql_unread_coun
  *   going to be deleted, parameter: $message
  * - @link hook_privatemsg_message_view_alter view_alter @endlink: the message
  *   is going to be displayed, parameter: $vars
+ * - @link hook_privatemsg_message_recipient_change recipient changed @endlink:
+ *   a recipient is added or removed from/to a message.
  *
  * In hooks with _alter suffix, $message is by reference.
  *
@@ -360,6 +362,29 @@ function hook_privatemsg_message_insert(
 }
 
 /**
+ * This hook is invoked when a recipient is added to a message.
+ *
+ * Since the hook might be invoked hundreds of times during batch or cron, only
+ * ids are passed and not complete user/message objects.
+ *
+ * @param $mid
+ *   Id of the message.
+ * @param $thread_id
+ *   Id of the thread the message belongs to.
+ * @param $recipient
+ *   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) {
+  if ($added && ($type == 'user' || $type == 'hidden')) {
+    privatemsg_filter_add_tags(array($thread_id), variable_get('privatemsg_filter_inbox_tag', ''), (object)array('uid' => $recipient));
+  }
+}
+
+/**
  * @}
  */
 
@@ -371,18 +396,32 @@ function hook_privatemsg_message_insert(
  */
 
 /**
- * Check if $author can send $recipient a message.
+ * Check if the author can send a message to the recipients.
  *
- * Return a message if it is not alled, nothing if it is. This hook is executed
- * for each recipient.
+ * This can be used to limit who can write whom based on other modules and/or
+ * settings.
  *
  * @param $author
  *   Author of the message to be sent
  * @param $recipient
  *   Recipient of the message
- */
-function hook_privatemsg_block_message($author, $recipient) {
-
+ * @return
+ *   An indexed array of arrays with the keys recipient ({type}_{key}) and
+ *   message (The reason why the recipient has been blocked).
+ */
+function hook_privatemsg_block_message($author, $recipients) {
+  $blocked = array();
+  foreach($recipients as $recipient_id => $recipient) {
+    // Deny/block if the recipient type is role and the account does not have
+    // the necessary permission.
+    if ($recipient->type == 'role' && $recipient->recipient == 2) {
+      $blocked[] = array(
+        'recipient' => $recipient_id,
+        'message' => t('Not allowed to write private messages to the role authenticated user'),
+      );
+    }
+  }
+  return $blocked;
 }
 /**
  * Add content to the view thread page.
@@ -429,6 +468,106 @@ function hook_privatemsg_thread_operatio
 }
 
 /**
+ * @}
+ */
+
+/**
+ * @defgroup types Types of recipients
+ *
+ * It is possible to define other types of recipients than the usual single
+ * user. These types are defined through a hook and a few callbacks and are
+ * stored in the {pm_index} table for each recipient entry.
+ *
+ * Because of that, only the combination of type and recipient (was uid in older
+ * versions) defines a unique recipient.
+ *
+ * This feature is usually used to define groups of recipients. Privatemsg
+ * comes with the privatemsg_roles sub-module, which allows to send messages to
+ * all members of a specific group.
+ *
+ * When sending a new message with a recipient type other than user, Privatemsg
+ * only inserts a single entry for that recipient type. However, when looking
+ * for messages for a user, Privatemsg only looks for recipient types user and
+ * hidden. To fill the gap, Privatemsg defines three ways how a non-user
+ * type is converted to hidden recipient entries.
+ *
+ * - For small recipient types (by default <100 recipients, configurable), the
+ *   entries are added directly after saving the original private message.
+ * - When sending messages through the UI, bigger recipient types are handled
+ *   with batch API.
+ * - For messages sent by the API, the hidden recipients are generated during
+ *   cron runs.
+ *
+ * Once all hidden recipients are added, the original recipient type is marked
+ * as read so Privatemsg knows that he has been processed.
+ *
+ * Privatemsg defines the following types:
+ *
+ * - user: This is the default recipient type which is used for a single user.
+ * - hidden: Used to add internal recipient entries for other recipient types.
+ * - role: The sub-module privatemsg_roles defines an additional type called
+ *   role. This allows to send messages to all members of a role.
+ *
+ * To implement a new type, the following hooks need to be implemented. Note
+ * that most of these hooks can also be used alone for other functionality than
+ * defining recipient types.
+ *
+ * - hook_privatemsg_recipient_type_info() - Tell Privatemsg about your
+ *   recipient type(s).
+ * - hook_privatemsg_name_lookup() - Convert a string to an
+ *   recipient object
+ *
+ * Additionaly, there is also a hook_privatemsg_recipient_type_info_alter() that
+ * allows to alter recipient type definitions.
+ */
+
+/**
+ * @addtogroup types
+ * @{
+ */
+
+
+/**
+ * This hook is used to tell privatemsg about the recipient types defined by a
+ * module. Each type consists of an array keyed by the internal recipient type
+ * name and the following keys must be defined.
+ *
+ * * name: Translated name of the recipient type.
+ * * description: A short description of how to send messages to to that
+ *   recipient type. This is displayed below the To: field when sending a
+ *   message.
+ * * load: A callback function that can load recipients based on their id,
+ *   example: privatemsg_roles_load_multiple().
+ * * format: Theme function to format the recipient before displaying. Must be
+ *   defined with hook_theme(), example: theme_privatemsg_roles_format().
+ * * autocomplete: Function callback to return possible autocomplete matches,
+ *   example: privatemsg_roles_autocomplete().
+ * * generate recipients: Function callback to return user ids which belong to a
+ *   recipient type, example: privatemsg_roles_load_recipients().
+ * * max: Function callback to return the highest user id of a recipient type,
+ *   example: privatemsg_roles_count_recipients().
+ * * write access: Optionally define a permission which controls write access
+ *   to that recipient type.
+ * * view access: Optionally define a permission which controls if the user is
+ *   able to see the recipient when he is looking at a thread.
+ */
+function hook_privatemsg_recipient_type_info() {
+  return array(
+    'role' => array(
+      'name' => t('Role'),
+      'description' => t('Enter the name of a role to write a message to all users which have that role. Example: authenticated user.'),
+      'load' => 'privatemsg_roles_load_multiple',
+      'format' => 'privatemsg_roles_format',
+      'autocomplete' => 'privatemsg_roles_autocomplete',
+      'generate recipients' => 'privatemsg_roles_load_recipients',
+      'max' => 'privatemsg_roles_count_recipients',
+      'write access' => 'write privatemsg to roles',
+      'view access' => 'view roles recipients',
+    ),
+  );
+}
+
+/**
  * Hook which allows to look up a user object.
  *
  * You can try to look up a user object based on the information passed to the
@@ -438,14 +577,25 @@ function hook_privatemsg_thread_operatio
  * up the string.
  */
 function hook_privatemsg_name_lookup($string) {
-  if ((int)$string > 0) {
-    // This is a possible uid, try to load a matching user.
-    if ($recipient = user_load(array('uid' => $string))) {
-      return $recipient;
-    }
+  $result = db_query("SELECT *, rid AS recipient FROM {role} WHERE name = '%s'", trim($role));
+  if ($role = db_fetch_object($result)) {
+    $role->type = 'role';
+    return $role;
   }
 }
 
 /**
+ * Allows to alter the defined recipient types.
+ *
+ * @param $types
+ *   Array with the recipient types.
+ *
+ * @see hook_privatemsg_recipient_type_info()
+ */
+function hook_privatemsg_recipient_type_info_alter(&$types) {
+  
+}
+
+/**
  * @}
  */
\ No newline at end of file
Index: privatemsg.install
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg.install,v
retrieving revision 1.15
diff -u -p -r1.15 privatemsg.install
--- privatemsg.install	4 Jan 2010 15:56:56 -0000	1.15
+++ privatemsg.install	19 Jun 2010 10:10:40 -0000
@@ -24,8 +24,8 @@ function privatemsg_schema() {
         'not null'      => TRUE,
         'unsigned'      => TRUE,
       ),
-      'uid'     => array(
-        'description'   => 'UID of either the author or the recipient',
+      'recipient'     => array(
+        'description'   => 'ID of the recipient object, typically user',
         'type'          => 'int',
         'not null'      => TRUE,
         'unsigned'      => TRUE,
@@ -44,13 +44,20 @@ function privatemsg_schema() {
         'not null' => TRUE,
         'default' => 0
       ),
+      'type' => array(
+        'description'   => 'Type of recipient object',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => 'user'
+      ),
 
     ),
     'indexes'         => array(
       'mid'               => array('mid'),
       'thread_id'         => array('thread_id'),
-      'uid'         => array('uid'),
-      'is_new'              => array('mid', 'uid', 'is_new', ),
+      'recipient'         => array('recipient', 'type'),
+      'is_new'            => array('mid', 'recipient', 'type', 'is_new'),
     ),
   );
 
@@ -528,3 +535,29 @@ function privatemsg_update_6007() {
 
   return $ret;
 }
+
+/**
+ * Change schema to allow other recipients than single users.
+ */
+function privatemsg_update_6008() {
+  $ret = array();
+  db_drop_index($ret, 'pm_index', 'uid');
+  db_drop_index($ret, 'pm_index', 'is_new');
+  db_change_field($ret, 'pm_index', 'uid', 'recipient', array(
+    'description'   => 'ID of the recipient object, typically user',
+    'type'          => 'int',
+    'not null'      => TRUE,
+    'unsigned'      => TRUE,
+  ));
+  db_add_field($ret, 'pm_index', 'type', array(
+        'description'   => 'Type of recipient object',
+        'type' => 'varchar',
+        'not null' => TRUE,
+        'length'  => '255',
+        'default' => 'user'
+      ), array('indexes' => array(
+      'recipient'         => array('recipient', 'type'),
+      'is_new'            => array('mid', 'recipient', 'type', 'is_new')
+  )));
+  return $ret;
+}
Index: privatemsg.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg.module,v
retrieving revision 1.132
diff -u -p -r1.132 privatemsg.module
--- privatemsg.module	6 Jun 2010 15:52:50 -0000	1.132
+++ privatemsg.module	19 Jun 2010 10:10:41 -0000
@@ -44,8 +44,6 @@ function privatemsg_perm() {
  *   Array with user objects.
  */
 function _privatemsg_generate_user_array($userstring, $slice = NULL) {
-  static $user_cache = array();
-
   // Convert user uid list (uid1,uid2,uid3) into an array. If $slice is not NULL
   // pass that as argument to array_slice(). For example, -4 will only load the
   // last four users.
@@ -57,11 +55,8 @@ function _privatemsg_generate_user_array
   }
   $participants = array();
   foreach ($users as $uid) {
-    if (!array_key_exists($uid, $user_cache)) {
-      $user_cache[$uid] = user_load($uid);
-    }
-    if (is_object($user_cache[$uid])) {
-      $participants[$uid] = $user_cache[$uid];
+    if ($account = privatemsg_user_load($uid)) {
+      $participants[privatemsg_recipient_key($account)] = $account;
     }
   }
   return $participants;
@@ -86,11 +81,15 @@ function _privatemsg_format_participants
     $to = array();
     $limited = FALSE;
     foreach ($part_array as $account) {
+      // Don't display recipients with type hidden.
+      if (isset($account->type) && $account->type == 'hidden') {
+        continue;
+      }
       if (is_int($limit) && count($to) >= $limit) {
         $limited = TRUE;
         break;
       }
-      $to[] = theme('username', $account);
+      $to[] = privatemsg_recipient_format($account);
     }
 
     $limit_string = '';
@@ -288,7 +287,7 @@ function privatemsg_view_access($thread)
  *   TRUE if user has disabled private messaging, FALSE otherwise
  */
 function privatemsg_is_disabled(&$account) {
-  if (!$account || !$account->uid) {
+  if (!$account || !isset($account->uid) || !$account->uid) {
     return FALSE;
   }
 
@@ -351,11 +350,34 @@ function privatemsg_thread_load($thread_
       $query = _privatemsg_assemble_query('participants', $thread_id);
       $participants = db_query($query['query']);
       $thread['participants'] = array();
+      $to_load = array();
       while ($participant = db_fetch_object($participants)) {
-        $thread['participants'][$participant->uid] = $participant;
+        // If the recipient is not of type user, store the recipient id for
+        // later loading, if the user has view access.
+        if ($participant->type != 'user' && $participant->type != 'hidden') {
+          if (privatemsg_recipient_access($participant->type, 'view access')) {
+            $to_load[$participant->type][] = $participant->recipient;
+          }
+        }
+        else {
+          $thread['participants'][privatemsg_recipient_key($participant)] = $participant;
+        }
       }
+
+      // Now, load all non-user recipients.
+      foreach ($to_load as $type => $ids) {
+        $type_info = privatemsg_recipient_get_type($type);
+        if (isset($type_info['load']) && is_callable($type_info['load'])) {
+          $loaded = $type_info['load']($ids);
+          if (is_array($loaded)) {
+            $thread['participants'] += $loaded;
+          }
+        }
+      }
+
       $thread['read_all'] = FALSE;
-      if (!array_key_exists($account->uid, $thread['participants']) && privatemsg_user_access('read all private messages', $account)) {
+      $exists = array_key_exists('user_' . $account->uid, $thread['participants']) || array_key_exists('hidden_' . $account->uid, $thread['participants']);
+      if (!$exists && privatemsg_user_access('read all private messages', $account)) {
         $thread['read_all'] = TRUE;
       }
 
@@ -508,6 +530,46 @@ function privatemsg_cron() {
       $flushed++;
     }
   }
+
+  $result = db_query_range("SELECT pmi.*, pm.author FROM {pm_index} pmi INNER JOIN {pm_message} pm ON pm.mid = pmi.mid WHERE pmi.type NOT IN ('user', 'hidden') AND pmi.is_new = 1 ORDER BY mid ASC", 0, 10);
+  // Number of user ids to process for this cron run.
+  $total_remaining = variable_get('privatemgs_cron_recipient_per_run', 1000);
+  $current_process = variable_get('privatemsg_cron_recipient_process', array());
+  while ($recipient = db_fetch_object($result)) {
+    $type = privatemsg_recipient_get_type($recipient->type);
+    if (!$type) {
+      continue;
+    }
+    // Check if we already started to process this recipient.
+    $offset = 0;
+    if (!empty($current_process) && $current_process['mid'] == $recipient->mid && $current_process['recipient'] == $recipient->recipient && $current_process['type'] == $recipient->type) {
+      $offset = $current_process['offset'];
+    }
+
+    $load_function = $type['generate recipients'];
+    $uids = $load_function($recipient, privatemsg_user_load($recipient->author), $total_remaining, $offset);
+    if (!empty($uids)) {
+      foreach ($uids as $uid) {
+        privatemsg_message_change_recipient($recipient->mid, $uid, 'hidden');
+      }
+    }
+    // If less than the total remaining uids were returned, we are finished.
+    if (count($uids) < $total_remaining) {
+      $total_remaining -= count($uids);
+      db_query("UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND recipient = %d AND type = '%s'", PRIVATEMSG_READ, $recipient->mid, $recipient->recipient, $recipient->type);
+      // Reset current process if necessary.
+      if ($offset > 0) {
+        variable_set('privatemsg_cron_recipient_process', array());
+      }
+    }
+    else {
+      // We are not yet finished, save current process and break.
+      $current_process = (array)$recipient;
+      $current_process['offset'] = max($uids);
+      variable_set('privatemsg_cron_recipient_process', $current_process);
+      break;
+    }
+  }
 }
 
 function privatemsg_theme() {
@@ -562,7 +624,7 @@ function privatemsg_preprocess_privatems
   $vars['mid'] = isset($message['mid']) ? $message['mid'] : NULL;
   $vars['thread_id'] = isset($message['thread_id']) ? $message['thread_id'] : NULL;
   $vars['author_picture'] = theme('user_picture', $message['author']);
-  $vars['author_name_link'] = theme('username', $message['author']);
+  $vars['author_name_link'] = privatemsg_recipient_format($message['author']);
   /**
    * @todo perhaps make this timestamp configurable via admin UI?
    */
@@ -610,7 +672,7 @@ function privatemsg_message_change_statu
     global $user;
     $account = $user;
   }
-  $query = "UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND uid = %d";
+  $query = "UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND recipient = %d AND type IN ('user', 'hidden')";
   db_query($query, $status, $pmid, $account->uid);
 }
 
@@ -628,7 +690,7 @@ function privatemsg_unread_count($accoun
     global $user;
     $account = $user;
   }
-  if ( !isset($counts[$account->uid])) {
+  if (!isset($counts[$account->uid])) {
     $query = _privatemsg_assemble_query('unread_count', $account);
     $counts[$account->uid] = db_result(db_query($query['query']));
   }
@@ -645,9 +707,17 @@ function _privatemsg_load_thread_partici
   $query = _privatemsg_assemble_query('participants', $thread_id);
   $result = db_query($query['query']);
   $participants = array();
-  while ($uid = db_fetch_object($result)) {
-    if (($recipient = user_load($uid->uid))) {
-      $participants[$recipient->uid] = $recipient;
+  while ($participant = db_fetch_object($result)) {
+    // Ignore hidden recipients.
+    if ($participant->type == 'hidden') {
+      continue;
+    }
+    if ($participant->type == 'user' && ($account = privatemsg_user_load($participant->recipient))) {
+      $participants[privatemsg_recipient_key($account)] = $account;
+
+    }
+    elseif ($participant->type != 'user' && privatemsg_recipient_access($participant->type, 'write access')) {
+      $participants[privatemsg_recipient_key($participant)] = $participant;
     }
   }
   return $participants;
@@ -676,22 +746,39 @@ function _privatemsg_parse_userstring($i
   foreach ($input as $string) {
     $string = trim($string);
     if (!empty($string)) { // We don't care about white space names.
-
       // First, check if another module is able to resolve the string into an
       // user object.
+
+      // Load recipient types.
+      $types = privatemsg_recipient_get_types();
+
       foreach (module_implements('privatemsg_name_lookup') as $module) {
         $function = $module . '_privatemsg_name_lookup';
         if (($recipient = $function($string)) && is_object($recipient)) {
-          // If there is a match, continue with the next input string.
-          $recipients[$recipient->uid] = $recipient;
-          continue 2;
+          // Default to recipient type user.
+          if (!isset($recipient->type)) {
+            $recipient->type = 'user';
+            $recipient->recipient = $recipient->uid;
+          }
+          // Only add recipient if user has the permission.
+          if (privatemsg_recipient_access($recipient->type, 'write access')) {
+            $recipients[privatemsg_recipient_key($recipient)] = $recipient;
+            continue 2;
+          }
+          else {
+            continue;
+          }
         }
       }
       // Fall back to the default username lookup.
       if (!$error = module_invoke('user', 'validate_name', $string)) {
         // String is a valid username, look it up.
+        // Remove optonal user specifier.
+        $string = str_replace('[user]', '', $string);
         if ($recipient = user_load(array('name' => $string))) {
-          $recipients[$recipient->uid] = $recipient;
+          $recipient->recipient = $recipient->uid;
+          $recipient->type = 'user';
+          $recipients[privatemsg_recipient_key($recipient)] = $recipient;
           continue;
         }
       }
@@ -745,14 +832,14 @@ function privatemsg_sql_list(&$fragments
     // @todo: Replace this with a single query similiar to the tag list.
     if ($GLOBALS['db_type'] == 'pgsql') {
       // PostgreSQL does not know GROUP_CONCAT, so a subquery is required.
-      $fragments['select'][]      = "array_to_string(array(SELECT DISTINCT textin(int4out(pmia.uid))
+      $fragments['select'][]      = "array_to_string(array(SELECT DISTINCT textin(int4out(pmia.recipient))
                                                             FROM {pm_index} pmia
-                                                            WHERE pmia.thread_id = pmi.thread_id), ',') AS participants";
+                                                            WHERE pmia.type = 'user' AND pmia.thread_id = pmi.thread_id), ',') AS participants";
     }
     else {
-      $fragments['select'][]      = '(SELECT GROUP_CONCAT(DISTINCT pmia.uid SEPARATOR ",")
+      $fragments['select'][]      = "(SELECT GROUP_CONCAT(DISTINCT pmia.recipient SEPARATOR ',')
                                                             FROM {pm_index} pmia
-                                                            WHERE pmia.thread_id = pmi.thread_id) AS participants';
+                                                            WHERE pmia.type = 'user' AND pmia.thread_id = pmi.thread_id) AS participants";
     }
   }
   if (in_array('thread_started', $fields)) {
@@ -762,8 +849,8 @@ function privatemsg_sql_list(&$fragments
   $fragments['inner_join'][]  = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid';
 
   // Only load undeleted messages of the current user and group by thread.
-  $fragments['where'][]       = 'pmi.uid = %d';
-  $fragments['query_args']['where'][]  = $account->uid;
+  $fragments['where'][]      = "pmi.recipient = %d AND pmi.type IN ('user', 'hidden')";
+  $fragments['query_args']['where'][] = $account->uid;
   $fragments['where'][]       = 'pmi.deleted = 0';
   $fragments['group_by'][]    = 'pmi.thread_id';
 
@@ -801,8 +888,8 @@ function privatemsg_sql_load(&$fragments
   $fragments['where'][]       = 'pmi.mid IN (' . db_placeholders($pmids) . ')';
   $fragments['query_args']['where']  += $pmids;
   if ($account) {
-    $fragments['where'][]       = 'pmi.uid = %d';
-    $fragments['query_args']['where'][]  = $account->uid;
+    $fragments['where'][]      = "pmi.recipient = %d AND pmi.type IN ('user', 'hidden')";
+    $fragments['query_args']['where'][] = $account->uid;
   }
   $fragments['order_by'][] = 'pm.timestamp ASC';
   $fragments['order_by'][] = 'pm.mid ASC';
@@ -828,9 +915,8 @@ function privatemsg_sql_messages(&$fragm
   $fragments['query_args']['where']   += $threads;
   $fragments['inner_join'][]  = 'INNER JOIN {pm_message} pm ON (pm.mid = pmi.mid)';
   if ($account) {
-    // Only load the user's messages.
-    $fragments['where'][]     = 'pmi.uid = %d';
-    $fragments['query_args']['where'][]  = $account->uid;
+    $fragments['where'][]      = "pmi.recipient = %d AND pmi.type IN ('user', 'hidden')";
+    $fragments['query_args']['where'][] = $account->uid;
   }
   if (!$load_all) {
     // Also load deleted messages when requested.
@@ -857,16 +943,29 @@ function privatemsg_sql_messages(&$fragm
  *   Thread id from which the participants should be loaded.
  */
 function privatemsg_sql_participants(&$fragments, $thread_id) {
+  global $user;
   $fragments['primary_table'] = '{pm_index} pmi';
 
   // Only load each participant once since they are listed as recipient for
   // every message of that thread.
-  $fragments['select'][]      = 'DISTINCT(pmi.uid) AS uid';
-  $fragments['select'][]      = 'u.name AS name';
+  $fragments['select'][]      = 'pmi.recipient';
+  $fragments['select'][]      = 'u.name';
+  $fragments['select'][]      = 'pmi.type';
 
-  $fragments['inner_join'][]  = 'INNER JOIN {users} u ON (u.uid = pmi.uid)';
+  $fragments['inner_join'][]  = "LEFT JOIN {users} u ON (u.uid = pmi.recipient AND pmi.type IN ('user', 'hidden'))";
   $fragments['where'][]       = 'pmi.thread_id = %d';
   $fragments['query_args']['where'][]  = $thread_id;
+
+  $fragments['where'][]       = "(pmi.type <> 'hidden') OR (pmi.type = 'hidden' AND pmi.recipient = %d)";
+  $fragments['query_args']['where'][]  = $user->uid;
+
+  // Only load recipients of messages which are visible for that user.
+  $fragments['where'][]       = '(SELECT 1 FROM {pm_index} pmiu WHERE pmi.mid = pmiu.mid AND pmiu.recipient = %d) = 1';
+  $fragments['query_args']['where'][]  = $user->uid;
+
+  $fragments['group_by'][]    = 'pmi.recipient';
+  $fragments['group_by'][]    = 'u.name';
+  $fragments['group_by'][]    = 'pmi.type';
 }
 
 /**
@@ -885,7 +984,7 @@ function privatemsg_sql_unread_count(&$f
   // Only count new messages that have not been deleted.
   $fragments['where'][]       = 'pmi.deleted = 0';
   $fragments['where'][]       = 'pmi.is_new = 1';
-  $fragments['where'][]       = 'pmi.uid = %d';
+  $fragments['where'][]       = "pmi.recipient = %d AND pmi.type IN ('user', 'hidden')";
   $fragments['query_args']['where'][]  = $account->uid;
 }
 
@@ -1032,10 +1131,10 @@ function privatemsg_user($op, &$edit, &$
       }
 
       // Delete recipient entries of that user.
-      db_query('DELETE FROM {pm_index} WHERE uid = %d', $account->uid);
+      db_query("DELETE FROM {pm_index} WHERE recipient = %d and type = 'user'", $account->uid);
 
       // DELETE any disable flag for user.
-      db_query('DELETE from {pm_disable} where uid=%d', $account->uid);
+      db_query("DELETE from {pm_disable} WHERE uid = %d", $account->uid);
       break;
   }
 }
@@ -1135,7 +1234,7 @@ function privatemsg_message_change_delet
   }
 
   if ($account) {
-    db_query('UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND uid = %d', $delete_value, $pmid, $account->uid);
+    db_query("UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND recipient = %d AND type IN ('user', 'hidden')", $delete_value, $pmid, $account->uid);
   }
   else {
     // Mark deleted for all users.
@@ -1189,10 +1288,14 @@ function privatemsg_new_thread($recipien
   $message = array();
   $message['subject'] = $subject;
   $message['body'] = $body;
-  // Make sure that recipients are keyed by user id and are not added
+  // Make sure that recipients are keyed correctly and are not added
   // multiple times.
   foreach ($recipients as $recipient) {
-    $message['recipients'][$recipient->uid] = $recipient;
+    if (!isset($recipient->type)) {
+      $recipient->type = 'user';
+      $recipient->recipient = $recipient->uid;
+    }
+    $message['recipients'][privatemsg_recipient_key($recipient)] = $recipient;
   }
 
   // Set custom options, if any.
@@ -1209,6 +1312,9 @@ function privatemsg_new_thread($recipien
   $validated = _privatemsg_validate_message($message);
   if ($validated['success']) {
     $validated['message'] = _privatemsg_send($message);
+    if ($validated['message'] !== FALSE) {
+      _privatemsg_handle_recipients($validated['message']['mid'], $validated['message']['recipients'], $validated['message']['author'], FALSE);
+    }
   }
 
   return $validated;
@@ -1284,11 +1390,14 @@ function privatemsg_reply($thread_id, $b
   $validated = _privatemsg_validate_message($message);
   if ($validated['success']) {
     $validated['message'] = _privatemsg_send($message);
+    if ($validated['message'] !== FALSE) {
+      _privatemsg_handle_recipients($validated['message']['mid'], $validated['message']['recipients'], $validated['message']['author'], FALSE);
+    }
   }
   return $validated;
 }
 
-function _privatemsg_validate_message(&$message, $form = FALSE) {
+function _privatemsg_validate_message(&$message, $form = FALSE, $display_block_messages = TRUE) {
   $messages = array('error' => array(), 'warning' => array());
   if (!privatemsg_user_access('write privatemsg', $message['author'])) {
     // no need to do further checks in this case...
@@ -1356,9 +1465,12 @@ function _privatemsg_validate_message(&$
 
   if (!empty($message['recipients']) && is_array($message['recipients'])) {
     foreach (module_invoke_all('privatemsg_block_message', $message['author'], $message['recipients']) as $blocked) {
-      unset($message['recipients'][$blocked['uid']]);
+      unset($message['recipients'][$blocked['recipient']]);
       if ($form) {
-        drupal_set_message($blocked['message'], 'warning');
+        // Don't display information about blocked users when replying.
+        if ($display_block_messages) {
+          drupal_set_message($blocked['message'], 'warning');
+        }
       }
       else {
         $messages['warning'][] = $blocked['message'];
@@ -1400,14 +1512,14 @@ function _privatemsg_send($message) {
 
   drupal_alter('privatemsg_message_presave', $message);
 
-  $index_sql = "INSERT INTO {pm_index} (mid, thread_id, uid, is_new, deleted) VALUES (%d, %d, %d, %d, 0)";
+  $index_sql = "INSERT INTO {pm_index} (mid, thread_id, recipient, type, is_new, deleted) VALUES (%d, %d, %d, '%s', %d, 0)";
   if (isset($message['read_all']) && $message['read_all']) {
     // The message was sent in read all mode, add the author as recipient to all
     // existing messages.
     $query_messages = _privatemsg_assemble_query('messages', array($message['thread_id']), NULL);
     $conversation = db_query($query_messages['query']);
     while ($result = db_fetch_array($conversation)) {
-      if (!db_query($index_sql, $result['mid'], $message['thread_id'], $message['author']->uid, 0)) {
+      if (!db_query($index_sql, $result['mid'], $message['thread_id'], $message['author']->uid, 'user', 0)) {
         return FALSE;
       }
     }
@@ -1433,7 +1545,7 @@ function _privatemsg_send($message) {
   // 2) Save message to recipients.
   // Each recipient gets a record in the pm_index table.
   foreach ($message['recipients'] as $recipient) {
-    if (!db_query($index_sql, $mid, $message['thread_id'], $recipient->uid, 1) ) {
+    if (!db_query($index_sql, $mid, $message['thread_id'], $recipient->recipient, $recipient->type, 1) ) {
       // We assume if one insert failed then the rest may fail too against the
       // same table.
       return FALSE;
@@ -1442,10 +1554,10 @@ function _privatemsg_send($message) {
 
   // When author is also the recipient, we want to set message to UNREAD.
   // All other times the message is set to READ.
-  $is_new = isset($message['recipients'][$message['author']->uid]) ? 1 : 0;
+  $is_new = isset($message['recipients']['user_' . $message['author']->uid]) ? 1 : 0;
 
   // Also add a record for the author to the pm_index table.
-  if (!db_query($index_sql, $mid, $message['thread_id'], $message['author']->uid, $is_new)) {
+  if (!db_query($index_sql, $mid, $message['thread_id'], $message['author']->uid, 'user', $is_new)) {
     return FALSE;
   }
 
@@ -1493,7 +1605,7 @@ function privatemsg_get_link($recipients
     if (variable_get('privatemsg_display_link_self', TRUE) == FALSE && $account->uid == $recipient->uid) {
       continue;
     }
-    if (count(module_invoke_all('privatemsg_block_message', $account, array($recipient))) > 0) {
+    if (count(module_invoke_all('privatemsg_block_message', $account, array(privatemsg_recipient_key($recipient) => $recipient))) > 0) {
       continue;
     }
     $validated[] = $recipient->uid;
@@ -1546,9 +1658,9 @@ function privatemsg_message_load_multipl
   $messages = array();
   while ($message = db_fetch_array($result)) {
     // Load author of message.
-    if (!($message['author'] = user_load($message['author']))) {
+    if (!($message['author'] = privatemsg_user_load($message['author']))) {
       // If user does not exist, load anonymous user.
-      $message['author'] = user_load(array('uid' => 0));
+      $message['author'] = privatemsg_user_load(0);
     }
     $returned = module_invoke_all('privatemsg_message_load', $message);
     if (!empty($returned)) {
@@ -1722,9 +1834,9 @@ function privatemsg_thread_change_status
     global $user;
     $account = drupal_clone($user);
   }
-  // Merge status and uid with the threads list. array_merge() will not overwrite/ignore thread_id 1.
+  // Merge status and uid with the exising thread list.
   $params = array_merge(array($status, $account->uid), $threads);
-  db_query('UPDATE {pm_index} SET is_new = %d WHERE uid = %d AND thread_id IN ('. db_placeholders($threads) .')', $params);
+  db_query("UPDATE {pm_index} SET is_new = %d WHERE recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', $params);
 
   if ($status == PRIVATEMSG_UNREAD) {
     drupal_set_message(format_plural(count($threads), 'Marked 1 thread as unread.', 'Marked @count threads as unread.'));
@@ -1839,15 +1951,15 @@ function privatemsg_privatemsg_block_mes
   $blocked = array();
   if (privatemsg_is_disabled($author)) {
     $blocked[] = array(
-      'uid' => $author->uid,
+      'recipient' => 'user_' . $author->uid,
       'message' => t('You have disabled private message sending and receiving.'),
     );
   }
   foreach ($recipients as $recipient) {
     if (privatemsg_is_disabled($recipient)) {
       $blocked[] = array(
-        'uid' => $recipient->uid,
-        'message' => t('%recipient has disabled private message receiving.', array('%recipient' => $recipient->name))
+        'recipient' => 'user_' . $recipient->uid,
+        'message' => t('%recipient has disabled private message receiving.', array('%recipient' => $recipient->name)),
       );
     }
   }
@@ -1917,7 +2029,7 @@ function privatemsg_link($type, $object,
   }
 
   $types = array_filter(variable_get('privatemsg_link_node_types', array()));
-  $url = privatemsg_get_link(user_load($object->uid));
+  $url = privatemsg_get_link(privatemsg_user_load($object->uid));
   if ($type == 'node' && in_array($object->type, $types) && !empty($url) && ($teaser == FALSE || variable_get('privatemsg_display_on_teaser', 1))) {
     $links['privatemsg_link'] = array(
       'title' => t('Send author a message'),
@@ -1943,4 +2055,261 @@ function privatemsg_views_api() {
     'api' => 2,
     'path' => drupal_get_path('module', 'privatemsg') . '/views',
   );
-}
\ No newline at end of file
+}
+
+/**
+ * Privatemsg wrapper function for user_load() with a static cache.
+ *
+ * The function additionaly also adds the privatemsg specific recipient id (uid)
+ * and recipient type to the user object.
+ *
+ * @param $uid
+ *   Which uid to load.
+ * @return
+ *   If existing for the passed in uid, the user object with the recipient
+ *   and type properties.
+ */
+function privatemsg_user_load($uid) {
+  static $user_cache = array();
+  if (!array_key_exists($uid, $user_cache)) {
+    $user_cache[$uid] = user_load($uid);
+    if (is_object($user_cache[$uid])) {
+      $user_cache[$uid]->recipient = $user_cache[$uid]->uid;
+      $user_cache[$uid]->type = 'user';
+    }
+  }
+  return $user_cache[$uid];
+}
+
+/**
+ * Return key for a recipient object used for arrays.
+ * @param $recipient
+ *   Recipient object, must have type and recipient properties.
+ * @return
+ *   A string that looks like type_id.
+ *
+ * @ingroup types
+ */
+function privatemsg_recipient_key($recipient) {
+  return $recipient->type . '_' . $recipient->recipient;
+}
+
+/**
+ * Returns an array of defined recipient types.
+ *
+ * @return
+ *   Array of recipient types
+ * @see hook_privatemsg_recipient_type_info()
+ *
+ * @ingroup types
+ */
+function privatemsg_recipient_get_types() {
+  static $types = NULL;
+
+  if ($types === NULL) {
+    $types = module_invoke_all('privatemsg_recipient_type_info');
+    if (!is_array($types)) {
+      $types = array();
+    }
+    drupal_alter('privatemsg_recipient_type_info', $types);
+    uasort($types, 'element_sort');
+  }
+  return $types;
+}
+/**
+ * Return a single recipient type information.
+ * @param $type
+ *   Name of the recipient type.
+ * @return
+ *   Array with the recipient type definition. NULL if the type doesn't exist.
+ *
+ * @ingroup types
+ */
+function privatemsg_recipient_get_type($type) {
+  $types = privatemsg_recipient_get_types();
+  if (!is_string($type)) {
+    exit;
+  }
+  if (isset($types[$type])) {
+    return $types[$type];
+  }
+}
+
+/**
+ * Add or remove a recipient to an existing message.
+ *
+ * @param $mid
+ *   Message id for which the recipient should be added.
+ * @param $recipient
+ *   Recipient id that should be added, for example uid.
+ * @param $type
+ *   Type of the recipient, defaults to hidden.
+ * @param $add
+ *   If TRUE, adds the recipient, if FALSE, removes it.
+ */
+function privatemsg_message_change_recipient($mid, $uid, $type = 'user', $add = TRUE) {
+
+  $thread_id = db_result(db_query('SELECT thread_id FROM {pm_index} WHERE mid = %d', $mid));
+  if ($add) {
+    // Only add the recipient if he does not block the author.
+    $author_uid = db_result(db_query('SELECT author FROM {pm_message} WHERE mid = %d', $mid));
+    $recipient = privatemsg_user_load($uid);
+    $user_blocked = module_invoke_all('privatemsg_block_message', privatemsg_user_load($author_uid), array(privatemsg_recipient_key($recipient) => $recipient));
+    if (count($user_blocked) <> 0) {
+      return;
+    }
+
+    // Make sure to only add a recipient once. The types user and hidden are
+    // considered equal here.
+    if ($type == 'user' || $type == 'hidden') {
+      $exists = db_result(db_query("SELECT 1 FROM {pm_index} WHERE type IN ('user', 'hidden') AND recipient = %d AND mid = %d", $uid, $mid));
+    }
+    else {
+      $exists = db_result(db_query("SELECT 1 FROM {pm_index} WHERE type = '%s' AND recipient = %d AND mid = %d", $type, $uid, $mid));
+    }
+    if (!$exists) {
+      $add_sql = "INSERT INTO {pm_index} (mid, thread_id, recipient, type, is_new, deleted) VALUES (%d, %d, %d, '%s', 1, 0)";
+      db_query($add_sql, $mid, $thread_id, $uid, $type);
+    }
+  }
+  else {
+    $delete_sql = "DELETE FROM {pm_index} WHERE mid = %d AND thread_id = %d AND recipient = %d AND type = '%s'";
+    db_query($delete_sql, $mid, $thread_id, $uid, $type);
+  }
+  module_invoke_all('privatemsg_message_recipient_changed', $mid, $thread_id, $uid, $type, $add);
+}
+
+/**
+ * Handle the non-user recipients of a new message.
+ *
+ * Either process them directly if they have less than a certain amount of users
+ * or, if enabled, add them to a batch.
+ *
+ * @param $mid
+ *   Message id for which the recipients are processed.
+ * @param $recipients
+ *   Array of recipients.
+ * @param $use_batch
+ *   Use batch API to process recipients.
+ */
+function _privatemsg_handle_recipients($mid, $recipients, $author, $use_batch = TRUE) {
+  $batch = array(
+    'title' => t('Processing recipients'),
+    'operations' => array(),
+    'file' => drupal_get_path('module', 'privatemsg') . '/privatemsg.pages.inc',
+    'progress_message' =>  t('Processing recipients'),
+  );
+  $small_threshold = variable_get('privatemsg_recipient_small_threshold', 100);
+  foreach ($recipients as $recipient) {
+    // Add a batch operation to press non-user recipient types.
+    if ($recipient->type != 'user' && $recipient->type != 'hidden') {
+      $type = privatemsg_recipient_get_type($recipient->type);
+      // Try to fetch 100 recipients, if there are 99 or less, process them
+      // now and don't use batch api.
+      $load_function = $type['generate recipients'];
+      if (!is_callable($load_function)) {
+        db_query("UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND recipient = %d AND type = '%s'", PRIVATEMSG_READ, $mid, $recipient->recipient, $recipient->type);
+        drupal_set_message(t('Recipient type %type is not correctly implemented', array('%type' => $recipient->type)), 'error');
+        continue;
+      }
+      $uids = $load_function($recipient, $author, $small_threshold, 0);
+      if (count($uids) < $small_threshold) {
+        if (!empty($uids)) {
+          foreach ($uids as $uid) {
+            privatemsg_message_change_recipient($mid, $uid, 'hidden');
+          }
+        }
+        db_query("UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND recipient = %d AND type = '%s'", PRIVATEMSG_READ, $mid, $recipient->recipient, $recipient->type);
+        continue;
+      }
+      if ($use_batch) {
+        $batch['operations'][] = array('privatemsg_load_recipients', array($mid, $recipient, $author));
+      }
+    }
+  }
+
+  // Set batch if there are outstanding operations.
+  if ($use_batch && !empty($batch['operations'])) {
+    batch_set($batch);
+  }
+}
+
+/**
+ * This function is used to test if the current user has write/view access
+ * for a specific recipient type.
+ *
+ * @param $type_name
+ *   The name of the recipient type.
+ * @param $permission
+ *   Which permission should be checked: 'write access' or 'view access'.
+ *
+ * @return
+ *   TRUE if the user has that permission (or not permission is defined) and
+ *   FALSE if not.
+ *
+ * @ingroup types
+ */
+function privatemsg_recipient_access($type_name, $permission) {
+  if (($type = privatemsg_recipient_get_type($type_name)) && isset($type[$permission])) {
+    if (is_bool($type[$permission])) {
+      return $types[$permission];
+    }
+    return user_access($type[$permission]);
+  }
+  // If no access permission is defined, access is allowed.
+  return TRUE;
+}
+
+/**
+ * Format a single participant.
+ *
+ * @param $participant
+ *   The participant object to format.
+ *
+ * @ingroup types.
+ */
+function privatemsg_recipient_format($participant) {
+  if ((!isset($participant->type) || $participant->type == 'user') && (!empty($participant->name) || isset($participant->uid) && $participant->uid === '0')) {
+    if (!isset($participant->uid)) {
+      $participant->uid = $participant->recipient;
+    }
+    return theme('username', $participant);
+  }
+  else {
+    $type = privatemsg_recipient_get_type($participant->type);
+    if (isset($type['format'])) {
+      return theme($type['format'], $participant, array());
+    }
+    return NULL;
+  }
+}
+
+/**
+ * Implements hook_privatemsg_recipient_types_info().
+ */
+function privatemsg_privatemsg_recipient_type_info() {
+  return array(
+    'user' => array(
+      'name' => t('User'),
+      'description' => t('Enter a user name to write a message to a user.'),
+      'load' => 'privatemsg_user_load',
+      'format' => 'username',
+      'autocomplete' => 'privatemsg_user_autocomplete',
+      // Make sure this comes always last.
+      '#weight' => 50,
+    ),
+  );
+}
+
+/**
+ * Implements callback_recipient_autocomplete().
+ */
+function privatemsg_user_autocomplete($fragment, $names, $limit) {
+  $query = _privatemsg_assemble_query('autocomplete', $fragment, $names);
+  $result = db_query_range($query['query'], $fragment, 0, $limit);
+  $matches = array();
+  while ($user = db_fetch_object($result)) {
+    $matches[] = $user->name;
+  }
+  return $matches;
+}
Index: privatemsg.pages.inc
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg.pages.inc,v
retrieving revision 1.4
diff -u -p -r1.4 privatemsg.pages.inc
--- privatemsg.pages.inc	6 Jun 2010 15:13:47 -0000	1.4
+++ privatemsg.pages.inc	19 Jun 2010 10:10:41 -0000
@@ -30,7 +30,7 @@ function privatemsg_list(&$form_state, $
     if (!privatemsg_user_access('read all private messages')) {
       drupal_set_message(t("You do not have sufficient rights to view someone else's messages"), 'warning');
     }
-    elseif ($account_check = user_load(array('uid' => $uid))) {
+    elseif ($account_check = privatemsg_user_load($uid)) {
       // Has rights and user_load return an array so user does exist
       $account = $account_check;
     }
@@ -196,23 +196,29 @@ function privatemsg_new(&$form_state, $r
 
   $usercount = 0;
   $to = array();
-  $to_themed = array();
   $blocked = FALSE;
   foreach ($recipients as $recipient) {
-    if (in_array($recipient->name, $to)) {
+    if ($recipient->type == 'hidden') {
+      continue;
+    }
+    if (isset($to[privatemsg_recipient_key($recipient)])) {
       // We already added the recipient to the list, skip him.
       continue;
     }
+    if (!privatemsg_recipient_access($recipient->type, 'write access')) {
+      // User does not have access to write to this recipient, continue.
+      continue;
+    }
+
     // Check if another module is blocking the sending of messages to the recipient by current user.
-    $user_blocked = module_invoke_all('privatemsg_block_message', $user, array($recipient->uid => $recipient));
-    if (!count($user_blocked) <> 0 && $recipient->uid) {
-      if ($recipient->uid == $user->uid) {
+    $user_blocked = module_invoke_all('privatemsg_block_message', $user, array(privatemsg_recipient_key($recipient) => $recipient));
+    if (!count($user_blocked) <> 0 && $recipient->recipient) {
+      if ($recipient->type == 'user' && $recipient->recipient == $user->uid) {
         $usercount++;
         // Skip putting author in the recipients list for now.
         continue;
       }
-      $to[] = $recipient->name;
-      $to_themed[$recipient->uid] = theme('username', $recipient);
+      $to[privatemsg_recipient_key($recipient)] = privatemsg_recipient_format($recipient);
     }
     else {
       // Recipient list contains blocked users.
@@ -222,8 +228,7 @@ function privatemsg_new(&$form_state, $r
 
   if (empty($to) && $usercount >= 1 && !$blocked) {
     // Assume the user sent message to own account as if the usercount is one or less, then the user sent a message but not to self.
-    $to[] = $user->name;
-    $to_themed[$user->uid] = theme('username', $user);
+    $to['user_' . $user->uid] = privatemsg_recipient_format($user);
   }
 
   if (!empty($to)) {
@@ -263,10 +268,20 @@ function privatemsg_new(&$form_state, $r
     '#value' => $user,
   );
   if (is_null($thread_id)) {
+
+    $description_array = array();
+    foreach (privatemsg_recipient_get_types() as $name => $type) {
+      if (privatemsg_recipient_access('write access', $name)) {
+        $description_array[] = $type['description'];
+      }
+    }
+    $description = t('Enter the recipient, separate recipients with commas.');
+    $description .= theme('item_list', $description_array);
+
     $form['privatemsg']['recipient'] = array(
       '#type'               => 'textfield',
       '#title'              => t('To'),
-      '#description'        => t('Separate multiple names with commas.'),
+      '#description'        => $description,
       '#default_value'      => $recipients_string,
       '#required'           => TRUE,
       '#weight'             => -10,
@@ -333,7 +348,7 @@ function privatemsg_new(&$form_state, $r
       '#type' => 'value',
       '#default_value' => $subject,
     );
-    $recipients_string_themed = implode(', ', $to_themed);
+    $recipients_string_themed = implode(', ', $to);
     $form['privatemsg']['recipient_display'] = array(
       '#value' =>  '<p>'. t('<strong>Reply to thread</strong>:<br /> Recipients: !to', array('!to' => $recipients_string_themed)) .'</p>',
       '#weight' => -10,
@@ -367,12 +382,11 @@ function privatemsg_new_validate($form, 
     // Load participants.
     $message['recipients'] = _privatemsg_load_thread_participants($message['thread_id']);
     // Remove author.
-    if (isset($message['recipients'][$message['author']->uid]) && count($message['recipients']) > 1) {
-      unset($message['recipients'][$message['author']->uid]);
+    if (isset($message['recipients']['user_' . $message['author']->uid]) && count($message['recipients']) > 1) {
+      unset($message['recipients']['user_' . $message['author']->uid]);
     }
   }
-
-  $validated = _privatemsg_validate_message($message, TRUE);
+  $validated = _privatemsg_validate_message($message, TRUE, !isset($message['thread_id']));
   foreach ($validated['messages'] as $type => $text) {
     drupal_set_message($text, $type);
   }
@@ -387,13 +401,14 @@ function privatemsg_new_validate($form, 
  * Submit callback for the privatemsg_new form.
  */
 function privatemsg_new_submit($form, &$form_state) {
-  $status = _privatemsg_send($form_state['validate_built_message']);
-  // Load usernames to which the message was sent to.
+  $message = _privatemsg_send($form_state['validate_built_message']);
+  // Format each recipient.
   $recipient_names = array();
   foreach ($form_state['validate_built_message']['recipients'] as $recipient) {
-    $recipient_names[] = theme('username', $recipient);
+    $recipient_names[] = privatemsg_recipient_format($recipient);
   }
-  if ($status !== FALSE )  {
+  if ($message !== FALSE )  {
+    _privatemsg_handle_recipients($message['mid'], $message['recipients'], $message['author']);
     drupal_set_message(t('A message has been sent to !recipients.', array('!recipients' => implode(', ', $recipient_names))));
   }
   else {
@@ -649,19 +664,71 @@ function privatemsg_user_name_autocomple
       $names[$name] = $name;
     }
   }
-  // By using user_validate_user we can ensure that names included in $names are at least logisticaly possible.
   // 2: Find the next user name suggestion.
   $fragment = array_pop($names);
   $matches = array();
   if (!empty($fragment)) {
-    $query = _privatemsg_assemble_query('autocomplete', $fragment, $names);
-    $result = db_query_range($query['query'], $fragment, 0, 10);
-    $prefix = count($names) ? implode(", ", $names) .", " : '';
-    // 3: Build proper suggestions and print.
-    while ($user = db_fetch_object($result)) {
-      $matches[$prefix . $user->name .", "] = $user->name;
+    $remaining = 10;
+    $types = privatemsg_recipient_get_types();
+    foreach ($types as $name => $type) {
+      if (isset($type['autocomplete']) && is_callable($type['autocomplete']) && privatemsg_recipient_access($name, 'write access')) {
+        $function = $type['autocomplete'];
+        $matches += $function($fragment, $names, $remaining);
+        $remaining = 10 - count($matches);
+        if ($remaining <= 0) {
+          break;
+        }
+      }
     }
   }
+  // Prefix the matches and convert them to the correct form for the
+  // autocomplete.
+  $prefix = count($names) ? implode(", ", $names) .", " : '';
+  $suggestions = array();
+  foreach ($matches as $match) {
+    $suggestions[$prefix . $match . ', '] = $match;
+  }
+
   // convert to object to prevent drupal bug, see http://drupal.org/node/175361
-  drupal_json((object)$matches);
+  drupal_json((object)$suggestions);
+}
+
+/**
+ * Batch processing function for rebuilding the index.
+ */
+function privatemsg_load_recipients($mid, $recipient, $author, &$context) {
+  // Get type information.
+  $type = privatemsg_recipient_get_type($recipient->type);
+
+  // First run, initialize sandbox.
+  if (!isset($context['sandbox']['current_offset'])) {
+    $context['sandbox']['current_offset'] = 0;
+    // Assume that the thread ids are distributed more or less equally over the
+    // whole data set. This allows us to calculate the approximate progress.
+    $max_function = $type['max'];
+    $context['sandbox']['max'] = $max_function($recipient, $author);
+  }
+
+  // Fetch the 10 next recipients.
+  $load_function = $type['generate recipients'];
+  $uids = $load_function($recipient, $author, 10, $context['sandbox']['current_offset']);
+
+  if (!empty($uids)) {
+    foreach ($uids as $uid) {
+      privatemsg_message_change_recipient($mid, $uid, 'hidden');
+    }
+
+    $context['sandbox']['current_offset'] = max($uids);
+    // Set finished based on sandbox.
+    $context['finished'] = empty($context['sandbox']['max']) ? 1 : ($context['sandbox']['current_offset'] / $context['sandbox']['max']);
+  }
+  else {
+    // If no recipients were returned, mark as finished too.
+    $context['sandbox']['finished'] = 1;
+  }
+
+  // If we are finished, mark the recipient as read.
+  if ($context['finished'] >= 1) {
+    db_query("UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND recipient = %d AND type = '%s'", PRIVATEMSG_READ, $mid, $recipient->recipient, $recipient->type);
+  }
 }
\ No newline at end of file
Index: privatemsg.test
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg.test,v
retrieving revision 1.14
diff -u -p -r1.14 privatemsg.test
--- privatemsg.test	6 Jun 2010 15:13:47 -0000	1.14
+++ privatemsg.test	19 Jun 2010 10:10:50 -0000
@@ -478,7 +478,7 @@ class PrivatemsgTestCase extends DrupalW
     $this->assertText($edit['body'], t('First message body displayed.'));
     $this->assertText($admin_edit['body'], t('New message body displayed.'));
 
-    $admin_recipient_count = db_result(db_query("SELECT COUNT(*) FROM {pm_index} WHERE uid = %d AND thread_id = %d", $admin->uid, 1));
+    $admin_recipient_count = db_result(db_query("SELECT COUNT(*) FROM {pm_index} WHERE recipient = %d AND thread_id = %d", $admin->uid, 1));
     $this->assertEqual($admin_recipient_count, 2, t('Admin is listed as recipient for every message once.'));
 
 
@@ -493,7 +493,7 @@ class PrivatemsgTestCase extends DrupalW
     $this->assertText($admin_edit['body'], t('Second response body displayed.'));
     $this->assertText($admin_edit2['body'], t('Third message body displayed.'));
 
-    $admin_recipient_count = db_result(db_query("SELECT COUNT(*) FROM {pm_index} WHERE uid = %d AND thread_id = %d", $admin->uid, 1));
+    $admin_recipient_count = db_result(db_query("SELECT COUNT(*) FROM {pm_index} WHERE recipient = %d AND thread_id = %d", $admin->uid, 1));
     $this->assertEqual($admin_recipient_count, 3, t('Admin is listed as recipient for every message once.'));
 
   }
Index: pm_block_user/pm_block_user.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/pm_block_user/pm_block_user.module,v
retrieving revision 1.7
diff -u -p -r1.7 pm_block_user.module
--- pm_block_user/pm_block_user.module	6 Jun 2010 15:13:47 -0000	1.7
+++ pm_block_user/pm_block_user.module	19 Jun 2010 10:10:51 -0000
@@ -161,35 +161,53 @@ function pm_block_user_privatemsg_block_
   $blocked = array();
   // Loop through each recipient and ensure there is no rule blocking this
   // author from sending them private messages. Use a reference, so when
-  // user_load() is needed here the array is updated, negating the need for
-  // further calls to user_load() later in the code.
-  foreach (array_keys($recipients) as $uid) {
+  // privatemsg_user_load() is needed here the array is updated, negating the
+  // need for further calls to privatemsg_user_load() later in the code.
+  foreach (array_keys($recipients) as $id) {
+    // Only recipients of type user are currently supported.
+    if (isset($recipients[$id]->type) && $recipients[$id]->type != 'user') {
+      continue;
+    }
 
     // Ensure we have a recipient user object which includes roles.
-    if (!isset($recipients[$uid]->roles)) {
-      $recipients[$uid] = user_load($uid);
+    if (!isset($recipients[$id]->roles)) {
+      $uid = str_replace('user_', '', $id);
+      $recipients[$id] = privatemsg_user_load($uid);
     }
     // Note: this is checks whether the author may send the message (see third
     // parameter). Further below is a check whether the recipient may block it.
-    if (_pm_block_user_rule_exists($author, $recipients[$uid], PM_BLOCK_USER_DISALLOW_SENDING)) {
+    if (_pm_block_user_rule_exists($author, $recipients[$id], PM_BLOCK_USER_DISALLOW_SENDING)) {
       $blocked[] = array(
-        'uid' => $uid,
-        'message' => t('Sorry, private messaging rules forbid sending messages to !name.', array('!name' => $recipients[$uid]->name)),
+        'recipient' => $id,
+        'message' => t('Sorry, private messaging rules forbid sending messages to !name.', array('!name' => $recipients[$id]->name)),
       );
     }
   }
 
-  $args = array_merge(array($author->uid), array_keys($recipients));
-  $result = db_query('SELECT recipient FROM {pm_block_user} WHERE author = %d AND recipient IN ('. db_placeholders($recipients) .') GROUP BY recipient', $args);
-  while ($row = db_fetch_array($result)) {
-    $recipient = $recipients[$row['recipient']];
+  // Only user recipients are supported for now, remove others.
+  $user_recipients = array();
+  foreach ($recipients as $key => $recipient) {
+    if ($recipient->type == 'user') {
+      $user_recipients[$recipient->recipient] = $recipient;
+    }
+  }
+
+  if (empty($user_recipients)) {
+    return $blocked;
+  }
+
+  $args = array_merge(array($author->uid), array_keys($user_recipients));
+  // Remove 'user_' prefix.
+  $result = db_query('SELECT recipient FROM {pm_block_user} WHERE author = %d AND recipient IN ('. db_placeholders(array_keys($user_recipients)) .') GROUP BY recipient', $args);
+  while ($row = db_fetch_object($result)) {
+    $recipient = $user_recipients[$row->recipient];
     // If there's a rule disallowing blocking of this message, send it anyway.
     if (_pm_block_user_rule_exists($author, $recipient, PM_BLOCK_USER_DISALLOW_BLOCKING)) {
       continue;
     }
     $blocked[] = array(
-      'uid' => $row['recipient'],
-      'message' => t('%name has chosen to not recieve any more messages from you.', array('%name' => $recipients[$row['recipient']]->name))
+      'recipient' => privatemsg_recipient_key($recipient),
+      'message' => t('%name has chosen to not recieve any more messages from you.', array('%name' => $recipient->name))
     );
   }
   return $blocked;
@@ -198,7 +216,7 @@ function pm_block_user_privatemsg_block_
 function pm_block_user_privatemsg_sql_load_alter(&$fragments, $pmid, $uid) {
   $fragments['select'][] = 'pmbu.recipient AS is_blocked';
 
-  $fragments['inner_join'][] = 'LEFT JOIN {pm_block_user} pmbu ON (pm.author = pmbu.author AND pmi.uid = pmbu.recipient)';
+  $fragments['inner_join'][] = "LEFT JOIN {pm_block_user} pmbu ON (pm.author = pmbu.author AND pmi.recipient = pmbu.recipient AND pmi.type = 'user')";
 }
 
 /**
Index: pm_block_user/pm_block_user.pages.inc
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/pm_block_user/pm_block_user.pages.inc,v
retrieving revision 1.2
diff -u -p -r1.2 pm_block_user.pages.inc
--- pm_block_user/pm_block_user.pages.inc	17 Feb 2010 11:03:30 -0000	1.2
+++ pm_block_user/pm_block_user.pages.inc	19 Jun 2010 10:10:51 -0000
@@ -110,7 +110,7 @@ function pm_block_user_list() {
   $rows = array();
   while ($row = db_fetch_object($result)) {
     $rows[] = array(
-      theme('username', user_load($row->author)),
+      theme('username', privatemsg_user_load($row->author)),
       l(t('unblock'), 'messages/block/' . $row->author, array('query' => drupal_get_destination())),
     );
   }
Index: pm_block_user/pm_block_user.test
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/pm_block_user/pm_block_user.test,v
retrieving revision 1.3
diff -u -p -r1.3 pm_block_user.test
--- pm_block_user/pm_block_user.test	6 Jun 2010 15:13:47 -0000	1.3
+++ pm_block_user/pm_block_user.test	19 Jun 2010 10:10:51 -0000
@@ -114,7 +114,6 @@ class PrivatemsgBlockUserCase extends Dr
     $this->assertText(t('Recipients: @user', array('@user' => $user3->name)), t('User1 is not displayed as recipient'));
     $edit = array('body' => $reply = $this->randomName(50));
     $this->drupalPost(NULL, $edit, t('Send message'));
-    $this->assertRaw(t('%user has chosen to not recieve any more messages from you.', array('%user' => $user1->name)), t('User 1 blocks user 2 message displayed'));
     $this->assertText(t('A message has been sent to @user.', array('@user' => $user3->name)), t('Message sent to user 3'));
 
     // Login as user1 again and check that we didn't recieve the messages.
Index: pm_email_notify/pm_email_notify.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/pm_email_notify/pm_email_notify.module,v
retrieving revision 1.9
diff -u -p -r1.9 pm_email_notify.module
--- pm_email_notify/pm_email_notify.module	6 Jun 2010 15:13:47 -0000	1.9
+++ pm_email_notify/pm_email_notify.module	19 Jun 2010 10:10:51 -0000
@@ -52,7 +52,7 @@ function _pm_email_notify_is_enabled($ui
 function pm_email_notify_privatemsg_message_insert($message) {
   foreach ($message['recipients'] as $recipient) {
     // check if recipient enabled email notifications
-    if (_pm_email_notify_is_enabled($recipient->uid)) {
+    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;
Index: privatemsg_filter/privatemsg_filter.admin.inc
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg_filter/privatemsg_filter.admin.inc,v
retrieving revision 1.7
diff -u -p -r1.7 privatemsg_filter.admin.inc
--- privatemsg_filter/privatemsg_filter.admin.inc	6 Jun 2010 15:13:47 -0000	1.7
+++ privatemsg_filter/privatemsg_filter.admin.inc	19 Jun 2010 10:10:52 -0000
@@ -249,11 +249,12 @@ function privatemsg_filter_inbox_rebuild
     // on a specific set of threads, this allows us to process the slow having
     // part on a relatively small subset of pm_index that can be selected based on
     // the thread_id index.
-    $sql = 'SELECT pmi.thread_id, pmi.uid FROM {pm_index} pmi WHERE thread_id IN (' . db_placeholders($threads) . ')  GROUP BY pmi.thread_id, pmi.uid HAVING ((SELECT pmf.author FROM pm_message pmf WHERE pmf.mid = pmi.thread_id) = pmi.uid AND COUNT(pmi.thread_id) > 1) OR (SELECT COUNT(*) FROM pm_message pmf INNER JOIN pm_index pmif ON (pmf.mid = pmif.mid) WHERE pmif.thread_id = pmi.thread_id AND pmf.author <> pmi.uid) > 0';
+    $sql = 'SELECT pmi.thread_id, pmi.recipient, pmi.type FROM {pm_index} pmi WHERE thread_id IN (' . db_placeholders($threads) . ')  GROUP BY pmi.thread_id, pmi.recipient HAVING ((SELECT pmf.author FROM pm_message pmf WHERE pmf.mid = pmi.thread_id) = pmi.recipient AND pmi.type IN ("user", "hidden") AND COUNT(pmi.thread_id) > 1) OR (SELECT COUNT(*) FROM pm_message pmf INNER JOIN pm_index pmif ON (pmf.mid = pmif.mid) WHERE pmif.thread_id = pmi.thread_id AND pmf.author <> pmi.recipient AND pmi.type IN ("user", "hidden")) > 0';
     $result = db_query($sql, $threads);
     while ($row = db_fetch_object($result)) {
       // $row is an object with uid property, so we pass it to the function as a
       // pseudo user object.
+      $row->uid = $row->recipient;
       privatemsg_filter_add_tags(array($row->thread_id), variable_get('privatemsg_filter_inbox_tag', ''), $row);
       $context['results']['count']++;
     }
Index: privatemsg_filter/privatemsg_filter.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg_filter/privatemsg_filter.module,v
retrieving revision 1.7
diff -u -p -r1.7 privatemsg_filter.module
--- privatemsg_filter/privatemsg_filter.module	6 Jun 2010 15:13:47 -0000	1.7
+++ privatemsg_filter/privatemsg_filter.module	19 Jun 2010 10:10:54 -0000
@@ -455,7 +455,7 @@ function privatemsg_filter_create_get_qu
       if (is_object($author) && isset($author->uid) && isset($author->name)) {
         $query['author'][] = $author->name;
       }
-      elseif ($author_obj = user_load($author)) {
+      elseif ($author_obj = privatemsg_user_load($author)) {
         $query['author'][] = $author_obj->name;
       }
     }
@@ -619,25 +619,25 @@ function privatemsg_filter_privatemsg_sq
   // Filter the message listing by any set tags.
   if ($filter) {
     $count = 0;
-    if (isset($filter['tags']) && !empty($filter['tags'])) {
+    if (!empty($filter['tags'])) {
       foreach ($filter['tags'] as $tag) {
-        $fragments['inner_join'][]  = "INNER JOIN {pm_tags_index} pmti$count ON (pmti$count.thread_id = pmi.thread_id AND pmti$count.uid = pmi.uid)";
+        $fragments['inner_join'][]  = "INNER JOIN {pm_tags_index} pmti$count ON (pmti$count.thread_id = pmi.thread_id AND pmti$count.uid = pmi.recipient AND pmi.type IN ('user', 'hidden'))";
         $fragments['where'][]       = "pmti$count.tag_id = %d";
         $fragments['query_args']['where'][]   = $tag;
         $count++;
       }
     }
 
-    if (isset($filter['author']) && !empty($filter['author'])) {
+    if (!empty($filter['author'])) {
       foreach ($filter['author'] as $author) {
         $fragments['inner_join'][]  = "INNER JOIN {pm_index} pmi$count ON (pmi$count.mid = pm.mid)";
-        $fragments['where'][]       = "pmi$count.uid = %d";
+        $fragments['where'][]       = "pmi$count.recipient = %d AND type = 'user'";
         $fragments['query_args']['where'][]   = $author->uid;
         $count++;
       }
     }
 
-    if (isset($filter['search']) && !empty($filter['search'])) {
+    if (!empty($filter['search'])) {
       if (variable_get('privatemsg_filter_searchbody', FALSE)) {
         $fragments['where'][]       = "pm.subject LIKE '%s' OR pm.body LIKE '%s'";
         $fragments['query_args']['where'][]    = '%%'. $filter['search'] .'%%';
@@ -784,10 +784,10 @@ function privatemsg_filter_privatemsg_sq
   // @todo: Check if these results can be grouped to avoid unecessary loops.
   if (arg(1) == 'filter') {
     // JOIN on index entries where the to be selected user is a recipient.
-    $fragments['inner_join'][] = 'INNER JOIN {pm_index} pip ON pip.uid = u.uid';
+    $fragments['inner_join'][] = "INNER JOIN {pm_index} pip ON pip.recipient = u.uid AND pip.type = 'user'";
     // JOIN on rows where the current user is the recipient and that have the
     // same mid as those above.
-    $fragments['inner_join'][] = 'INNER JOIN {pm_index} piu ON piu.uid = %d AND pip.mid = piu.mid';
+    $fragments['inner_join'][] = "INNER JOIN {pm_index} piu ON piu.recipient = u.uid AND piu.type = 'user' AND pip.mid = piu.mid";
     $fragments['query_args']['join'][] = $user->uid;
   }
 }
@@ -906,9 +906,22 @@ function privatemsg_filter_user($op, &$e
   }
 }
 
+/**
+ * Implements hook_privatemsg_message_insert().
+ */
 function privatemsg_filter_privatemsg_message_insert($message) {
   foreach ($message['recipients'] as $recipient) {
-    privatemsg_filter_add_tags(array($message['thread_id']), variable_get('privatemsg_filter_inbox_tag', ''), $recipient);
+    if ($recipient->type == 'user' || $recipient->type == 'hidden') {
+      privatemsg_filter_add_tags(array($message['thread_id']), variable_get('privatemsg_filter_inbox_tag', ''), $recipient);
+    }
+  }
+}
+/**
+ * Implements hook_privatemsg_message_recipient_changed().
+ */
+function privatemsg_filter_privatemsg_message_recipient_changed($mid, $thread_id, $recipient, $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));
   }
 }
 
Index: privatemsg_roles/privatemsg_roles.info
===================================================================
RCS file: privatemsg_roles/privatemsg_roles.info
diff -N privatemsg_roles/privatemsg_roles.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ privatemsg_roles/privatemsg_roles.info	19 Jun 2010 10:10:54 -0000
@@ -0,0 +1,6 @@
+; $Id: pm_block_user.info,v 1.3 2009/11/30 17:37:15 berdir Exp $
+name = Privatemsg roles
+description = Allows to send message to all members of a role.
+package = Mail
+core = 6.x
+dependencies[] = privatemsg
Index: privatemsg_roles/privatemsg_roles.module
===================================================================
RCS file: privatemsg_roles/privatemsg_roles.module
diff -N privatemsg_roles/privatemsg_roles.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ privatemsg_roles/privatemsg_roles.module	19 Jun 2010 10:10:54 -0000
@@ -0,0 +1,152 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Allows to send messages to all members of a role.
+ */
+
+/**
+ * Implements hook_perm().
+ */
+function privatemsg_roles_perm() {
+  return array(
+    'write privatemsg to roles', 'view roles recipients',
+  );
+}
+
+function privatemsg_roles_theme() {
+  return array(
+    'privatemsg_roles_format' => array(
+      'arguments' => array('role' => NULL, 'options' => array()),
+    ),
+  );
+}
+
+/**
+ * Implements hook_privatemsg_recipient_types_info().
+ */
+function privatemsg_roles_privatemsg_recipient_type_info() {
+  return array(
+    'role' => array(
+      'name' => t('Role'),
+      'description' => t('Enter the name of a role to write a message to all users which have that role. Example: authenticated user.'),
+      'load' => 'privatemsg_roles_load_multiple',
+      'format' => 'privatemsg_roles_format',
+      'autocomplete' => 'privatemsg_roles_autocomplete',
+      'generate recipients' => 'privatemsg_roles_load_recipients',
+      'max' => 'privatemsg_roles_count_recipients',
+      'write access' => 'write privatemsg to roles',
+      'view access' => 'view roles recipients',
+    ),
+  );
+}
+
+/**
+ * Implements callback_load_multiple().
+ */
+function privatemsg_roles_load_multiple($rids) {
+  $result = db_query("SELECT name, rid AS recipient FROM {role} WHERE rid IN (" . db_placeholders($rids) . ")", $rids);
+
+  $roles = array();
+  while ($role = db_fetch_object($result)) {
+    $role->type = 'role';
+    $roles[privatemsg_recipient_key($role)] = $role;
+  }
+  return $roles;
+}
+
+/**
+ * Implements callback_format().
+ */
+function theme_privatemsg_roles_format($role, $options = array()) {
+  return t('%role (role)', array('%role' => $role->name));
+}
+
+/**
+ * Implements callback_load_recipients().
+ *
+ * Loads users with a specific role.
+ */
+function privatemsg_roles_load_recipients($recipient, $author, $limit, $offset) {
+  $recipients = array();
+  $rid = isset($recipient->recipient) ? $recipient->recipient : $recipient->rid;
+  if ($rid == DRUPAL_AUTHENTICATED_RID) {
+    $result = db_query('SELECT uid FROM {users} WHERE uid > %d ORDER BY uid ASC LIMIT %d', $offset, $limit);
+  }
+  else {
+    $result = db_query('SELECT uid FROM {users_roles} WHERE rid = %d AND uid > %d ORDER BY uid ASC LIMIT %d', $rid, $offset, $limit);
+  }
+
+  while ($row = db_fetch_object($result)) {
+    $recipients[] = $row->uid;
+  }
+  return $recipients;
+}
+
+/**
+ * Implements callback_count_recipients()
+ *
+ * Return the number of users which have a given role.
+ */
+function privatemsg_roles_count_recipients($recipient, $author) {
+  $rid = isset($recipient->recipient) ? $recipient->recipient : $recipient->rid;
+  if ($rid == DRUPAL_AUTHENTICATED_RID) {
+    return db_result(db_query('SELECT MAX(uid) FROM {users}'));
+  }
+  else {
+    return db_result(db_query('SELECT MAX(uid) FROM {users_roles} WHERE rid = %d', $rid));
+  }
+}
+
+/**
+ * Implements callback_privatemsg_recipient_autocomplete().
+ */
+function privatemsg_roles_autocomplete($fragment, $names, $limit) {
+  $query = _privatemsg_assemble_query(array('autocomplete_roles', 'privatemsg_roles'), $fragment, $names);
+  $result = db_query_range($query['query'], $fragment, 0, $limit);
+  // 3: Build proper suggestions and print.
+  $matches = array();
+  while ($role = db_fetch_object($result)) {
+    $matches[] = $role->name;
+  }
+  return $matches;
+}
+
+/**
+ * Implements hook_privatemsg_name_lookup().
+ */
+function privatemsg_roles_privatemsg_name_lookup($string) {
+  // Remove optonal role specifier.
+  $string = str_replace('[role]', '', $string);
+  $result = db_query("SELECT *, rid AS recipient FROM {role} WHERE name = '%s'", trim($string));
+  if ($role = db_fetch_object($result)) {
+    $role->type = 'role';
+    return $role;
+  }
+}
+
+/**
+ * Query definition to search for username autocomplete suggestions.
+ *
+ * @param $fragments
+ *   Query fragments array.
+ * @param $search
+ *   Which search string is currently searched for.
+ * @param $names
+ *   Array of names not to be used as suggestions.
+ */
+function privatemsg_roles_sql_autocomplete_roles(&$fragments, $search, $names) {
+  $fragments['primary_table'] = '{role} role';
+  $fragments['select'][] = 'role.name';
+
+  // Escape the % to get it through the placeholder replacement.
+  $fragments['where'][] = "role.name LIKE '%s'";
+  $fragments['query_args']['where'][] = $search .'%%';
+  if (!empty($names)) {
+    // If there are already names selected, exclude them from the suggestions.
+    $fragments['where'][] = "role.name NOT IN (". db_placeholders($names, 'text') .")";
+    $fragments['query_args']['where'] += $names;
+  }
+  $fragments['order_by'][] = 'role.name ASC';
+}
Index: privatemsg_roles/privatemsg_roles.test
===================================================================
RCS file: privatemsg_roles/privatemsg_roles.test
diff -N privatemsg_roles/privatemsg_roles.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ privatemsg_roles/privatemsg_roles.test	19 Jun 2010 10:10:54 -0000
@@ -0,0 +1,280 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * This file contains tests for the privatemsg roles module
+ */
+
+/**
+ * Test cases for the privatemsg_roles module.
+ */
+class PrivatemsgRolesTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Privatemsg Roles functionality',
+      'description' => 'Tests sending messages to roles',
+      'group' => 'Privatemsg',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('privatemsg', 'privatemsg_roles', 'privatemsg_filter', 'pm_block_user');
+  }
+
+  function testSendMessagetoRoleAPI() {
+    $admin = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg', 'write privatemsg to roles'));
+    $user1 = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg'));
+    $user2 = $this->drupalCreateUser();
+
+    // Add role of user 1 to user 2;
+    $edit = array('roles' => $user2->roles + $user1->roles);
+    user_save($user2, $edit);
+
+    $recipient = (object)array('recipient' => 4, 'type' => 'role', 'name' => $user1->roles[4]);
+    privatemsg_new_thread(array($recipient), $subject = $this->randomName(10), $body = $this->randomName(50), array('author' => $admin));
+
+    $this->drupalLogin($user2);
+    $this->drupalGet('messages');
+    $this->assertRaw($subject . '</a> <span class="marker">new</span>', t('Message is displayed as new'));
+
+    $this->clickLink($subject);
+    $this->assertText($body, t('Thread starter body displayed.'));
+  }
+
+    function testSendMessagetoRoleCron() {
+    $admin = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg', 'write privatemsg to roles'));
+    $user1 = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg'));
+
+    variable_set('privatemsg_recipient_small_threshold', 20);
+    variable_set('privatemgs_cron_recipient_per_run', 20);
+
+    // Create 26 users (more than are allowed to be process directly).
+    $users = array();
+    for ($i = 0; $i < 25; $i++) {
+      $users[$i] = $this->drupalCreateUser();
+      // Add role of user 1 to user 2;
+      $edit = array('roles' => $users[$i]->roles + $user1->roles);
+      user_save($users[$i], $edit);
+    }
+
+    $recipient = (object)array('recipient' => 4, 'type' => 'role', 'name' => $user1->roles[4]);
+    privatemsg_new_thread(array($recipient), $subject = $this->randomName(10), $body = $this->randomName(50), array('author' => $admin));
+
+    // Run cron.
+    privatemsg_cron();
+
+    // Test a few recipients to see if they recieved the message.
+    foreach (array(0, 5, 18) as $uid) {
+      $this->drupalLogin($users[$uid]);
+      $this->drupalGet('messages');
+      $this->assertRaw($subject . '</a> <span class="marker">new</span>', t('Message is displayed as new'));
+
+      $this->clickLink($subject);
+      $this->assertText($body, t('Thread starter body displayed.'));
+    }
+
+    // Make sure that user 20 has not yet recieved the message.
+    $this->drupalLogin($users[20]);
+    $this->drupalGet('messages');
+    $this->assertNoText($subject, t('Message is not yet displayed for this user'));
+
+    // Run cron again.
+    privatemsg_cron();
+
+    // Test that the remaining recipients do now see the message too.
+    foreach (array(20, 24) as $uid) {
+      $this->drupalLogin($users[$uid]);
+      $this->drupalGet('messages');
+      $this->assertRaw($subject . '</a> <span class="marker">new</span>', t('Message is displayed as new'));
+
+      $this->clickLink($subject);
+      $this->assertText($body, t('Thread starter body displayed.'));
+    }
+  }
+
+  function testSendMessagetoRoleBatch() {
+    $admin = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg', 'write privatemsg to roles'));
+    $user1 = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg'));
+
+    variable_set('privatemsg_recipient_small_threshold', 20);
+    // Create 25 users (more than are allowed to be process directly).
+    $users = array();
+    for ($i = 0; $i < 25; $i++) {
+      $users[$i] = $this->drupalCreateUser();
+      // Add role of user 1 to user 2;
+      $edit = array('roles' => $users[$i]->roles + $user1->roles);
+      user_save($users[$i], $edit);
+    }
+    $this->drupalLogin($admin);
+
+    // Send a message to the role shared by all users.
+    $edit = array(
+      'recipient' => $user1->roles[4] . '[role]',
+      'subject' => $this->randomName(10),
+      'body' => $this->randomName(50),
+    );
+    $this->drupalPost('messages/new', $edit, t('Send message'));
+    $this->assertText(t('A message has been sent to @role (role).', array('@role' => $user1->roles[4])), t('Confirmation message displayed'));
+
+    // Test a few recipients to see if they all recieved the message.
+    foreach (array(0, 5, 15, 23) as $uid) {
+      $this->drupalLogin($users[$uid]);
+      $this->drupalGet('messages');
+      $this->assertRaw($edit['subject'] . '</a> <span class="marker">new</span>', t('Message is displayed as new'));
+
+      $this->clickLink($edit['subject']);
+      $this->assertText($edit['body'], t('Thread starter body displayed.'));
+    }
+  }
+
+  function testPermission() {
+    $user1 = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg'));
+    $user2 = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg'));
+    $this->drupalLogin($user1);
+
+    // Send a message to the role of user 1 and 2.
+    $edit = array(
+      'recipient' => $user2->roles[4] . '[role]',
+      'subject' => $this->randomName(10),
+      'body' => $this->randomName(50),
+    );
+    $this->drupalPost('messages/new', $edit, t('Send message'));
+    $this->assertText(t('Disallowed to send a message without at least one valid recipient'), t('User is not allowed to write message to roles'));
+  }
+
+  function testSendMessageToRole() {
+    $admin = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg', 'write privatemsg to roles'));
+    $user1 = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg'));
+    $user2 = $this->drupalCreateUser(array('view roles recipients'));
+
+    // Add role of user 1 to user 2;
+    $edit = array('roles' => $user2->roles + $user1->roles);
+    user_save($user2, $edit);
+    $this->drupalLogin($admin);
+
+    // Send a message to the role of user 1 and 2.
+    $edit = array(
+      'recipient' => $user1->roles[4] . '[role]',
+      'subject' => $this->randomName(10),
+      'body' => $this->randomName(50),
+    );
+    $this->drupalPost('messages/new', $edit, t('Send message'));
+    $this->assertText(t('A message has been sent to @role (role).', array('@role' => $user1->roles[4])), t('Confirmation message displayed'));
+
+    // Log in as user1 and check that the message is listed, is marked as new
+    // and can be marked as read.
+    $this->drupalLogin($user1);
+    $this->drupalGet('messages');
+    $this->assertRaw($edit['subject'] . '</a> <span class="marker">new</span>', t('Message is displayed as new'));
+
+    $this->clickLink($edit['subject']);
+    $this->assertText($edit['body'], t('Thread starter body displayed.'));
+
+    // Make sure that the user doesn't see the role recipient.
+    $this->assertNoText(t('@role (role)', array('@role' => $user1->roles[4])));
+
+    // Reply to the message, only admin should see this.
+    $reply1 = array(
+      'body' => $this->randomName(50),
+    );
+    $this->drupalPost(NULL, $reply1, t('Send message'));
+
+    $this->drupalGet('messages');
+    $this->assertNoRaw($edit['subject'] . '</a> <span class="marker">new</span>', t('Message is displayed as read'));
+
+    // Login as admin again and check if he sees the response.
+    $this->drupalLogin($admin);
+    $this->drupalGet('messages');
+    $this->assertRaw($edit['subject'] . '</a> <span class="marker">new</span>', t('Message is displayed as new'));
+    $this->clickLink($edit['subject']);
+
+    $this->assertText($reply1['body'], t('Reply1 is displayed'));
+
+    // Reply to the message, all recipients should see this.
+    $reply2 = array(
+      'body' => $this->randomName(50),
+    );
+    $this->drupalPost(NULL, $reply2, t('Send message'));
+
+    // Log in as user2 and check that he only sees the messages from admin.
+    $this->drupalLogin($user2);
+    $this->drupalGet('messages');
+    $this->assertRaw($edit['subject'] . '</a> <span class="marker">new</span>', t('Message is displayed as new'));
+    $this->clickLink($edit['subject']);
+    $this->assertText(t('@role (role)', array('@role' => $user1->roles[4])));
+
+    $this->assertText($edit['body'], t('Thread starter body is displayed'));
+    $this->assertNoText($reply1['body'], t('Reply1 is not displayed'));
+    $this->assertText($reply2['body'], t('Reply2 is displayed'));
+  }
+
+  function testSendMessageToRoleBlocked() {
+    $admin = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg', 'write privatemsg to roles'));
+    $user1 = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg'));
+    $user2 = $this->drupalCreateUser();
+
+    // Add role of user 1 to user 2;
+    $edit = array('roles' => $user2->roles + $user1->roles);
+    user_save($user2, $edit);
+
+    // Block admin as user 2.
+    $this->drupalLogin($user2);
+    $this->drupalPost('messages/blocked', array('name' => $admin->name), t('Block user'));
+
+    $this->drupalLogin($admin);
+
+    // Send a message to the role of user 1 and 2.
+    $edit = array(
+      'recipient' => $user1->roles[4] . '[role]',
+      'subject' => $this->randomName(10),
+      'body' => $this->randomName(50),
+    );
+    $this->drupalPost('messages/new', $edit, t('Send message'));
+    $this->assertText(t('A message has been sent to @role (role).', array('@role' => $user1->roles[4])), t('Confirmation message displayed'));
+
+    // Log in as user1 and check that the message is listed, is marked as new
+    // and can be marked as read.
+    $this->drupalLogin($user1);
+    $this->drupalGet('messages');
+    $this->assertRaw($edit['subject'] . '</a> <span class="marker">new</span>', t('Message is displayed as new'));
+
+    $this->clickLink($edit['subject']);
+    $this->assertText($edit['body'], t('Thread starter body displayed.'));
+
+    // Make sure that the user doesn't see the role recipient.
+    $this->assertNoText(t('@role (role)', array('@role' => $user1->roles[4])));
+
+    // Log in as user2 and make sure that he didn't received the messages
+    // as he blocks admin.
+    $this->drupalLogin($user2);
+    $this->drupalGet('messages');
+    $this->assertNoText($edit['subject'], t('Message is not displayed'));
+  }
+
+  function testNewUser() {
+    $admin = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg', 'write privatemsg to roles'));
+    $user1 = $this->drupalCreateUser(array('read privatemsg', 'write privatemsg'));
+
+    // Send a message to the role of user 1.
+    $this->drupalLogin($admin);
+    $edit = array(
+      'recipient' => $user1->roles[4] . '[role]',
+      'subject' => $this->randomName(10),
+      'body' => $this->randomName(50),
+    );
+    $this->drupalPost('messages/new', $edit, t('Send message'));
+    $this->assertText(t('A message has been sent to @role (role).', array('@role' => $user1->roles[4])), t('Confirmation message displayed'));
+
+    // Add user 2 with the same role now. The user should not see the message.
+    $user2 = $this->drupalCreateUser();
+
+    // Add role of user 1 to user 2;
+    $edit_roles = array('roles' => $user2->roles + $user1->roles);
+    user_save($user2, $edit_roles);
+    $this->drupalLogin($user2);
+
+    $this->drupalGet('messages');
+    $this->assertNoText($edit['subject'], t('Newly added user does not see the old message sent to his role'));
+  }
+}
Index: privatemsg_rules/privatemsg_rules.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg_rules/privatemsg_rules.module,v
retrieving revision 1.3
diff -u -p -r1.3 privatemsg_rules.module
--- privatemsg_rules/privatemsg_rules.module	6 Jun 2010 15:13:47 -0000	1.3
+++ privatemsg_rules/privatemsg_rules.module	19 Jun 2010 10:10:54 -0000
@@ -11,7 +11,8 @@
  */
 function privatemsg_rules_privatemsg_message_insert($message) {
   foreach ($message['recipients'] as $recipient) {
-    rules_invoke_event('privatemsg_insert', $message['subject'], $message['body'], $message['author'], $recipient, $message['mid'], $message['thread_id']);
+    if ($recipient->type == 'user') {
+      rules_invoke_event('privatemsg_insert', $message['subject'], $message['body'], $message['author'], $recipient, $message['mid'], $message['thread_id']);
+    }
   }
-}
-?>
+}
\ No newline at end of file
Index: views/views_handler_field_privatemsg_link.inc
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/views/views_handler_field_privatemsg_link.inc,v
retrieving revision 1.5
diff -u -p -r1.5 views_handler_field_privatemsg_link.inc
--- views/views_handler_field_privatemsg_link.inc	6 Jun 2010 15:13:47 -0000	1.5
+++ views/views_handler_field_privatemsg_link.inc	19 Jun 2010 10:10:56 -0000
@@ -150,7 +150,7 @@ class views_handler_field_privatemsg_lin
     }
 
     $data = '';
-    if (($recipient = user_load($uid)) && ($url = privatemsg_get_link(array($recipient), NULL, $subject))) {
+    if (($recipient = privatemsg_user_load($uid)) && ($url = privatemsg_get_link(array($recipient), NULL, $subject))) {
       $data = l($text, $url, $options);
     }
     return $data;
