? privatemsg_filter/privatemsg_filter.module_changes
Index: privatemsg.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg.module,v
retrieving revision 1.70.2.30.2.91.2.25
diff -u -p -r1.70.2.30.2.91.2.25 privatemsg.module
--- privatemsg.module	22 Feb 2009 21:21:08 -0000	1.70.2.30.2.91.2.25
+++ privatemsg.module	2 Mar 2009 17:45:43 -0000
@@ -6,7 +6,8 @@
  * Allows users to send private messages to other users.
  */
 
-define('THREADED', 0);
+define('PRIVATEMSG_READ', 0);
+define('PRIVATEMSG_UNREAD', 1);
 
 /**
  * Implementation of hook_perm().
@@ -25,6 +26,9 @@ function _privatemsg_generate_user_array
 
   $users = array_slice(explode(',', $userstring), -4);
   $participants = array();
+  if (empty($users[0])) {
+    return array();
+  }
   foreach ($users as $user) {
     if (isset($user_cache[$user])) {
       $participants[$user] = $user_cache[$user];
@@ -77,28 +81,23 @@ function privatemsg_menu() {
   $items['messages'] = array(
     'title'            => 'Messages',
     'title callback'  => 'privatemsg_title_callback',
-    'page callback'    => 'privatemsg_list',
+    'page callback'    => 'drupal_get_form',
+    'page arguments'   => array('privatemsg_list', 'list'),
     'access callback'  => 'privatemsg_user_access',
     'type'             => MENU_NORMAL_ITEM,
   );
   $items['messages/inbox'] = array(
     'title'            => 'All messages',
-    'page callback'    => 'privatemsg_list',
+    'page callback'    => 'drupal_get_form',
+    'page arguments'   => array('privatemsg_list', 'list'),
     'access callback'  => 'privatemsg_user_access',
     'type'             => MENU_DEFAULT_LOCAL_TASK,
     'weight'           => -10,
   );
-  $items['messages/mark-read'] = array(
-    'title'            => 'Mark all as read',
-    'page callback'    => 'privatemsg_set_new_status',
-    'page arguments'   => array(NULL, NULL, 0, TRUE),
-    'access callback'  => 'privatemsg_unread_count',
-    'type'             => MENU_NORMAL_ITEM,
-    'weight'           => -9,
-  );
   $items['messages/sent'] = array(
     'title'            => 'Sent messages',
-    'page callback'    => 'privatemsg_list',
+    'page callback'    => 'drupal_get_form',
+    'page arguments'   => array('privatemsg_list', 'list_sent'),
     'access callback'  => 'privatemsg_user_access',
     'type'             => MENU_LOCAL_TASK,
     'weight'           => -5,
@@ -153,6 +152,13 @@ function privatemsg_menu() {
     'type'             => MENU_DEFAULT_LOCAL_TASK,
     'weight'           => -10,
   );
+  $items['messages/undo/action'] = array(
+    'title'            => 'Private messages',
+    'description'      => 'Undo last thread action',
+    'page callback'    => 'privatemsg_undo_action',
+    'access arguments' => array('read privatemsg'),
+    'type'             => MENU_CALLBACK,
+  );
   return $items;
 }
 
@@ -256,6 +262,27 @@ function private_message_settings() {
     '#default_value' => variable_get('privatemsg_display_loginmessage', TRUE),
     '#description' => t('This option can safely be disabled if the "New message indication" block is used instead.'),
   );
+  $form['privatemsg_listing'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Configure listings'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+
+  $fields = array(
+    'author' => t('Author'),
+    'last_updated' => t('Last updated'),
+    'thread_started' => t('Started'),
+    'count' => t('Answers'),
+  );
+  $form['privatemsg_listing']['privatemsg_display_fields'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Configure fields'),
+    '#description' => t('Select which columns/fields should be displayed in the message listings.'),
+    '#options' => $fields,
+    '#default_value' => variable_get('privatemsg_display_fields', array('author', 'last_updated')),
+  );
+
   $form['#submit'][] = 'private_message_settings_submit';
   return system_settings_form($form);
 }
@@ -282,10 +309,26 @@ function privatemsg_theme() {
       'arguments'        => array('recipients' => NULL),
       'template'         => 'privatemsg-between',
     ),
-    'privatemsg_message_row'  => array(
-      'arguments'             => array('row'),
+    'privatemsg_list'    => array(
+      'file'                  => 'privatemsg.theme.inc',
+      'path'                  => drupal_get_path('module', 'privatemsg') .'/',
+      'arguments'        => array('form'),
+    ),
+    'privatemsg_list_header'  => array(
+      'file'                  => 'privatemsg.theme.inc',
+      'path'                  => drupal_get_path('module', 'privatemsg') .'/',
+      'pattern'               => 'privatemsg_list_header__',
+      'arguments'             => array(),
+    ),
+    'privatemsg_list_field'   => array(
+      'file'                  => 'privatemsg.theme.inc',
+      'path'                  => drupal_get_path('module', 'privatemsg') .'/',
+      'pattern'               => 'privatemsg_list_field__',
+      'arguments'             => array('thread'),
     ),
     'privatemsg_new_block'  => array(
+      'file'                  => 'privatemsg.theme.inc',
+      'path'                  => drupal_get_path('module', 'privatemsg') .'/',
       'arguments'             => array('count'),
     ),
   );
@@ -323,14 +366,18 @@ function privatemsg_preprocess_privatems
 /**
  * List messages.
  *
- * @param $uid - user id for whom to load messages.
+ * @param array  $form_state Form state array
+ * @param string $query_name Which query id should be used to load the messages
+ * @param int    $uid        User id messages of another user should be displayed
+ *
+ * @return array Form array
  */
-function privatemsg_list($uid = NULL) {
+function privatemsg_list(&$form_state, $query_name = 'list', $uid = NULL) {
   global $user;
 
   // Setting default behavior...
   $account = $user;
-  if (is_int($uid) && $uid != $user->uid) {
+  if ((int)$uid > 0 && $uid != $user->uid) {
     // Trying to view someone else's messages...
     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');
@@ -342,107 +389,47 @@ function privatemsg_list($uid = NULL) {
   }
   // By this point we have figured out for which user we are listing messages and now it is safe to use $account->uid in the listing query.
 
-  $content = array();
-  $content['list']['content'] = privatemsg_list_messages($account);
-  $content['list']['#weight'] = 0;
-
-  drupal_alter('privatemsg_list_messages', $content, $account);
-
-  uasort($content, 'element_sort');
-
-  $expand = '';
-  foreach ($content as $element) {
-    $expand .= $element['content'];
-  }
-  return $expand;
-}
-
-/**
- * Display the list of messages for a given user, called by privatemsg_privatemsg_alter
- */
-function privatemsg_list_messages($account) {
-  switch (arg(1)) {
-    case 'sent':
-      $query = _privatemsg_assemble_query('list_sent', $account);
-      break;
-    case 'inbox':
-    default:
-      $query = _privatemsg_assemble_query('list', $account);
-  }
+  $query = _privatemsg_assemble_query($query_name, $account);
   $result = pager_query($query['query'], variable_get('privatemsg_per_page', 25), 0, $query['count']);
 
-  $rows = array();
-  while ($thread = db_fetch_array($result)) {
-    $rows[] = theme('privatemsg_message_row', $thread);
+  $threads = array();
+  $form['#data'] = array();
+  while ($row = db_fetch_array($result)) {
+    $form['#data'][$row['thread_id']] = $row;
+    $form['#rows'][$row['thread_id']] = _privatemsg_list_thread($row);
+    $threads[$row['thread_id']] = '';
   }
-
-  $content = '';
-  if (!empty($rows)) {
-    $head = array();
-    foreach (array_keys($rows[0]) as $index) {
-      $head[$index] =  array('data' => $index);
-      if ($index == t('Last updated') || $index == t('Subject')) {
-        $head[$index] += array('field' => $index, 'sort' => 'desc');
-      }
-    }
-
-    $content = theme('table', $head, $rows);
-    $content .= theme('pager');
-    drupal_add_css(drupal_get_path('module', 'privatemsg') .'/styles/privatemsg-list.css');
+  if (empty($form['#data'])) {
+    $keys = array('subject', 'author', 'last_updated');
   }
   else {
-    drupal_set_message(t('No messages to display.'));
+    // Load the keys of the first row in data, we don't know the key
+    $keys = array_keys($form['#data'][key($form['#data'])]);
+    $form['actions'] = _privatemsg_action_form();
   }
 
-  return $content;
-}
-
-function theme_privatemsg_new_block($count) {
-  $text = format_plural($count, 'You have a new message, click here to read it',
-                        'You have @count new messages, click here to read them',
-                        array('@count' => $count));
-
-  return l($text, 'messages', array('attributes' => array('id' => 'privatemsg-new-link')));
+  $form['#headers'] = _privatemsg_list_headers(!empty($form['#data']), $keys);
+  $form['threads'] = array('#type' => 'checkboxes', '#options' => $threads);
+  $form['pager'] = array('#value' => theme('pager'), '#weight' => 20);
+  $form['#theme'] = 'privatemsg_list';
+  $form['#account'] = $account;
+  return $form;
 }
 
 /**
- * Theme a row of messages
+ * Changes the read/new status of a single message.
  *
+ * @param int    $status  Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD
+ * @param int    $pmid    Message id, mid
+ * @param object $account User object, defaults to the current user
  */
-function theme_privatemsg_message_row($row) {
-  $themed_row = array();
-
-  $unread = '';
-  if (isset($row['is_new']) && $row['is_new'] ) {
-    $unread = ' privatemsg-unread';
-  }
-  $themed_row[t('Subject')] = '<span class="privatemsg-list-subject'. $unread .'">'. l($row['subject'], 'messages/view/'. $row['thread_id']) .'</span>';
-  if (isset($row['author']) && $row['author']) {
-    $authors = _privatemsg_generate_user_array($row['author']);
-    $themed_row[t('Authors')] = '<span class="privatemsg-list-from'.  $unread .'">'. _privatemsg_format_participants($authors, 3, TRUE) .'</span>';
-  }
-  if (array_key_exists('recipient', $row)) {
-    $themed_row[t('Recipients')] = '';
-    if (!empty($row['recipient'])) {
-      $recipients = _privatemsg_generate_user_array($row['recipient']);
-      $themed_row[t('Recipients')] = '<span class="privatemsg-list-to'.  $unread .'">'. _privatemsg_format_participants($recipients, 3, TRUE) .'</span>';
-    }
-  }
-//  drupal_set_message('<pre>'. print_r($row, 1) . '</pre>');
-  if ($row['last_updated']) {
-    $themed_row[t('Last updated')] = '<span class="privatemsg-list-date'.  $unread .'">'. format_date($row['last_updated'], 'small') .'</span>';
+function privatemsg_message_change_status($pmid, $status, $account = NULL) {
+  if (!$account) {
+    global $user;
+    $account = $user;
   }
-  return $themed_row;
-}
-
-/**
- * API function
- *
- * Sets a single message as read for a user.
- */
-function privatemsg_mark_as_read($pmid, $account) {
-  $query = "UPDATE {pm_index} SET is_new = 0 WHERE mid = %d AND recipient = %d";
-  db_query($query, $pmid, $account->uid);
+  $query = "UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND uid = %d";
+  db_query($query, $status, $pmid, $account->uid);
 }
 
 /**
@@ -463,45 +450,6 @@ function privatemsg_unread_count($accoun
   return $counts[$account->uid];
 }
 
-/**
- * API function
- *
- * Change status of one or ALL messages to read / unread.
- */
-function privatemsg_set_new_status($account = NULL, $mid = NULL, $new = 0, $verbose = FALSE) {
-  if (!$account || 0 == $account->uid) {
-    global $user;
-    $account = $user;
-  }
-  $query = "UPDATE {pm_index} SET is_new = %d WHERE uid = %d AND is_new = %d";
-  $arg[] = $new;
-  $arg[] = $account->uid;
-  $arg[] = ($new == 0) ? 1 : 0;
-  if ($mid) {
-    $query .= " AND mid = %d";
-    $arg[] = $mid;
-  }
-
-  $result = db_query($query, $arg) ;
-
-  if ($verbose) {
-    if ($result) {
-      if ($new == 1) {
-        $status = t('unread');
-      }
-      else {
-        $status = t('read');
-      }
-      $total_marked = db_affected_rows();
-      drupal_set_message(format_plural($total_marked, "1 message marked as %status", '@count messages marked as %status', array('%status' => $status)));
-    }
-    else {
-      drupal_set_message(t('An error has occured, please contact the site administrator.'), 'error');
-    }
-    return '';
-  }
-}
-
 function privatemsg_view($thread_id) {
   global $user;
   $output = '';
@@ -516,13 +464,13 @@ function privatemsg_view($thread_id) {
   $content['participants']['#weight'] = -5;
 
   // Load the messages.
-  $query = _privatemsg_assemble_query('messages', $thread_id, $user);
+  $query = _privatemsg_assemble_query('messages', array($thread_id), $user);
   $conversation = db_query($query['query']);
   $message_count = 0;
   while ($result = db_fetch_array($conversation)) {
     $pmid = $result['mid'];
     $message = _privatemsg_load($pmid, $user);
-    privatemsg_set_new_status($user, $pmid);
+    privatemsg_message_change_status($pmid, PRIVATEMSG_READ, $user);
     $message['author'] = user_load($message['author']);
     // Some tasks only need to be done once - on the first message of a thread.
     if ($message_count == 0) {
@@ -776,37 +724,40 @@ function privatemsg_sql_list_sent(&$frag
   $fragments['group_by'][]  = 'pmi.thread_id';
   $fragments['group_by'][]  = 'pm.subject';
 
-  $order = 'last_updated';
-  $sort = 'desc';
-  if (isset($_GET['order'])) {
-    switch ($_GET['order']) {
-      case 'subject':
-        $order = 'subject';
-        break;
-    }
-    $sort = isset($_GET['sort']) && ($_GET['sort'] == 'asc' || $_GET['sort'] == 'desc') ? $_GET['sort'] : 'desc';
-  }
-  $fragments['order_by'][]  = $order .' '. $sort;
+  // We can use tablesort_sql now, but we don't need the ORDER BY part of the query.
+  $order_by = drupal_substr(tablesort_sql(_privatemsg_list_headers( FALSE, array('subject', 'recipient', 'last_updated'))), 9);
+  $fragments['order_by'][]  = $order_by;
 }
 
 function privatemsg_sql_list(&$fragments, $account) {
   $fragments['primary_table'] = '{pm_message} pm';
 
+  $fields = array_filter(variable_get('privatemsg_display_fields', array('author', 'last_updated')));
+
   $fragments['select'][]      = 'pmi.thread_id';
   $fragments['select'][]      = 'MIN(pm.subject) as subject';
-  $fragments['select'][]      = 'COUNT(pmi.thread_id) as count';
+  if (in_array('count', $fields)) {
+    $fragments['select'][]      = 'COUNT(pmi.thread_id) as count';
+  }
 
-  if ($GLOBALS['db_type'] == 'pgsql') {
-    $fragments['select'][]      = "array_to_string(array(SELECT DISTINCT textin(int4out(pma.author))
-                                                         FROM {pm_message} pma
-                                                         INNER JOIN pm_index pmia ON pma.mid = pmia.mid
-                                                         WHERE pmia.thread_id = pmi.thread_id),',') AS author";
+  if (in_array('author', $fields)) {
+    if ($GLOBALS['db_type'] == 'pgsql') {
+      $fragments['select'][]      = "array_to_string(array(SELECT DISTINCT textin(int4out(pma.author))
+                                                           FROM {pm_message} pma
+                                                           INNER JOIN pm_index pmia ON pma.mid = pmia.mid
+                                                           WHERE pmia.thread_id = pmi.thread_id),',') AS author";
+    }
+    else {
+      $fragments['select'][]      = 'GROUP_CONCAT(DISTINCT author SEPARATOR ",") as author';
+    }
   }
-  else {
-    $fragments['select'][]      = 'GROUP_CONCAT(DISTINCT author SEPARATOR ",") as author';
+  if (in_array('last_updated', $fields)) {
+    $fragments['select'][]      = 'MAX(pm.timestamp) as last_updated';
+  }
+
+  if (in_array('thread_started', $fields)) {
+    $fragments['select'][]      = 'MIN(pm.timestamp) as thread_started';
   }
-  $fragments['select'][]      = 'MAX(pm.timestamp) as last_updated';
-  $fragments['select'][]      = 'MIN(pm.timestamp) as thread_started';
   $fragments['select'][]      = 'MAX(pmi.is_new) as is_new';
   // pm_index needs to be the first join.
   $fragments['inner_join'][]  = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid';
@@ -814,19 +765,10 @@ function privatemsg_sql_list(&$fragments
   $fragments['query_args'][]  = $account->uid;
   $fragments['where'][]       = 'pmi.deleted = 0';
   $fragments['group_by'][]    = 'pmi.thread_id';
-  $order = 'is_new';
-  $sort = 'desc';
-  if (isset($_GET['order'])) {
-    switch ($_GET['order']) {
-      case t('Subject'): // Allows translated headers to be sorted
-        $order = 'subject';
-        break;
-      default:
-    }
-    $sort = isset($_GET['sort']) && ($_GET['sort'] == 'asc' || $_GET['sort'] == 'desc') ? $_GET['sort'] : 'desc';
-  }
-  $fragments['order_by'][]  = $order .' '. $sort .', last_updated DESC';
 
+  // We can use tablesort_sql now, but we don't need the ORDER BY part of the query.
+  $order_by = drupal_substr(tablesort_sql(_privatemsg_list_headers( FALSE, array('subject') + $fields), 'is_new DESC,'), 9);
+  $fragments['order_by'][]  = $order_by;
 }
 
 function privatemsg_sql_load(&$fragments, $pmid, $account) {
@@ -847,15 +789,17 @@ function privatemsg_sql_load(&$fragments
   $fragments['query_args'][]  = $account->uid;
 }
 
-function privatemsg_sql_messages(&$fragments, $thread_id, $account) {
+function privatemsg_sql_messages(&$fragments, $threads, $account, $load_all = FALSE) {
   $fragments['primary_table'] = '{pm_index} pmi';
 
   $fragments['select'][]      = 'DISTINCT(pmi.mid) as mid';
-  $fragments['where'][]       = 'pmi.thread_id = %d';
-  $fragments['query_args'][]  = $thread_id;
+  $fragments['where'][]       = 'pmi.thread_id IN ('. db_placeholders($threads) .')';
+  $fragments['query_args']   += $threads;
   $fragments['where'][]       = 'pmi.uid = %d';
   $fragments['query_args'][]  = $account->uid;
-  $fragments['where'][]       = 'pmi.deleted = 0';
+  if (!$load_all) {
+    $fragments['where'][]       = 'pmi.deleted = 0';
+  }
   $fragments['order_by'][]    = 'pmi.mid ASC';
 }
 
@@ -1103,14 +1047,14 @@ function privatemsg_delete($form_state, 
   );
 }
 
-function privatemsg_message_delete($pmid, $account = NULL) {
+function privatemsg_message_change_delete($pmid, $delete, $account = NULL) {
   if (is_null($account)) {
     global $user;
     $account = drupal_clone($user);
   }
   $message = _privatemsg_load($pmid, $account);
 
-  db_query('UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND uid = %d', 1, $pmid, $account->uid);
+  db_query('UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND uid = %d', $delete, $pmid, $account->uid);
 
   $result = db_query("SELECT MIN(deleted) AS deleted_by_all FROM {pm_index} WHERE mid = %d", $pmid);
   $deleted = db_fetch_array($result);
@@ -1124,10 +1068,8 @@ function privatemsg_message_delete($pmid
 }
 
 function privatemsg_delete_submit($form, &$form_state) {
-  $deleted = 1;
-
   if ($form_state['values']['confirm']) {
-    privatemsg_message_delete($form_state['values']['pmid']);
+    privatemsg_message_change_delete($form_state['values']['pmid'], 1);
     drupal_set_message(t('Message has been deleted'));
   }
   $form_state['redirect'] = 'messages';
@@ -1453,3 +1395,226 @@ function _privatemsg_assemble_query($que
   }
   return FALSE;
 }
+
+function _privatemsg_action_form() {
+  $form = array(
+      '#type'        => 'fieldset',
+      '#title'       => t('Actions'),
+      '#prefix'      => '<div class="container-inline">',
+      '#suffix'      => '</div>',
+      '#collapsible' => TRUE,
+      '#collapsed'   => FALSE,
+      '#weight'      => 15,
+  );
+  $form['delete'] = array(
+      '#type'   => 'submit',
+      '#value'  => t('Delete'),
+      '#key'    => 'delete',
+  );
+  $options = array(0 => t('More actions..'));
+  foreach (module_invoke_all('privatemsg_thread_operations') as $operation => $array) {
+    if (isset($array['label'])) {
+      $options[$operation] = $array['label'];
+    }
+  }
+  $form['operation'] = array(
+      '#type'          => 'select',
+      '#options'       => $options,
+      '#default_value' => 0,
+      '#attributes'    => array('onchange' => "$('#edit-submit').click()"),
+  );
+  $form['submit'] = array(
+      '#type'       => 'submit',
+      '#value'      => t('Execute'),
+      '#submit'     => array('privatemsg_list_submit'),
+      '#attributes' => array('class' => 'privatemsg-action-button'),
+  );
+  // JS for hiding the execute button(s)
+  drupal_add_js(drupal_get_path('module', 'privatemsg') .'/privatemsg-list.js');
+  return $form;
+}
+
+/**
+ * Marks one or multiple threads as (un)read
+ *
+ * @param array|int $threads Array with id's, a single int value will be converted to int
+ * @param int       $status  Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD, sets the new status
+ * @param object    $account User object for which the threads should be deleted, defaults to the current user
+ */
+function privatemsg_thread_change_status($threads, $status, $account = NULL) {
+  if (!is_array($threads)) {
+    $threads = array($threads);
+  }
+
+  if (empty($account)) {
+    global $user;
+    $account = drupal_clone($user);
+  }
+
+  $params = 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);
+
+  if ($status == PRIVATEMSG_UNREAD) {
+    drupal_set_message(t('Marked %count threads as unread.', array('%count' => count($threads))));
+  }
+  else {
+    drupal_set_message(t('Marked %count threads as read.', array('%count' => count($threads))));
+  }
+}
+
+function _privatemsg_list_headers($has_posts, $keys) {
+  $select_header = $has_posts ? theme('table_select_header_cell') : '';
+  $select_header['#weight'] = -50;
+
+  // theme() doesn't include the theme file for patterns
+  include_once drupal_get_path('module', 'privatemsg') .'/privatemsg.theme.inc';
+
+  $header = array($select_header);
+  foreach ($keys as $key) {
+    if ($return = theme(array('privatemsg_list_header__'. $key, 'privatemsg_list_header'))) {
+      $header[$key] = $return;
+    }
+  }
+  return $header;
+}
+
+function _privatemsg_list_thread($thread) {
+  $row = array('data' => array());
+
+  if (!empty($thread['is_new'])) {
+    $row['class'] = 'privatemsg-unread';
+  }
+  foreach ($thread as $key => $data) {
+    if ($return = theme(array('privatemsg_list_field__'. $key, 'privatemsg_list_field'), $thread)) {
+      $row['data'][$key] = $return;
+    }
+  }
+  return $row;
+}
+
+/**
+ * Menu callback for messages/undo/action.
+ *
+ * This function will test if a undo callback is stored in SESSION and execute it.
+ */
+function privatemsg_undo_action() {
+  if (isset($_SESSION['privatemsg']['undo callback']) && is_array($_SESSION['privatemsg']['undo callback'])) {
+    $undo = $_SESSION['privatemsg']['undo callback'];
+    if (isset($undo['function']) && isset($undo['args'])) {
+      call_user_func_array($undo['function'], $undo['args']);
+    }
+    drupal_goto();
+  }
+}
+
+/**
+ * Process privatemsg_list form submissions.
+ *
+ * Execute the chosen action on the selected messages. This function is
+ * a copy of node_admin_nodes_submit
+ */
+function privatemsg_list_submit($form, &$form_state) {
+  $operations = module_invoke_all('privatemsg_thread_operations');
+
+  $operation = array('callback' => 0);
+  if (isset($form_state['values']['operation']) && isset($operations[$form_state['values']['operation']])) {
+    $operation = $operations[$form_state['values']['operation']];
+  }
+  // Load all keys where the value is the current op.
+  $keys = array_keys($form_state['values'], $form_state['values']['op']);
+
+  // The first one is op itself, we need to use the second.
+  if (isset($keys[1]) && isset($operations[$keys[1]])) {
+    $operation = $operations[$form_state['clicked_button']['#key']];
+  }
+
+  // Filter out unchecked nodes.
+  $threads = array_filter($form_state['values']['threads']);
+  if (!empty($operation['callback'])) {
+    // Add in callback arguments if present.
+    if (isset($operation['callback arguments'])) {
+      $args = array_merge(array($threads), $operation['callback arguments']);
+    }
+    else {
+      $args = array($threads);
+    }
+    call_user_func_array($operation['callback'], $args);
+
+    if (isset($operation['undo callback']) && $undo_function = $operation['undo callback']) {
+      // Add in callback arguments if present.
+      if (isset($operation['undo callback arguments'])) {
+        $undo_args = array_merge(array($threads), $operation['undo callback arguments']);
+      }
+      else {
+        $undo_args = array($threads);
+      }
+      $_SESSION['privatemsg']['undo callback'] = array('function' => $undo_function, 'args' => $undo_args);
+      $undo = l(t('undone'), 'messages/undo/action', array('query' => drupal_get_destination()));
+
+      drupal_set_message(t('The previous action can be !undo.', array('!undo' => $undo)));
+    }
+  }
+}
+
+/**
+ * Delete or restore one or multiple threads
+ *
+ * @param array|int $threads Array with id's, a single int value will be converted to int
+ * @param int       $delete  Indiciates if the threads should be deleted or restored. 1 = delete, 0 => restore
+ * @param object    $account User object for which the threads should be deleted, defaults to the current user
+ */
+function privatemsg_thread_change_delete($threads, $delete, $account = NULL) {
+  if (!is_array($threads)) {
+    $threads = array($threads);
+  }
+
+  if (empty($account)) {
+    global $user;
+    $account = drupal_clone($user);
+  }
+
+  $params = array($delete, $account->uid) + $threads;
+
+  $query = _privatemsg_assemble_query('messages', $threads, $account, TRUE);
+  $result = db_query($query['query']);
+
+  while ($row = db_fetch_array($result)) {
+    privatemsg_message_change_delete($row['mid'], $delete, $account);
+  }
+
+  if ($delete) {
+    drupal_set_message(t('Deleted %count threads.', array('%count' => count($threads))));
+  }
+  else {
+    drupal_set_message(t('Restored %count threads.', array('%count' => count($threads))));
+  }
+}
+
+/**
+ * Implementation of hook_privatemsg_thread_operations().
+ */
+function privatemsg_privatemsg_thread_operations() {
+  $operations = array(
+    'mark as read' => array(
+      'label' => t('Mark as read'),
+      'callback' => 'privatemsg_thread_change_status',
+      'callback arguments' => array('status' => PRIVATEMSG_READ),
+      'undo callback' => 'privatemsg_thread_change_status',
+      'undo callback arguments' => array('status' => PRIVATEMSG_UNREAD),
+    ),
+    'mark as unread' => array(
+      'label' => t('Mark as unread'),
+      'callback' => 'privatemsg_thread_change_status',
+      'callback arguments' => array('status' => PRIVATEMSG_UNREAD),
+      'undo callback' => 'privatemsg_thread_change_status',
+      'undo callback arguments' => array('status' => PRIVATEMSG_READ),
+    ),
+    'delete' => array(
+      'callback' => 'privatemsg_thread_change_delete',
+      'callback arguments' => array('delete' => 1),
+      'undo callback' => 'privatemsg_thread_change_delete',
+      'undo callback arguments' => array('delete' => 0),
+    ),
+  );
+  return $operations;
+}
Index: privatemsg.theme.inc
===================================================================
RCS file: privatemsg.theme.inc
diff -N privatemsg.theme.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ privatemsg.theme.inc	2 Mar 2009 17:45:43 -0000
@@ -0,0 +1,169 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Theme functions for privatemsg.
+ */
+
+function theme_privatemsg_list_field($thread) {
+}
+
+function phptemplate_privatemsg_list_field__author($thread) {
+  $authors = _privatemsg_generate_user_array($thread['author']);
+  $field = array();
+  $field['data'] = _privatemsg_format_participants($authors, 3, TRUE);
+  $field['class'] = 'privatemsg-list-author';
+  return $field;
+}
+
+function phptemplate_privatemsg_list_field__recipient($thread) {
+  $recipients = _privatemsg_generate_user_array($thread['recipient']);
+  $field = array();
+  $field['data'] = _privatemsg_format_participants($recipients, 3, TRUE);
+  $field['class'] = 'privatemsg-list-recipient';
+  return $field;
+}
+
+function phptemplate_privatemsg_list_field__subject($thread) {
+  $field = array();
+  $field['data'] =l($thread['subject'], 'messages/view/'. $thread['thread_id']);
+  $field['class'] = 'privatemsg-list-subject';
+  return $field;
+}
+
+function phptemplate_privatemsg_list_field__count($thread) {
+  $field = array();
+  $field['data'] =$thread['count'] -1;
+  $field['class'] = 'privatemsg-list-count';
+  return $field;
+}
+
+function phptemplate_privatemsg_list_field__last_updated($thread) {
+  $field = array();
+  $field['data'] = format_date($thread['last_updated'], 'small');
+  $field['class'] = 'privatemsg-list-date';
+  return $field;
+}
+
+function phptemplate_privatemsg_list_field__thread_started($thread) {
+  $field = array();
+  $field['data'] = format_date($thread['thread_started'], 'small');
+  $field['class'] = 'privatemsg-list-date-started';
+  return $field;
+}
+
+function theme_privatemsg_list_header() {
+
+}
+
+function phptemplate_privatemsg_list_header__subject() {
+  return array(
+    'data'    => t('Subject'),
+    'field'   => 'subject',
+    'key'     => 'subject',
+    'class'   => 'privatemsg-header-subject',
+    '#weight' => -40,
+  );
+}
+
+function phptemplate_privatemsg_list_header__count() {
+  return array(
+    'data'    => t('Answers'),
+    'key'     => 'count',
+    'class'   => 'privatemsg-header-count',
+    '#weight' => -25,
+  );
+}
+
+function phptemplate_privatemsg_list_header__author() {
+  return array(
+      'data'    => t('Authors'),
+      'key'     => 'author',
+      'class'   => 'privatemsg-header-authors',
+      '#weight' => -30,
+  );
+}
+
+function phptemplate_privatemsg_list_header__recipient() {
+  return array(
+      'data'    => t('Recipients'),
+      'key'     => 'recipient',
+      'class'   => 'privatemsg-header-recipients',
+      '#weight' => -30,
+  );
+}
+
+function phptemplate_privatemsg_list_header__last_updated() {
+  return array(
+    'data'    => t('Last Updated'),
+    'field'   => 'last_updated',
+    'key'     => 'last_updated',
+    'sort'    => 'desc',
+    'class'   => 'privatemsg-header-lastupdated',
+    '#weight' => -20,
+  );
+}
+
+function phptemplate_privatemsg_list_header__thread_started() {
+  return array(
+    'data'    => t('Started'),
+    'field'   => 'thread_started',
+    'key'     => 'thread_started',
+    'class'   => 'privatemsg-header-threadstarted',
+    '#weight' => -15,
+  );
+}
+
+/**
+ * Theme to display the privatemsg list
+ */
+function theme_privatemsg_list($form) {
+  $has_posts = !empty($form['#rows']);
+
+  drupal_add_css(drupal_get_path('module', 'privatemsg') .'/styles/privatemsg-list.css');
+
+  $headers = $form['#headers'];
+  usort($headers, 'element_sort');
+
+  $themed_rows = array();
+  if ($has_posts) {
+    foreach ($form['#rows'] as $thread_id => $row) {
+      $data = array();
+      $data[] = drupal_render($form['threads'][$thread_id]);
+
+      foreach ($headers as $header) {
+        if (!empty($header['key'])) {
+          if (isset($row['data'][$header['key']])) {
+            $data[] = $row['data'][$header['key']];
+          }
+          else {
+            $data[] = '';
+          }
+        }
+      }
+      unset($row['data']);
+      $themed_rows[] = array('data' => $data) + $row;
+    }
+  }
+  else {
+    $themed_rows[] = array(array('data' => t('No messages available.'), 'colspan' => count($header)));
+  }
+
+  //remove any data in header that we don't need anymore
+  foreach ($headers as $id => $header) {
+    unset($headers[$id]['key']);
+    unset($headers[$id]['#weight']);
+  }
+
+  $form['list'] = array('#value' => theme('table', $headers, $themed_rows, array('class' => 'privatemsg-list')), '#weight' => 5);
+  return drupal_render($form);
+}
+
+function theme_privatemsg_new_block($count) {
+  $text = format_plural($count, 'You have a new message, click here to read it',
+                        'You have @count new messages, click here to read them',
+                        array('@count' => $count));
+
+  return l($text, 'messages', array('attributes' => array('id' => 'privatemsg-new-link')));
+}
\ No newline at end of file
Index: privatemsg-list.js
===================================================================
RCS file: privatemsg-list.js
diff -N privatemsg-list.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ privatemsg-list.js	2 Mar 2009 17:45:43 -0000
@@ -0,0 +1,3 @@
+Drupal.behaviors.hideButtons = function (context) {
+ $("input.privatemsg-action-button").hide();
+};
\ No newline at end of file
Index: styles/privatemsg-list.css
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/styles/privatemsg-list.css,v
retrieving revision 1.1
diff -u -p -r1.1 privatemsg-list.css
--- styles/privatemsg-list.css	3 Sep 2008 06:24:50 -0000	1.1
+++ styles/privatemsg-list.css	2 Mar 2009 17:45:43 -0000
@@ -1,3 +1,3 @@
-.privatemsg-list-subject.privatemsg-unread {
+tr.privatemsg-unread td.privatemsg-list-subject {
   font-weight: bold;
 }
\ No newline at end of file
Index: privatemsg_filter/privatemsg_filter.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg_filter/privatemsg_filter.module,v
retrieving revision 1.1.2.7
diff -u -p -r1.1.2.7 privatemsg_filter.module
--- privatemsg_filter/privatemsg_filter.module	20 Feb 2009 21:38:29 -0000	1.1.2.7
+++ privatemsg_filter/privatemsg_filter.module	2 Mar 2009 17:45:43 -0000
@@ -400,14 +400,8 @@ function privatemsg_filter_create_get_qu
   return $query;
 }
 
-/**
- * Implementation of hook_privatemsg_list_messages_alter().
- */
-function privatemsg_filter_privatemsg_list_messages_alter(&$content, $account) {
-  if (!empty($content['list']['content']) || privatemsg_filter_get_filter($account)) {
-    $content['tags']['content'] = drupal_get_form('privatemsg_filter_dropdown', $account);
-    $content['tags']['#weight'] = -5;
-  }
+function privatemsg_filter_form_privatemsg_list_alter(&$form, $form_state) {
+  $form += privatemsg_filter_dropdown($form_state, $form['#account']);
 }
 
 
