Index: privatemsg.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/privatemsg/privatemsg.module,v retrieving revision 1.70.2.30.2.91.2.64.2.31 diff -u -p -r1.70.2.30.2.91.2.64.2.31 privatemsg.module --- privatemsg.module 19 Nov 2009 22:28:26 -0000 1.70.2.30.2.91.2.64.2.31 +++ privatemsg.module 23 Nov 2009 23:09:37 -0000 @@ -470,11 +470,6 @@ function privatemsg_theme() { 'variables' => array('recipients' => NULL), 'template' => 'privatemsg-between', ), - 'privatemsg_list' => array( - 'file' => 'privatemsg.theme.inc', - 'path' => drupal_get_path('module', 'privatemsg'), - 'render element' => 'form', - ), // Define pattern for header/field templates. The theme system will register all // theme functions that start with the defined pattern. 'privatemsg_list_header' => array( @@ -568,33 +563,36 @@ function privatemsg_list($form, &$form_s } // 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. - $query = _privatemsg_assemble_query('list', $account, $argument); + drupal_add_css(drupal_get_path('module', 'privatemsg') .'/styles/privatemsg-list.css'); + + // Load the table columns. + $columns = array_merge(array('subject', 'last_updated'), array_filter(variable_get('privatemsg_display_fields', array('participants')))); - $threads = array(); - $form['#data'] = array(); + // Load the themed list headers based on the available data. + $headers = _privatemsg_list_headers($columns); + uasort($headers, 'element_sort'); + + $form['list'] = array( + '#type' => 'tableselect', + '#header' => $headers, + '#options' => array(), + '#attributes' => array('class' => array('privatemsg-list')), + '#empty' => t('No messages available.'), + '#weight' => 5, + '#pre_render' => array('_privatemsg_list_thread'), + ); + + $query = _privatemsg_assemble_query('list', $account, $argument); foreach ($query->execute() as $row) { // Store the raw row data. - $form['#data'][$row->thread_id] = (array)$row; - // Store the themed row data. - $form['#rows'][$row->thread_id] = _privatemsg_list_thread((array)$row); - // store thread id for the checkboxes array - $threads[$row->thread_id] = ''; - } - if (empty($form['#data'])) { - // If no threads are displayed, use these default columns. - $keys = array('subject', 'author', 'last_updated'); + $form['list']['#options'][$row->thread_id] = (array)$row; } - else { - // Load the keys of the first row in data, we don't know the key - $keys = array_keys($form['#data'][key($form['#data'])]); + + if (!empty($form['list']['#options'])) { $form['actions'] = _privatemsg_action_form(); - $form['actions']['#weight'] = 20; } - // Load the themed list headers based on the available data - $form['#headers'] = _privatemsg_list_headers(!empty($form['#data']), $keys); // Define checkboxes, pager and theme - $form['threads'] = array('#type' => 'checkboxes', '#options' => $threads); $form['pager'] = array('#theme' => 'pager', '#weight' => 20); //$form['#theme'] = 'privatemsg_list'; @@ -1052,7 +1050,7 @@ function privatemsg_sql_list($account, $ ->condition('pmi.deleted', 0) ->groupBy('pmi.thread_id') ->orderBy('MAX(pmi.is_new)', 'DESC') - ->orderByHeader(_privatemsg_list_headers( FALSE, array('subject', 'last_updated') + $fields)) + ->orderByHeader(_privatemsg_list_headers(array_merge(array('subject', 'last_updated'), $fields))) ->limit(variable_get('privatemsg_per_page', 25)); } @@ -1971,22 +1969,20 @@ function privatemsg_thread_change_status * @return * Array with header defintions for tablesort_sql and theme('table'). */ -function _privatemsg_list_headers($has_posts, $keys) { - $select_header = $has_posts ? theme('table_select_header_cell') : ''; - $select_header['#weight'] = -50; +function _privatemsg_list_headers($keys) { // theme() doesn't include the theme file for patterns, we need to do it manually. include_once drupal_get_path('module', 'privatemsg') .'/privatemsg.theme.inc'; - $header = array($select_header); + $header = array(); foreach ($keys as $key) { // First, try to load a specific theme for that header, if not present, use the default. - if ($return = theme(array('privatemsg_list_header__'. $key, 'privatemsg_list_header'))) { + if ($return = theme(array('privatemsg_list_header__' . $key , 'privatemsg_list_header'))) { // The default theme returns nothing, only store the value if we have something. $header[$key] = $return; } } - if (count($header) == 1) { + if (count($header) == 0) { // No header definition returned, fallback to the default. $header += _privatemsg_list_headers_fallback($keys); } @@ -2012,7 +2008,7 @@ function _privatemsg_list_headers_fallba } /** - * Formats a row in the message list. + * Formats all rows (#options) in the privatemsg tableselect thread list. * * Uses @link theming theme patterns @endlink to theme single fields. * @@ -2021,24 +2017,26 @@ function _privatemsg_list_headers_fallba * @return * Row definition for use with theme('table') */ -function _privatemsg_list_thread($thread) { - $row = array('data' => array()); - - if (!empty($thread['is_new'])) { - // Set the css class in the tr tag. - $row['class'][] = 'privatemsg-unread'; - } - foreach ($thread as $key => $data) { - // First, try to load a specific theme for that field, if not present, use the default. - if ($return = theme(array('privatemsg_list_field__'. $key, 'privatemsg_list_field'), $thread)) { - // The default theme returns nothing, only store the value if we have something. - $row['data'][$key] = $return; +function _privatemsg_list_thread($tableselect) { + foreach ($tableselect['#options'] as $id => $thread) { + $row = array(); + if (!empty($thread['is_new'])) { + // Set the css class in the tr tag. + $row['#attributes']['class'][] = 'privatemsg-unread'; + } + foreach ($thread as $key => $data) { + // First, try to load a specific theme for that field, if not present, use the default. + if ($return = theme(array('privatemsg_list_field__'. $key, 'privatemsg_list_field'), $thread)) { + // The default theme returns nothing, only store the value if we have something. + $row['data'][$key] = $return; + } } + if (empty($row['data'])) { + $row = _privatemsg_list_thread_fallback($thread); + } + $tableselect['#options'][$id] = $row; } - if (empty($row['data'])) { - $row['data'] = _privatemsg_list_thread_fallback($thread); - } - return $row; + return $tableselect; } /** @@ -2105,7 +2103,7 @@ function privatemsg_list_submit($form, & // Only execute something if we have a valid callback and at least one checked thread. if (!empty($operation['callback'])) { - privatemsg_operation_execute($operation, $form_state['values']['threads']); + privatemsg_operation_execute($operation, $form_state['values']['list']); } } Index: privatemsg.theme.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/privatemsg/Attic/privatemsg.theme.inc,v retrieving revision 1.1.2.7.2.4 diff -u -p -r1.1.2.7.2.4 privatemsg.theme.inc --- privatemsg.theme.inc 12 Nov 2009 14:35:09 -0000 1.1.2.7.2.4 +++ privatemsg.theme.inc 23 Nov 2009 23:09:37 -0000 @@ -155,7 +155,6 @@ function phptemplate_privatemsg_list_fie * this column. */ function theme_privatemsg_list_header() { - } /** @@ -167,7 +166,6 @@ function phptemplate_privatemsg_list_hea return array( 'data' => t('Subject'), 'field' => 'subject', - 'key' => 'subject', 'class' => array('privatemsg-header-subject'), '#weight' => -40, ); @@ -181,7 +179,6 @@ function phptemplate_privatemsg_list_hea function phptemplate_privatemsg_list_header__count() { return array( 'data' => t('Messages'), - 'key' => 'count', 'class' => array('privatemsg-header-count'), '#weight' => -25, ); @@ -195,7 +192,6 @@ function phptemplate_privatemsg_list_hea function phptemplate_privatemsg_list_header__participants() { return array( 'data' => t('Participants'), - 'key' => 'participants', 'class' => array('privatemsg-header-participants'), '#weight' => -30, ); @@ -210,7 +206,6 @@ function phptemplate_privatemsg_list_hea return array( 'data' => t('Last Updated'), 'field' => 'last_updated', - 'key' => 'last_updated', 'sort' => 'desc', 'class' => array('privatemsg-header-lastupdated'), '#weight' => -20, @@ -226,78 +221,12 @@ function phptemplate_privatemsg_list_hea return array( 'data' => t('Started'), 'field' => 'thread_started', - 'key' => 'thread_started', 'class' => array('privatemsg-header-threadstarted'), '#weight' => -15, ); } /** - * Theme to display the privatemsg list. - * - * This theme builds a table with paging based on the data which has been built - * by the header and field theme patterns. - */ -function theme_privatemsg_list($form) { - // @todo: Something is wrong, remove this hack. - $form = $form['form']; - $has_posts = !empty($form['#rows']); - - drupal_add_css(drupal_get_path('module', 'privatemsg') .'/styles/privatemsg-list.css'); - - // sort the headers array based on the #weight property. - $headers = $form['#headers']; - usort($headers, 'element_sort'); - - $themed_rows = array(); - // Check if there is atleast a single thread. - if ($has_posts) { - foreach ($form['#rows'] as $thread_id => $row) { - $data = array(); - // Render the checkbox. - $data[] = array('data' => drupal_render($form['threads'][$thread_id]), 'class' => array('privatemsg-list-select')); - - // Store the #rows data in the same order as the header is, the key property of the header refers to the field that belongs to it. - foreach ($headers as $header) { - if (!empty($header['key'])) { - if (isset($row['data'][$header['key']])) { - $data[] = $row['data'][$header['key']]; - } - else { - // Store a empty value so that the order is still correct. - $data[] = ''; - } - } - } - // Replace the data - $row['data'] = $data; - $themed_rows[] = $row; - } - } - else { - // Display a message if now messages are available. - $themed_rows[] = array(array('data' => t('No messages available.'), 'colspan' => count($headers))); - } - - // Remove any data in header that we don't need anymore. - foreach ($headers as $id => $header) { - unset($headers[$id]['key']); - unset($headers[$id]['#weight']); - } - - // Theme the table, pass all generated information to the table theme function. - $form['list'] = array( - '#markup' => theme('table', array( - 'header' => $headers, - 'rows' => $themed_rows, - 'attributes' => array('class' => array('privatemsg-list')), - )), - '#weight' => 5, - ); - return drupal_render_children($form); -} - -/** * Theme a block which displays the number of new messages a user has. */ function theme_privatemsg_new_block($count) { Index: privatemsg_filter/privatemsg_filter.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/privatemsg/privatemsg_filter/privatemsg_filter.module,v retrieving revision 1.1.2.17.2.10 diff -u -p -r1.1.2.17.2.10 privatemsg_filter.module --- privatemsg_filter/privatemsg_filter.module 9 Nov 2009 21:13:33 -0000 1.1.2.17.2.10 +++ privatemsg_filter/privatemsg_filter.module 23 Nov 2009 23:09:40 -0000 @@ -164,6 +164,8 @@ function privatemsg_filter_form_private_ '#title' => t('Choose the default list option'), '#description' => t('Choose which of the two lists are shown by default when following the messages link.'), ); + // Add tags to the list of possible columns. + $form['privatemsg_listing']['privatemsg_display_fields']['#options']['tags'] = t('Tags'); $form['#submit'][] = 'privatemsg_filter_settings_submit'; } @@ -487,8 +489,28 @@ function privatemsg_filter_form_privatem $form += privatemsg_filter_dropdown($form_state, $form['#account']); } + $fields = array_filter(variable_get('privatemsg_display_fields', array('participants'))); + if (in_array('tags', $fields)) { + // Load thread id's of the current list. + $threads = array_keys($form['list']['#options']); + + // Fetch all tags of those threads. + $query = _privatemsg_assemble_query(array('tags', 'privatemsg_filter'), $user, $threads, 3); + + // Add them to #data + foreach ($query->execute() as $tag) { + $form['list']['#options'][$tag->thread_id]['tags'][$tag->tag_id] = $tag->tag; + } + // Avoid notices for threads without tags. + foreach ($form['list']['#options'] as &$thread) { + if (empty($thread['tags'])) { + $thread['tags'] = array(); + } + } + } + $tags = privatemsg_filter_get_tags_data($user); - if (privatemsg_user_access('filter private messages') && !empty($tags) && !empty($form['#data'])) { + if (privatemsg_user_access('filter private messages') && !empty($tags) && !empty( $form['list']['#options'])) { $options = array(); $options[] = t('Apply Tag...'); foreach ($tags as $tag_id => $tag) { @@ -525,6 +547,43 @@ function privatemsg_filter_form_privatem } /** + * Define the header for the tags column. + * + * @see theme_privatemsg_list_header() + */ +function phptemplate_privatemsg_list_header__tags() { + return array( + 'data' => t('Tags'), + 'key' => 'tags', + 'class' => 'privatemsg-header-tags', + '#weight' => -42, + ); +} + + +/** + * Default theme pattern function to display tags. + * + * @see theme_privatemsg_list_field() + */ +function phptemplate_privatemsg_list_field__tags($thread) { + if (!empty($thread['tags'])) { + $tags = array(); + + foreach ($thread['tags'] as $tag_id => $tag) { + $tags[] = l(strlen($tag) > 15 ? substr($tag, 0, 13) . '...' : $tag, 'messages', array( + 'attributes' => array('title' => $tag), + 'query' => array('tags' => $tag) + )); + } + return array( + 'data' => implode(', ', $tags), + 'class' => 'privatemsg-list-tags', + ); + } +} + +/** * Form callback for adding a tag to threads. */ function privatemsg_filter_add_tag_submit($form, &$form_state) { @@ -535,7 +594,7 @@ function privatemsg_filter_add_tag_submi 'undo callback arguments' => array('tag_id' => $form_state['values']['tag-add']), ); drupal_set_message(t('The selected conversations have been tagged.')); - privatemsg_operation_execute($operation, $form_state['values']['threads']); + privatemsg_operation_execute($operation, $form_state['values']['list']); } /** @@ -549,7 +608,7 @@ function privatemsg_filter_remove_tag_su 'undo callback arguments' => array('tag_id' => $form_state['values']['tag-remove']), ); drupal_set_message(t('The tag has been removed from the selected conversations.')); - privatemsg_operation_execute($operation, $form_state['values']['threads']); + privatemsg_operation_execute($operation, $form_state['values']['list']); } /** @@ -622,7 +681,7 @@ function privatemsg_filter_form(&$form_s $thread_id = arg(2); // Get a list of current tags for this thread - $query = _privatemsg_assemble_query(array('tags', 'privatemsg_filter'), $user, $thread_id); + $query = _privatemsg_assemble_query(array('tags', 'privatemsg_filter'), $user, array($thread_id)); $count = $query->countQuery()->execute()->fetchField(); $tags = implode(', ', $query->execute()->fetchCol(1)); @@ -723,17 +782,20 @@ function privatemsg_filter_query_private * Query definition to get the tags in use by the specified user. * * @param $user - * User object for whom we want the tags. - * @param $thread_id - * The thread_id - Only needed if we want the tags for a specific thread instead of all used tags on any thread. + * User object for whom we want the tags. + * @param $threads + * Array of thread ids, defaults to all threads of a user. + * @param $limit + * Limit the number of tags *per thread*. */ -function privatemsg_filter_sql_tags($user = NULL, $thread_id = NULL) { +function privatemsg_filter_sql_tags($user = NULL, $threads = NULL, $limit = NULL) { $query = db_select('pm_tags', 't') ->fields('t', array('tag_id', 'tag', 'public')) ->orderBy('t.tag', 'ASC'); - if (!empty($thread_id)) { + if (!empty($threads)) { + $query->addField('ti', 'thread_id'); $query->join('pm_tags_index', 'ti', 'ti.tag_id = t.tag_id'); - $query->condition('ti.thread_id', $thread_id); + $query->condition('ti.thread_id', $threads); } else { $query->addExpression('COUNT(ti.thread_id)', 'count'); @@ -746,7 +808,22 @@ function privatemsg_filter_sql_tags($use if (!empty($user)) { $query->condition('ti.uid', $user->uid); } - if (!empty($thread_id) || !empty($user)) { + + // Only select n tags per thread (ordered per tag_id), see + // http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/. + // + // It does select how many tags for that thread/uid combination exist that + // have a lower tag_id and does only select those that have less than $limit. + // + // This should only have a very minor performance impact as most users won't + // tag a thread with 1000 different tags. + if ($limit) { + $query->where('(SELECT count(*) FROM {pm_tags_index} AS pmtic + WHERE pmtic.thread_id = ti.thread_id + AND pmtic.uid = ti.uid + AND pmtic.tag_id < ti.tag_id) < :limit', array(':limit' => $limit)); + } + elseif (!empty($thread_id) || !empty($user)) { $query->orderBy('t.tag', 'ASC'); } return $query; Index: privatemsg_filter/privatemsg_filter.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/privatemsg/privatemsg_filter/Attic/privatemsg_filter.test,v retrieving revision 1.1.4.5 diff -u -p -r1.1.4.5 privatemsg_filter.test --- privatemsg_filter/privatemsg_filter.test 29 Oct 2009 15:57:14 -0000 1.1.4.5 +++ privatemsg_filter/privatemsg_filter.test 23 Nov 2009 23:09:41 -0000 @@ -74,7 +74,7 @@ class PrivatemsgFilterTestCase extends D // Test for bug http://drupal.org/node/617648 // Delete all messages for author. $delete = array( - 'threads[1]' => 1, + 'list[1]' => 1, ); $this->drupalPost(NULL, $delete, t('Delete')); $this->assertNoText($edit['subject'], t('Thread has been deleted for author.')); Index: styles/privatemsg-list.css =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/privatemsg/styles/privatemsg-list.css,v retrieving revision 1.1.2.2 diff -u -p -r1.1.2.2 privatemsg-list.css --- styles/privatemsg-list.css 17 Jul 2009 00:32:05 -0000 1.1.2.2 +++ styles/privatemsg-list.css 23 Nov 2009 23:09:41 -0000 @@ -9,3 +9,11 @@ .privatemsg-list-count { text-align: center; } + +.privatemsg-list-subject { + min-width: 35%; +} + +.privatemsg-list-date { + min-width: 20%; +}