Index: privatemsg.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg.module,v
retrieving revision 1.70.2.30.2.91.2.24
diff -u -p -r1.70.2.30.2.91.2.24 privatemsg.module
--- privatemsg.module	20 Feb 2009 21:38:28 -0000	1.70.2.30.2.91.2.24
+++ privatemsg.module	20 Feb 2009 22:15:59 -0000
@@ -256,6 +256,37 @@ 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['flush_deleted'] = array(
+    '#type'        => 'fieldset',
+    '#collapsible' => TRUE,
+    '#collapsed'   => TRUE,
+    '#title'       => t('Flush deleted messages'),
+    '#description' => t('By default, deleted messages are only hidden from the
+                         user but still saved in the database. These settings
+                         allow to control if and when they should be deleted.'),
+  );
+
+  $form['flush_deleted']['privatemsg_flush_enabled'] = array(
+    '#type'          => 'checkbox',
+    '#title'         => t('Flush deleted messages'),
+    '#default_value' => variable_get('privatemsg_flush_enabled', FALSE),
+    '#description'   => t('Enable the flushing of deleted messages. Requires that cron is enabled'),
+  );
+
+  $form['flush_deleted']['privatemsg_flush_days'] = array(
+    '#type' => 'select',
+    '#title' => t('Flush messages after they have been deleted for more days than'),
+    '#default_value' => variable_get('privatemsg_flush_days', 30),
+    '#options' => drupal_map_assoc(array(0, 1, 2, 5, 10, 30, 100)),
+  );
+
+  $form['flush_deleted']['privatemsg_flush_max'] = array(
+    '#type' => 'select',
+    '#title' => t('Maximum number of messages to flush per cron run'),
+    '#default_value' => variable_get('privatemsg_flush_max', 200),
+    '#options' => drupal_map_assoc(array(50, 100, 200, 500, 1000)),
+  );
   $form['#submit'][] = 'private_message_settings_submit';
   return system_settings_form($form);
 }
@@ -264,6 +295,54 @@ function private_message_settings_submit
   drupal_rebuild_theme_registry();
 }
 
+/**
+ * hook_cron implementation, flushes deleted queries
+ */
+function privatemsg_cron() {
+  if (variable_get('privatemsg_flush_enabled', FALSE)) {
+
+    $query = _privatemsg_assemble_query('deleted', variable_get('privatemsg_flush_days', 30));
+    $result = db_query($query['query']);
+
+    $flushed = 0;
+    while (($row = db_fetch_array($result)) && ($flushed < variable_get('privatemsg_flush_max', 200))) {
+      $starttime = microtime(true);
+
+      $message = _privatemsg_load($row['mid']);
+      module_invoke_all('privatemsg_message_flush', $message);
+
+      // Delete recipients
+      db_query('DELETE FROM {pm_index} WHERE mid = %d', $row['mid']);
+
+      // Delete message
+      db_query('DELETE FROM {pm_message} WHERE mid = %d', $row['mid']);
+      $flushed++;
+    }
+  }
+}
+
+/**
+ * Sql function for the deleted messages query
+ *
+ * @param array $fragments Query-fragments
+ * @param int   $days      Select messages older than x days
+ */
+function privatemsg_sql_deleted(&$fragments, $days) {
+  $fragments['primary_table'] = '{pm_message} pm';
+
+  $fragments['select'][] = 'pm.mid';
+  $fragments['select'][] = 'MIN(pmi.deleted) as is_deleted';
+  $fragments['select'][] = 'MAX(pmi.deleted) as last_deleted';
+
+  $fragments['inner_join'][] = 'INNER JOIN {pm_index} pmi ON (pmi.mid = pm.mid)';
+
+  $fragments['group_by'][] = 'pm.mid';
+
+  $fragments['having'][] = 'is_deleted > 0';
+  $fragments['having'][] = 'last_deleted < %d';
+  $fragments['query_args'][] = time() - $days * 86400;
+}
+
 function privatemsg_theme() {
   return array(
     'privatemsg_view'    => array(
@@ -1107,22 +1186,11 @@ function privatemsg_message_delete($pmid
   }
   $message = _privatemsg_load($pmid, $account);
 
-  db_query('UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND uid = %d', 1, $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);
-
-  $deleted_by_all = FALSE;
-  if ($deleted['deleted_by_all'] == 0) {
-    $deleted_by_all = TRUE;
-  }
-
-  module_invoke_all('privatemsg_message_delete', $message, $deleted_by_all);
+  db_query('UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND uid = %d', time(), $pmid, $account->uid);
+  module_invoke_all('privatemsg_message_delete', $message);
 }
 
 function privatemsg_delete_submit($form, &$form_state) {
-  $deleted = 1;
-
   if ($form_state['values']['confirm']) {
     privatemsg_message_delete($form_state['values']['pmid']);
     drupal_set_message(t('Message has been deleted'));
@@ -1354,6 +1422,7 @@ function _privatemsg_assemble_query($que
   $INNER_JOIN = array();
   $WHERE = array();
   $GROUP_BY = array();
+  $HAVING   = array();
   $ORDER_BY = array();
   $QUERY_ARGS = array();
   $primary_table = '';
@@ -1363,6 +1432,7 @@ function _privatemsg_assemble_query($que
     'inner_join'  => $INNER_JOIN,
     'where'       => $WHERE,
     'group_by'    => $GROUP_BY,
+    'having'      => $HAVING,
     'order_by'    => $ORDER_BY,
     'query_args'  => $QUERY_ARGS,
     'primary_table'  => $primary_table,
@@ -1396,6 +1466,7 @@ function _privatemsg_assemble_query($que
   $INNER_JOIN = $fragments['inner_join'];
   $WHERE = $fragments['where'];
   $GROUP_BY = $fragments['group_by'];
+  $HAVING   = $fragments['having'];
   $ORDER_BY = $fragments['order_by'];
   $QUERY_ARGS = $fragments['query_args'];
   $primary_table = $fragments['primary_table'];
@@ -1407,6 +1478,10 @@ function _privatemsg_assemble_query($que
   // Perform the whole query assembly only if we have something to select.
   if (!empty($SELECT)) {
     $str_select = implode(", ", $SELECT);
+    // Another hack for making HAVING work on PostgreSQL
+    // This only adds aggregate and subqueries to a special $str_select_having
+    $str_select_having = _privatemsg_generate_aggregate_select($SELECT);
+
     $query = "SELECT {$str_select} FROM ". $primary_table;
 
     // Also build a count query which can be passed to pager_query to get a "page count" as that does not play well with queries including "GROUP BY".
@@ -1415,8 +1490,9 @@ function _privatemsg_assemble_query($que
     if (!empty($GROUP_BY)) {
       // PostgreSQL does not support COUNT(sometextfield, someintfield), so I'm only using the first one
       // Works fine for thread_id/list but may generate an error when a more complex GROUP BY is used.
+      // But for HAVING, we still need all aggregate fields, so that HAVING works
       $str_group_by_count = current($GROUP_BY);
-      $count = "SELECT COUNT(DISTINCT {$str_group_by_count}) FROM ". $primary_table;
+      $count = "SELECT COUNT(DISTINCT {$str_group_by_count})$str_select_having FROM ". $primary_table;
     }
     else {
       $count = "SELECT COUNT(*) FROM ". $primary_table;
@@ -1436,6 +1512,12 @@ function _privatemsg_assemble_query($que
       $str_group_by = ' GROUP BY '. implode(", ", $GROUP_BY) ;
       $query .= " {$str_group_by}";
     }
+    if (!empty($HAVING)) {
+      $str_having = '('. implode(') AND (', $HAVING) .')';
+      $query .= " HAVING {$str_having}";
+      $count .= " HAVING {$str_having}";
+    }
+
     if (!empty($ORDER_BY)) {
       $str_order_by = ' ORDER BY '. implode(", ", $ORDER_BY) ;
       $query .= " {$str_order_by}";
@@ -1450,3 +1532,31 @@ function _privatemsg_assemble_query($que
   }
   return FALSE;
 }
+
+/**
+ * Returns a select query part with only aggregate functions and subqueries.
+ *
+ * This is used in the query builder to have a working count query for
+ * postgreSQL with HAVING and GROUP BY.
+ *
+ * @param array $SELECT Array with Select fields
+ *
+ * @return string Select query, MAX(field), MIN(field2)...
+ */
+function _privatemsg_generate_aggregate_select($SELECT) {
+  $str_select_having_array = array();
+  $aggregates = array('MAX, MIN, COUNT, SELECT, GROUP_CONCAT');
+  foreach ($SELECT as $field) {
+    foreach ($aggregates as $aggregate) {
+      if (strpos($field, $aggregate) !== FALSE) {
+        $str_select_having_array[] = $field;
+      }
+    }
+  }
+  $str_select_having = '';
+  if (!empty($str_select_having_array)) {
+    $str_select_having = ', ';
+    $str_select_having .= implode(', ', $str_select_having_array);
+  }
+  return $str_select_having;
+}
