From c10073a9d00439808e9f5c7983046a15dbea89d7 Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Fri, 17 Jun 2011 17:53:42 +0200
Subject: [PATCH] Issue #744374 by Berdir: Added pm_thread table to improve
 thread list queries.

---
 privatemsg.install                           |  168 +++++++++++++++++
 privatemsg.module                            |  261 ++++++++++++++++++++++---
 privatemsg_filter/privatemsg_filter.module   |    7 +-
 privatemsg_forward/privatemsg_forward.module |    1 +
 4 files changed, 401 insertions(+), 36 deletions(-)

diff --git a/privatemsg.install b/privatemsg.install
index 1c2b1cb..535f53b 100644
--- a/privatemsg.install
+++ b/privatemsg.install
@@ -61,6 +61,75 @@ function privatemsg_schema() {
     ),
   );
 
+  $schema['pm_thread'] = array(
+    'description'       => '{pm_thread} holds global information about a thread.',
+    'fields'        => array(
+      'thread_id'    => array(
+        'description'   => 'Private message thread ID.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'subject'    => array(
+        'description'   => 'Subject of the thread.',
+        'type'          => 'varchar',
+        'length'        => 255,
+        'not null'      => TRUE,
+      ),
+    ),
+    'primary key' => array('thread_id'),
+  );
+
+  $schema['pm_thread_user'] = array(
+    'description'       => '{pm_thread_user} holds information about a thread for a single recipient.',
+    'fields'        => array(
+      'thread_id'    => array(
+        'description'   => 'Private message thread ID.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'uid'    => array(
+        'description'   => 'User ID of the recipient',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'started'    => array(
+        'description'   => 'When the first message of the thread was received.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'last_updated'    => array(
+        'description'   => 'When the last message of that thread was received.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'count'    => array(
+        'description'   => 'Number of messages in a thread.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'unread_count'    => array(
+        'description'   => 'Number of unread messages in a thread.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'last_sent'       => array(
+        'description'   => 'Indicates when this recipient sent the last message.',
+        'type'          => 'int',
+        'not null'      => FALSE,
+        'unsigned'      => TRUE,
+      ),
+    ),
+    'primary key' => array('thread_id', 'uid'),
+  );
+
+
   $schema['pm_message'] = array(
     'description'       => '{pm_messages} holds the message information',
     'fields'        => array(
@@ -745,6 +814,105 @@ function privatemsg_update_6206() {
   return $ret;
 }
 
+/**
+ * Add {pm_thread} and {pm_thread_recipient} table.
+ */
+function privatemsg_update_6207() {
+  $schema = array();
+  $schema['pm_thread'] = array(
+   'description'       => '{pm_thread} holds global information about a thread.',
+    'fields'        => array(
+      'thread_id'    => array(
+        'description'   => 'Private message thread ID.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'subject'    => array(
+        'description'   => 'Subject of the thread.',
+        'type'          => 'varchar',
+        'length'        => 255,
+        'not null'      => TRUE,
+      ),
+    ),
+    'primary key' => array('thread_id'),
+  );
+
+  $schema['pm_thread_user'] = array(
+    'description'       => '{pm_thread_user} holds information about a thread for a single recipient.',
+    'fields'        => array(
+      'thread_id'    => array(
+        'description'   => 'Private message thread ID.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'uid'    => array(
+        'description'   => 'User ID of the recipient',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'started'    => array(
+        'description'   => 'When the first message of the thread was received.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'last_updated'    => array(
+        'description'   => 'When the last message of that thread was received.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'count'    => array(
+        'description'   => 'Number of messages in a thread.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'unread_count'    => array(
+        'description'   => 'Number of unread messages in a thread.',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'last_sent'       => array(
+        'description'   => 'Indicates when this recipient sent the last message.',
+        'type'          => 'int',
+        'not null'      => FALSE,
+        'unsigned'      => TRUE,
+      ),
+    ),
+    'primary key' => array('thread_id', 'uid'),
+  );
+
+  $ret = array();
+  db_create_table($ret, 'pm_thread', $schema['pm_thread']);
+  db_create_table($ret, 'pm_thread_user', $schema['pm_thread_user']);
+  return $ret;
+}
+
+/**
+ * Populate {pm_thread} and {pm_thread_user}.
+ */
+function privatemsg_update_6208() {
+  // Make it easy to re-run the update.
+  db_query('DELETE FROM {pm_thread}');
+  db_query('DELETE FROM {pm_thread_user}');
+
+  // Populate {pm_thread}
+  $insert_sql = 'INSERT INTO {pm_thread} (thread_id, subject)';
+  $select_sql = 'SELECT pmi.thread_id, pm.subject FROM {pm_index} pmi INNER JOIN {pm_message} pm ON pmi.thread_id = pm.mid GROUP BY pmi.thread_id, pm.subject';
+  db_query($insert_sql . ' ' . $select_sql);
+
+  // Populate {pm_thread_user}.
+  $insert_sql_user = 'INSERT INTO {pm_thread_user} (thread_id, uid, started, last_updated, count, unread_count, last_sent)';
+  $select_sql_user = "SELECT pmi.thread_id, pmi.recipient, MIN(pm.timestamp), MAX(pm.timestamp), (SELECT COUNT(*) FROM {pm_index} pmic WHERE pmic.thread_id = pmi.thread_id AND pmic.recipient = pmi.recipient AND deleted = 0), SUM(pmi.is_new), (SELECT MAX(timestamp) FROM {pm_message} pms INNER JOIN {pm_index} pmis ON pmis.mid = pms.mid WHERE pmi.thread_id = pmis.thread_id and pms.author = pmi.recipient) FROM {pm_index} pmi INNER JOIN {pm_message} pm ON pm.mid = pmi.mid WHERE pmi.type IN ('user', 'hidden') GROUP BY pmi.thread_id, pmi.recipient";
+  db_query($insert_sql_user . ' ' . $select_sql_user);
+
+  return array();
+}
 
 /**
  * Checks if an index exists in the given table.
diff --git a/privatemsg.module b/privatemsg.module
index 81cb47f..4936ded 100644
--- a/privatemsg.module
+++ b/privatemsg.module
@@ -777,6 +777,20 @@ function privatemsg_message_change_status($pmid, $status, $account = NULL) {
   $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);
 
+  // Update pm_thread_user table.
+  $query = _privatemsg_thread_update()
+    ->by(array(
+      'uid' => $account->uid,
+      'mid' => $pmid,
+    ));
+  if ($status == PRIVATEMSG_READ) {
+    $query->decrease('unread_count');
+  }
+  else {
+    $query->increase('unread_count');
+  }
+  $query->execute();
+
   // Allows modules to respond to the status change.
   module_invoke_all('privatemsg_message_status_changed', $pmid, $status, $account);
 }
@@ -996,25 +1010,27 @@ function privatemsg_privatemsg_name_lookup($string) {
  */
 
 function privatemsg_sql_list(&$fragments, $account, $argument = 'list') {
-  $fragments['primary_table'] = '{pm_message} pm';
+  $fragments['primary_table'] = '{pm_thread_user} ptu';
+
+  $fragments['inner_join'][]  = 'INNER JOIN {pm_thread} pt ON pt.thread_id = ptu.thread_id';
 
   // Load enabled columns.
   $fields = privatemsg_get_enabled_headers();
 
   // Required columns.
-  $fragments['select'][]      = 'pmi.thread_id';
+  $fragments['select'][]      = 'ptu.thread_id';
   // We have to use MIN as the subject might not be the same in some threads.
   // MIN() does not have a useful meaning except that it helps to correctly
   // aggregate the thread on PostgreSQL.
-  $fragments['select'][]      = 'MIN(pm.subject) as subject';
-  $fragments['select'][]      = 'MAX(pm.timestamp) as last_updated';
+  $fragments['select'][]      = 'pt.subject';
+  $fragments['select'][]      = 'ptu.last_updated';
   // We use SUM so that we can count the number of unread messages.
-  $fragments['select'][]      = 'SUM(pmi.is_new) as is_new';
+  $fragments['select'][]      = 'ptu.unread_count as is_new';
 
   // Select number of messages in the thread if the count is
   // set to be displayed.
   if (in_array('count', $fields)) {
-    $fragments['select'][]      = 'COUNT(distinct pmi.mid) as count';
+    $fragments['select'][]      = 'ptu.count';
   }
   if (in_array('participants', $fields)) {
     // Query for a string with uid's, for example "1,6,7".
@@ -1023,26 +1039,25 @@ function privatemsg_sql_list(&$fragments, $account, $argument = 'list') {
       // PostgreSQL does not know GROUP_CONCAT, so a subquery is required.
       $fragments['select'][]      = "array_to_string(array(SELECT DISTINCT pmia.type || '_' || textin(int4out(pmia.recipient))
                                                             FROM {pm_index} pmia
-                                                            WHERE pmia.type <> 'hidden' AND pmia.thread_id = pmi.thread_id AND pmia.recipient <> %d), ',') AS participants";
+                                                            WHERE pmia.type <> 'hidden' AND pmia.thread_id = ptu.thread_id AND pmia.recipient <> %d), ',') AS participants";
     }
     else {
       $fragments['select'][]      = "(SELECT GROUP_CONCAT(DISTINCT CONCAT(pmia.type, '_', pmia.recipient) SEPARATOR ',')
                                                             FROM {pm_index} pmia
-                                                            WHERE pmia.type = 'user' AND pmia.thread_id = pmi.thread_id AND pmia.recipient <> %d) AS participants";
+                                                            WHERE pmia.type = 'user' AND pmia.thread_id = ptu.thread_id AND pmia.recipient <> %d) AS participants";
     }
     $fragments['query_args']['select'][] = $account->uid;
   }
   if (in_array('thread_started', $fields)) {
-    $fragments['select'][]      = 'MIN(pm.timestamp) as thread_started';
+    $fragments['select'][]      = 'ptu.started AS thread_started';
   }
 
-  $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.recipient = %d AND pmi.type IN ('user', 'hidden')";
+  $fragments['where'][]      = "ptu.uid = %d";
   $fragments['query_args']['where'][] = $account->uid;
-  $fragments['where'][]       = 'pmi.deleted = 0';
-  $fragments['group_by'][]    = 'pmi.thread_id';
+
+  // Only show threads with messages.
+  $fragments['where'][]      = "ptu.count > 0";
 
   // tablesort_sql() generates a ORDER BY string. However, the "ORDER BY " part
   // is not needed and added by the query builder. Discard the first 9
@@ -1173,14 +1188,13 @@ function privatemsg_sql_participants(&$fragments, $thread_id, $account = NULL) {
  *   User object for which the messages are being counted.
  */
 function privatemsg_sql_unread_count(&$fragments, $account) {
-  $fragments['primary_table'] = '{pm_index} pmi';
+  $fragments['primary_table'] = '{pm_thread_user} ptu';
 
-  $fragments['select'][]      = 'COUNT(DISTINCT thread_id) as unread_count';
+  $fragments['select'][]      = 'COUNT(thread_id) as unread_count';
 
   // Only count new messages that have not been deleted.
-  $fragments['where'][]       = 'pmi.deleted = 0';
-  $fragments['where'][]       = 'pmi.is_new = 1';
-  $fragments['where'][]       = "pmi.recipient = %d AND pmi.type IN ('user', 'hidden')";
+  $fragments['where'][]       = 'ptu.uid = %d';
+  $fragments['where'][]       = 'ptu.unread_count > 0';
   $fragments['query_args']['where'][]  = $account->uid;
 }
 
@@ -1495,14 +1509,32 @@ function privatemsg_message_change_delete($pmid, $delete, $account = NULL) {
     $delete_value = time();
   }
 
+  $conditions = array(
+    'mid' => $pmid,
+  );
+
   if ($account) {
     db_query("UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND recipient = %d AND type IN ('user', 'hidden')", $delete_value, $pmid, $account->uid);
+
+    // Add specific uid to thread update command.
+    $conditions['uid'] = $account->uid;
   }
   else {
     // Mark deleted for all users.
     db_query('UPDATE {pm_index} SET deleted = %d WHERE mid = %d', $delete_value, $pmid);
   }
 
+  // Update pm_thread_user table.
+  $query = _privatemsg_thread_update()
+    ->by($conditions);
+  if ($delete) {
+    $query->decrease('count');
+  }
+  else {
+    $query->increase('count');
+  }
+  $query->execute();
+
   // Allow modules to respond to the deleted changes.
   module_invoke_all('privatemsg_message_status_delete', $pmid, $delete, $account);
 }
@@ -1786,7 +1818,7 @@ function _privatemsg_send($message) {
     }
   }
 
-  // 1) Save the message body first.
+  // Save the message body and subject.
   $args = array();
   $args[] = $message['subject'];
   $args[] = $message['author']->uid;
@@ -1801,9 +1833,12 @@ function _privatemsg_send($message) {
   // Thread ID is the same as the mid if it's the first message in the thread.
   if (!isset($message['thread_id'])) {
     $message['thread_id'] = $mid;
+
+    // Create an entry for this thread in {pm_thread}
+    db_query("INSERT INTO {pm_thread} (thread_id, subject) VALUES(%d, '%s')", $message['thread_id'], $message['subject']);
   }
 
-  // 2) Save message to recipients.
+  // 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->recipient, $recipient->type, 1) ) {
@@ -1815,12 +1850,45 @@ function _privatemsg_send($message) {
 
   // We only want to add the author to the pm_index table, if the message has
   // not been sent directly to him.
+  $author_is_recipient = TRUE;
   if (!isset($message['recipients']['user_' . $message['author']->uid])) {
+    $author_is_recipient = FALSE;
     if (!db_query($index_sql, $mid, $message['thread_id'], $message['author']->uid, 'user', 0)) {
       return FALSE;
     }
   }
 
+  // If this is a new thread, initalize {pm_thread_user}
+  if ($message['thread_id'] == $message['mid']) {
+    _privatemsg_thread_refresh($message['thread_id']);
+  }
+  else {
+    // If this is a reply, update count, count_unread, last_updated for all
+    // recipients.
+    _privatemsg_thread_update()
+      ->by(array(
+        'mid' => $message['mid'],
+        'thread_id' => $message['thread_id'],
+      ))
+      ->increase('count')
+      ->increase('unread_count')
+      ->set('last_updated', $message['timestamp'])
+      ->execute();
+
+      // Update last_sent for author for new threads and replies.
+      $query = _privatemsg_thread_update()
+        ->by(array(
+          'thread_id' => $message['thread_id'],
+          'uid' => $message['author']->uid,
+        ))
+        ->set('last_sent', $message['timestamp']);
+      // If the author is not a recipient, set unread_count to 0 too.
+      if (!$author_is_recipient) {
+        $query->set('unread_count', 0);
+      }
+      $query->execute();
+  }
+
   module_invoke_all('privatemsg_message_insert', $message);
 
   // If we reached here that means we were successful at writing all messages to db.
@@ -1828,6 +1896,53 @@ function _privatemsg_send($message) {
 }
 
 /**
+ * Updates the {pm_thread_recipient} table for all recipients.
+ */
+function _privatemsg_thread_refresh($thread_id, $uid = NULL) {
+  if ($uid) {
+    db_query('DELETE FROM {pm_thread_user} WHERE thread_id = %d AND uid = %d', $thread_id, $uid);
+  }
+  else {
+    db_query('DELETE FROM {pm_thread_user} WHERE thread_id = %d', $thread_id);
+  }
+
+  $condition = '';
+  if ($uid) {
+    $condition = ' AND pmi.recipient = %d';
+  }
+  $insert_sql = 'INSERT INTO {pm_thread_user} (thread_id, uid, started, last_updated, count, unread_count, last_sent)';
+  $select_sql = "SELECT pmi.thread_id,
+                        pmi.recipient,
+                        MIN(pm.timestamp),
+                        MAX(pm.timestamp),
+                        (SELECT COUNT(*) FROM {pm_index} pmic WHERE pmic.thread_id = pmi.thread_id AND pmic.recipient = pmi.recipient AND deleted = 0),
+                        SUM(pmi.is_new),
+                        (SELECT MAX(timestamp) FROM {pm_message} pms INNER JOIN {pm_index} pmis ON pmis.mid = pms.mid WHERE pmi.thread_id = pmis.thread_id and pms.author = pmi.recipient)
+                 FROM {pm_index} pmi
+                 INNER JOIN {pm_message} pm ON pm.mid = pmi.mid
+                 WHERE pmi.thread_id = %d AND pmi.type IN ('user', 'hidden')$condition
+                 GROUP BY pmi.thread_id, pmi.recipient";
+
+
+  if ($uid) {
+    db_query($insert_sql . ' ' . $select_sql, $thread_id, $uid);
+  }
+  else {
+    db_query($insert_sql . ' ' . $select_sql, $thread_id);
+  }
+}
+
+/**
+ * Build a query to update {pm_thread_user}.
+ *
+ * @return PrivatemsgUpdateQueryBuilder
+ */
+function _privatemsg_thread_update() {
+  return new PrivatemsgUpdateQueryBuilder();
+}
+
+
+/**
  * Returns a link to send message form for a specific users.
  *
  * Contains permission checks of author/recipient, blocking and
@@ -2114,6 +2229,7 @@ function _privatemsg_assemble_query($query) {
       _db_query_callback($query_args_count, TRUE);
       $count = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $count);
     }
+    //drupal_set_message(str_replace(array('{', '}'), '', $query));
     return array('query' => $query, 'count' => $count);
   }
   return FALSE;
@@ -2143,17 +2259,27 @@ function privatemsg_thread_change_status($threads, $status, $account = NULL) {
 
   // Record which messages will change status.
   $changed = array();
-  $result = db_query("SELECT mid FROM {pm_index} WHERE is_new <> %d AND recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', $params);
+  $result = db_query("SELECT mid, thread_id FROM {pm_index} WHERE is_new <> %d AND recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', $params);
   while($row = db_fetch_object($result)) {
-    $changed[] = $row->mid;
+    $changed[$row->thread_id][] = $row->mid;
   }
-
   // Update the status of the threads.
   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);
 
-  // Allow modules to respond to the status changes.
-  foreach ($changed as $mid) {
-    module_invoke_all('privatemsg_message_status_changed', $mid, $status, $account);
+  foreach ($changed as $thread_id => $mids) {
+    // Allow modules to respond to the status changes.
+    foreach ($mids as $mid) {
+      module_invoke_all('privatemsg_message_status_changed', $mid, $status, $account);
+    }
+    // Update pm_thread_user table.
+    _privatemsg_thread_update()
+      ->by(array(
+      'condition' => array(
+        'thread_id' => $thread_id,
+        'uid' => $account->uid,
+      )))
+      ->set($status == PRIVATEMSG_READ ? 0 : count($mids))
+      ->execute();
   }
 
   if ($status == PRIVATEMSG_UNREAD) {
@@ -2255,9 +2381,9 @@ function privatemsg_thread_change_delete($threads, $delete, $account = NULL) {
   // Record which messages will be deleted.
   $changed = array();
   $cond = $delete ? '=' : '>';
-  $result = db_query("SELECT mid FROM {pm_index} WHERE deleted $cond 0 AND recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', array_merge(array($account->uid), $threads));
+  $result = db_query("SELECT mid, thread_id FROM {pm_index} WHERE deleted $cond 0 AND recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', array_merge(array($account->uid), $threads));
   while($row = db_fetch_object($result)) {
-    $changed[] = $row->mid;
+    $changed[$row->thread_id][] = $row->mid;
   }
 
   // Merge status and uid with the threads list. array_merge() will not overwrite/ignore thread_id 1.
@@ -2269,9 +2395,19 @@ function privatemsg_thread_change_delete($threads, $delete, $account = NULL) {
     // Update the status of the threads.
   db_query("UPDATE {pm_index} SET deleted = %d WHERE recipient = %d and type IN ('user', 'hidden') AND thread_id IN (" . db_placeholders($threads) . ')', $params);
 
-  // Allow modules to respond to the deleted changes.
-  foreach ($changed as $mid) {
-    module_invoke_all('privatemsg_message_status_delete', $mid, $delete, $account);
+  foreach ($changed as $thread_id => $mids) {
+    // Allow modules to respond to the delete changes.
+    foreach ($mids as $mid) {
+      module_invoke_all('privatemsg_message_delete changed', $mid, $delete, $account);
+    }
+    // Update pm_thread_user table.
+    _privatemsg_thread_update()
+      ->by(array(
+        'thread_id' => $thread_id,
+        'uid' => $account->uid,
+      ))
+      ->set('count', $delete ? 0 : count($mids))
+      ->execute();
   }
 
   if ($delete) {
@@ -2625,6 +2761,8 @@ function privatemsg_message_change_recipient($mid, $uid, $type = 'user', $add =
     }
     db_query($delete_sql, $mid, $thread_id, $uid, $type);
   }
+  // Refresh the thread for that user only.
+  _privatemsg_thread_refresh($thread_id, $uid);
   module_invoke_all('privatemsg_message_recipient_changed', $mid, $thread_id, $uid, $type, $add);
 }
 
@@ -3054,3 +3192,62 @@ function privatemsg_get_default_setting_ids($account = NULL) {
     'global' => array(0),
   );
 }
+
+class PrivatemsgUpdateQueryBuilder {
+  var $conditions = array();
+  var $condition_args = array();
+  var $updates = array();
+  var $update_args = array();
+
+  function set($field, $value) {
+    $this->updates[] = db_escape_table($field) . ' = %d';
+    $this->update_args[] = $value;
+    return $this;
+  }
+
+  function increase($field, $amount = 1) {
+    $this->updates[] = db_escape_table($field) . ' = ' . db_escape_table($field) . ' + %d';
+    $this->update_args[] = $amount;
+    return $this;
+  }
+
+  function decrease($field, $amount = 1) {
+    $this->updates[] = db_escape_table($field) . ' = IF(' . db_escape_table($field) . ' > 0, ' . db_escape_table($field) . ' - %d, 0)';
+    $this->update_args[] = $amount;
+    return $this;
+  }
+
+  function by($conditions) {
+    foreach ($conditions as $field => $value) {
+      switch ($field) {
+        case 'thread_id':
+        case 'uid':
+          $this->conditions[] = $field . ' = %d';
+          $this->condition_args[] = $value;
+
+          break;
+        case 'mid':
+          // mid is not an actual existing field. Instead, first select the
+          // thread_id if not already given.
+          if (empty($conditions['thread_id'])) {
+            $thread_id = db_result(db_query_range('SELECT thread_id FROM {pm_index} WHERE mid = %d', $value, 0, 1));
+            $this->conditions[] = 'thread_id = %d';
+            $this->condition_args[] = $thread_id;
+          }
+          // If there is no explicit uid definition, apply for all recipients of this message.
+          if (empty($conditions['uid'])) {
+            $this->conditions[] = "uid IN (SELECT pmi.recipient FROM {pm_index} pmi WHERE pmi.mid = %d AND pmi.type IN ('user', 'hidden'))";
+            $this->condition_args[] = $value;
+          }
+          break;
+      }
+    }
+    return $this;
+  }
+
+  function execute() {
+    $sql = 'UPDATE {pm_thread_user} SET ' . implode(', ', $this->updates) . ' WHERE (' . implode(') AND (', $this->conditions) . ')';
+    $args = array_merge($this->update_args, $this->condition_args);
+    db_query($sql, $args);
+  }
+}
diff --git a/privatemsg_filter/privatemsg_filter.module b/privatemsg_filter/privatemsg_filter.module
index 652acf2..720bc1f 100644
--- a/privatemsg_filter/privatemsg_filter.module
+++ b/privatemsg_filter/privatemsg_filter.module
@@ -630,8 +630,7 @@ function privatemsg_filter_privatemsg_sql_list_alter(&$fragments, $account, $arg
 
   // Check if its a filtered view.
   if ($argument == 'sent') {
-    $fragments['where'][]       = "pm.author = %d";
-    $fragments['query_args']['where'][]   = $account->uid;
+    $fragments['where'][]       = "ptu.last_sent > 0";
   }
   $filter = privatemsg_filter_get_filter($account);
   if ($argument == 'inbox') {
@@ -643,7 +642,7 @@ function privatemsg_filter_privatemsg_sql_list_alter(&$fragments, $account, $arg
     $count = 0;
     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.recipient AND pmi.type IN ('user', 'hidden'))";
+        $fragments['inner_join'][]  = "INNER JOIN {pm_tags_index} pmti$count ON (pmti$count.thread_id = ptu.thread_id AND pmti$count.uid = ptu.uid)";
         $fragments['where'][]       = "pmti$count.tag_id = %d";
         $fragments['query_args']['where'][]   = $tag;
         $count++;
@@ -652,7 +651,7 @@ function privatemsg_filter_privatemsg_sql_list_alter(&$fragments, $account, $arg
 
     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['inner_join'][]  = "INNER JOIN {pm_index} pmi$count ON (pmi$count.thread_id = ptu.thread_id)";
         $fragments['where'][]       = "pmi$count.recipient = %d AND pmi$count.type = 'user'";
         $fragments['query_args']['where'][]   = $author->uid;
         $count++;
diff --git a/privatemsg_forward/privatemsg_forward.module b/privatemsg_forward/privatemsg_forward.module
index c3629e4..448caea 100644
--- a/privatemsg_forward/privatemsg_forward.module
+++ b/privatemsg_forward/privatemsg_forward.module
@@ -134,6 +134,7 @@ function privatemsg_forward_form_delete_submit($form, &$form_state) {
   global $user;
   if ($form_state['values']['remove']) {
     db_query("DELETE FROM {pm_index} WHERE thread_id = %d AND recipient = %d AND type IN ('hidden', 'user')", $form_state['values']['thread_id'], $user->uid);
+    _privatemsg_thread_refresh($form_state['values']['thread_id'], $user->uid);
 
     // Display message and redirect to messages if user has removed himself.
     $form_state['redirect'] = 'messages';
-- 
1.7.5.4

