'Filter', 'description' => 'Configure filter settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('privatemsg_filter_admin'), 'file' => 'privatemsg_filter.admin.inc', 'access arguments' => array('administer privatemsg settings'), 'type' => MENU_LOCAL_TASK, ); $items['admin/settings/messages/tags'] = array( 'title' => 'Tags', 'description' => 'Configure tags.', 'page callback' => 'privatemsg_tags_admin', 'file' => 'privatemsg_filter.admin.inc', 'access arguments' => array('administer privatemsg settings'), 'type' => MENU_LOCAL_TASK, 'file' => 'privatemsg_filter.admin.inc', ); $items['admin/settings/messages/tags/list'] = array( 'title' => 'List', 'description' => 'List all existing tags.', 'page callback' => 'privatemsg_tags_admin', 'file' => 'privatemsg_filter.admin.inc', 'access arguments' => array('administer privatemsg settings'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/settings/messages/tags/add'] = array( 'title' => 'Add', 'description' => 'Add a new tag.', 'page callback' => 'drupal_get_form', 'page arguments' => array('privatemsg_tags_form'), 'file' => 'privatemsg_filter.admin.inc', 'access arguments' => array('administer privatemsg settings'), 'type' => MENU_LOCAL_TASK, ); $items['admin/settings/messages/tags/rebuild'] = array( 'title' => 'Rebuild inbox', 'description' => 'Tag all thread which should be displayed in the inbox.', 'page callback' => 'drupal_get_form', 'page arguments' => array('privatemsg_filter_inbox_rebuid_form'), 'file' => 'privatemsg_filter.admin.inc', 'access arguments' => array('administer privatemsg settings'), 'type' => MENU_LOCAL_TASK, ); $items['admin/settings/messages/tags/edit/%'] = array( 'title' => 'Add', 'description' => 'Configure tags.', 'page callback' => 'drupal_get_form', 'page arguments' => array('privatemsg_tags_form', 5), 'file' => 'privatemsg_filter.admin.inc', 'access arguments' => array('administer privatemsg settings'), 'type' => MENU_CALLBACK, ); $items['admin/settings/messages/tags/delete/%'] = array( 'title' => 'Add', 'description' => 'Configure tags.', 'page callback' => 'drupal_get_form', 'page arguments' => array('privatemsg_filter_tags_delete', 5), 'file' => 'privatemsg_filter.admin.inc', 'access arguments' => array('administer privatemsg settings'), 'type' => MENU_CALLBACK, ); $items[$url_prefix. '/inbox'] = array( 'title' => 'Inbox', 'page callback' => 'privatemsg_list_page', 'page arguments' => array('inbox', $user_arg_position), 'file' => 'privatemsg.pages.inc', 'file path' => drupal_get_path('module', 'privatemsg'), 'access callback' => 'privatemsg_menu_access', 'type' => variable_get('privatemsg_filter_default_list', 0) ? MENU_LOCAL_TASK : MENU_DEFAULT_LOCAL_TASK, 'weight' => -15, ); $items[$url_prefix . '/sent'] = array( 'title' => 'Sent Messages', 'page callback' => 'privatemsg_list_page', 'page arguments' => array('sent', $user_arg_position), 'file' => 'privatemsg.pages.inc', 'file path' => drupal_get_path('module', 'privatemsg'), 'access callback' => 'privatemsg_menu_access', 'type' => MENU_LOCAL_TASK, 'weight' => -12, ); $items['messages/filter/autocomplete'] = array( 'page callback' => 'privatemsg_autocomplete', 'file' => 'privatemsg.pages.inc', 'file path' => drupal_get_path('module', 'privatemsg'), 'access callback' => 'privatemsg_menu_access', 'access arguments' => array('write privatemsg'), 'type' => MENU_CALLBACK, ); $items['messages/filter/tag-autocomplete'] = array( 'page callback' => 'privatemsg_filter_tags_autocomplete', 'access callback' => 'privatemsg_menu_access', 'access arguments' => array('tag private messages'), 'type' => MENU_CALLBACK, 'weight' => -10, ); return $items; } /** * Implements hook_menu_alter(). */ function privatemsg_filter_menu_alter(&$items) { $url_prefix = variable_get('privatemsg_url_prefix', 'messages'); $user_arg_position = array_search('%user', explode('/', $url_prefix)); // Rename messages to "All messages". $items[$url_prefix . '/list']['title'] = 'All messages'; if (variable_get('privatemsg_filter_default_list', 0) == 0) { // Change default argument of /messages to inbox. and set the task to MENU_LOCAL_TASK. $items[$url_prefix]['page arguments'] = array('inbox', $user_arg_position); $items[$url_prefix . '/list']['type'] = MENU_LOCAL_TASK; } } /** * Implements hook_form_FORM_ID_alter(). * * Add a filter widget to the message listing pages. */ function privatemsg_filter_form_privatemsg_admin_settings_alter(&$form, $form_state) { $form['privatemsg_listing']['privatemsg_filter_default_list'] = array( '#type' => 'radios', '#default_value' => variable_get('privatemsg_filter_default_list', 0), '#options' => array(t('Inbox'), t('All messages')), '#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'; } /** * Rebuilding the menu if necessary. */ function privatemsg_filter_settings_submit($form, &$form_state) { if ($form['privatemsg_listing']['privatemsg_filter_default_list']['#default_value'] != $form_state['values']['privatemsg_filter_default_list']) { menu_rebuild(); } } /** * Function to create a tag * * @param $tags * A single tag or an array of tags. */ function privatemsg_filter_create_tags($tags) { if (!is_array($tags)) { $tags = array($tags); } $tag_ids = array(); foreach ($tags as $tag) { $tag = trim($tag); if (empty($tag)) { // Do not save a blank tag. continue; } // Check if the tag already exists and only create the tag if it does not. $tag_id = db_result(db_query("SELECT tag_id FROM {pm_tags} WHERE tag = '%s'", $tag)); if (empty($tag_id) && privatemsg_user_access('create private message tags')) { db_query("INSERT INTO {pm_tags} (tag) VALUES ('%s')", $tag); $tag_id = db_last_insert_id('pm_tags', 'tag_id'); } elseif (empty($tag_id)) { // The user does not have permission to create new tags - disregard this tag and move onto the next. drupal_set_message(t('Tag %tag was ignored because you do not have permission to create new tags.', array('%tag' => $tag))); continue; } $tag_ids[] = $tag_id; } return $tag_ids; } /** * Tag one or multiple threads with a tag. * * @param $threads * A single thread id or an array of thread ids. * @param $tag_id * Id of the tag. */ function privatemsg_filter_add_tags($threads, $tag_ids, $account = NULL) { if (!is_array($threads)) { $threads = array($threads); } if (!is_array($tag_ids)) { $tag_ids = array($tag_ids); } if (empty($account)) { global $user; $account = drupal_clone($user); } foreach ($tag_ids as $tag_id) { foreach ($threads as $thread) { // Make sure that we don't add a tag to a thread twice, // only insert if there is no such tag yet. if (!db_result(db_query('SELECT 1 FROM {pm_tags_index} WHERE tag_id = %d AND (uid = %d AND thread_id = %d)', $tag_id, $account->uid, $thread))) { db_query('INSERT INTO {pm_tags_index} (tag_id, uid, thread_id) VALUES (%d, %d, %d)', $tag_id, $account->uid, $thread); } } } } /** * Remove tag from one or multiple threads. * * @param $threads * A single thread id or an array of thread ids. * @param $tag_id * Id of the tag - set to NULL to remove all tags. */ function privatemsg_filter_remove_tags($threads, $tag_ids = NULL, $account = NULL) { if (!is_array($threads)) { $threads = array($threads); } if (!is_null($tag_ids) && !is_array($tag_ids)) { $tag_ids = array($tag_ids); } if (empty($account)) { global $user; $account = drupal_clone($user); } if (is_null($tag_ids)) { // Delete all tag mapping - all except for the inbox tag if it exists. $inbox_tag = variable_get('privatemsg_filter_inbox_tag', ''); foreach ($threads as $thread) { db_query('DELETE FROM {pm_tags_index} WHERE uid = %d AND thread_id = %d AND tag_id <> %d', $account->uid, $thread, $inbox_tag); } } else { // Delete tag mapping for the specified tags and threads. $sql = 'DELETE FROM {pm_tags_index} WHERE uid = %d AND thread_id IN (' . db_placeholders($threads) . ') AND tag_id IN (' . db_placeholders($tag_ids) . ')'; db_query($sql, array_merge(array($account->uid), $threads, $tag_ids)); } } function privatemsg_filter_get_filter($account) { $filter = array(); // Filtering by tags is either allowed if the user can use tags or he can // filter. if (privatemsg_user_access('filter private messages') || privatemsg_user_access('tag private messages')) { if (isset($_GET['tags'])) { $_GET['tags'] = urldecode($_GET['tags']); $tag_data = privatemsg_filter_get_tags_data($account); foreach (explode(',', $_GET['tags']) as $tag) { if (isset($tag_data[$tag])) { $filter['tags'][$tag] = $tag; } elseif (in_array($tag, $tag_data)) { $filter['tags'][array_search($tag, $tag_data)] = array_search($tag, $tag_data); } } } } // Users can only use the text search or search by author if they have the // necessary permission. if (privatemsg_user_access('filter private messages')) { if (isset($_GET['author'])) { list($filter['author']) = _privatemsg_parse_userstring($_GET['author']); } if (isset($_GET['search'])) { $filter['search'] = $_GET['search']; } } if (!empty($filter)) { return $filter; } if (!empty($_SESSION['privatemsg_filter'])) { return $_SESSION['privatemsg_filter']; } } function privatemsg_filter_get_tags_data($account) { static $tag_data; if (is_array($tag_data)) { return $tag_data; } // Only show the tags that a user have used. $query = _privatemsg_assemble_query(array('tags', 'privatemsg_filter'), $account); $results = db_query($query['query']); $tag_data = array(); while ($result = db_fetch_object($results)) { $tag_data[$result->tag_id] = $result->tag; } return $tag_data; } function privatemsg_filter_dropdown(&$form_state, $account) { drupal_add_css(drupal_get_path('module', 'privatemsg_filter') .'/privatemsg_filter.css'); $form['filter'] = array( '#type' => 'fieldset', '#title' => t('Filter messages'), '#collapsible' => TRUE, '#collapsed' => TRUE, // The form is always called when search arguments are passed in, even if // they don't have access to it. This is necessary to process the search // query. But we don't want to show them the form. '#access' => privatemsg_user_access('filter private messages'), '#weight' => -20, ); $form['filter']['search'] = array( '#type' => 'textfield', '#title' => variable_get('privatemsg_filter_searchbody', FALSE) ? t('By content') : t('By subject'), '#weight' => -20, '#size' => 25, ); $form['filter']['author'] = array( '#type' => 'textfield', '#title' => t('By participant'), '#weight' => -5, '#size' => 25, '#autocomplete_path' => 'messages/filter/autocomplete', ); // Only show form if the user has some messages tagged. if (count($tag_data = privatemsg_filter_get_tags_data($account))) { $form['filter']['tags'] = array( '#type' => 'select', '#title' => t('By tags'), '#options' => $tag_data, '#multiple' => TRUE, '#weight' => 0 ); } $form['filter']['submit'] = array( '#type' => 'submit', '#value' => t('Filter'), '#prefix' => '
', '#weight' => 10, '#submit' => array('privatemsg_filter_dropdown_submit'), ); $form['filter']['save'] = array( '#type' => 'submit', '#value' => t('Save filter'), '#suffix' => '
', '#weight' => 11, '#submit' => array('privatemsg_filter_dropdown_submit'), ); if ($filter = privatemsg_filter_get_filter($account)) { // Display a message if the user will not see the filter form. if (!empty($filter['tags']) && !empty($_GET['tags']) && !privatemsg_user_access('filter private messages')) { drupal_set_message(t('Messages tagged with %tags are currently displayed. Click here to remove this filter.', array('%tags' => $_GET['tags'], '@remove_filter_url' => url($_GET['q'])))); } privatemsg_filter_dropdown_set_active($form, $filter); } return $form; } function privatemsg_filter_dropdown_set_active(&$form, $filter) { $form['filter']['#title'] = t('Filter messages (active)'); $form['filter']['#collapsed'] = FALSE; if (isset($filter['author'])) { $string = ''; foreach ($filter['author'] as $author) { $string .= privatemsg_recipient_format($author, array('plain' => TRUE)) . ', '; } $form['filter']['author']['#default_value'] = $string; } if (isset($filter['tags'])) { $form['filter']['tags']['#default_value'] = $filter['tags']; } if (isset($filter['search'])) { $form['filter']['search']['#default_value'] = $filter['search']; } $form['filter']['reset'] = array( '#type' => 'submit', '#value' => t('Reset'), '#suffix' => '', '#weight' => 12, '#submit' => array('privatemsg_filter_dropdown_submit'), ); unset($form['filter']['save']['#suffix']); } function privatemsg_filter_dropdown_submit($form, &$form_state) { if (!empty($form_state['values']['author'])) { list($form_state['values']['author']) = _privatemsg_parse_userstring($form_state['values']['author']); } switch ($form_state['values']['op']) { case t('Save filter'): $filter = array(); if (!empty($form_state['values']['tags'])) { $filter['tags'] = $form_state['values']['tags']; } if (!empty($form_state['values']['author'])) { $filter['author'] = $form_state['values']['author']; } if (!empty($form_state['values']['search'])) { $filter['search'] = $form_state['values']['search']; } $_SESSION['privatemsg_filter'] = $filter; break; case t('Filter'): drupal_goto($_GET['q'], privatemsg_filter_create_get_query($form_state['values'])); return; break; case t('Reset'): $_SESSION['privatemsg_filter'] = array(); break; } $form_state['redirect'] = $_GET['q']; } /** * Creates a GET query based on the selected filters. */ function privatemsg_filter_create_get_query($filter) { $query = array(); if (isset($filter['tags']) && !empty($filter['tags'])) { $ids = array(); foreach ($filter['tags'] as $tag) { if ((int)$tag > 0) { $ids[] = $tag; } else { $query['tags'][] = $tag; } } $sql = 'SELECT pmt.tag FROM {pm_tags} pmt WHERE pmt.tag_id IN ('. implode(', ', $filter['tags']) .')'; $result = db_query($sql); while ($row = db_fetch_object($result)) { $query['tags'][] = $row->tag; } if (isset($query['tags'])) { $query['tags'] = implode(',', $query['tags']); } } if (isset($filter['author']) && !empty($filter['author'])) { foreach ($filter['author'] as $author) { if (is_object($author) && isset($author->uid) && isset($author->name)) { $query['author'][] = privatemsg_recipient_format($author, array('plain' => TRUE)); } elseif (is_int($author) && $author_obj = _privatemsg_user_load($author)) { $query['author'][] = privatemsg_recipient_format($author, array('plain' => TRUE)); } } if (isset($query['author'])) { $query['author'] = implode(',', $query['author']); } } if (isset($filter['search']) && !empty($filter['search'])) { $query['search'] = $filter['search']; } return $query; } /** * Implements hook_form_FORM_ID_alter(). * * Adds a filter widget to the message listing pages. */ function privatemsg_filter_form_privatemsg_list_alter(&$form, $form_state) { global $user; if (!empty($form['#data']) || privatemsg_filter_get_filter($user)) { $form += privatemsg_filter_dropdown($form_state, $form['#account']); } $fields = array_filter(variable_get('privatemsg_display_fields', array('participants'))); if (privatemsg_user_access('tag private messages') && in_array('tags', $fields) && !empty($form['#data'])) { // Load thread id's of the current list. $threads = array_keys($form['#data']); // Fetch all tags of those threads. $query = _privatemsg_assemble_query(array('tags', 'privatemsg_filter'), $user, $threads, 3); // Add them to #data $result = db_query($query['query']); while ($tag = db_fetch_array($result)) { $form['#data'][$tag['thread_id']]['tags'][$tag['tag_id']] = $tag['tag']; } } if (privatemsg_user_access('tag private messages') && !empty($form['#data'])) { $form['actions']['tag-add'] = array( '#type' => 'textfield', '#size' => 15, '#autocomplete_path' => 'messages/filter/tag-autocomplete', ); $form['actions']['tag-add-submit'] = array( //'#prefix' => '
', //'#suffix' => '
', '#type' => 'submit', '#value' => t('Apply Tag'), '#submit' => array('privatemsg_filter_add_tag_submit'), //'#attributes' => array('class' => 'privatemsg-action-button'), ); $tags = privatemsg_filter_get_tags_data($user); if (!empty($tags)) { $options[0] = t('Remove Tag...'); foreach ($tags as $tag_id => $tag) { $options[$tag_id] = $tag; } $form['actions']['tag-remove'] = array( '#type' => 'select', '#options' => $options, '#default_value' => 0, ); $form['actions']['tag-remove-submit'] = array( '#prefix' => '
', '#suffix' => '
', '#type' => 'submit', '#value' => t('Remove Tag'), '#submit' => array('privatemsg_filter_remove_tag_submit'), '#attributes' => array('class' => 'privatemsg-action-button'), ); } // JS for hiding the submit buttons. drupal_add_js(drupal_get_path('module', 'privatemsg_filter') .'/privatemsg-filter-list.js'); } } /** * Implements hook_privatemsg_thread_operations(). */ function privatemsg_filter_privatemsg_thread_operations($type) { if ($type == 'inbox') { $archive = array( 'label' => t('Archive'), 'callback' => 'privatemsg_filter_remove_tags', 'callback arguments' => array('tag_id' => variable_get('privatemsg_filter_inbox_tag', '')), 'success message' => t('The messages have been archived.'), 'undo callback' => 'privatemsg_filter_add_tags', 'undo callback arguments' => array('tag_id' => variable_get('privatemsg_filter_inbox_tag', '')), ); return array('archive' => $archive); } } /** * Define the header for the tags column. * * @see theme_privatemsg_list_header() */ function phptemplate_privatemsg_list_header__tags() { if (privatemsg_user_access('tag private messages')) { 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(drupal_strlen($tag) > 15 ? drupal_substr($tag, 0, 13) . '...' : $tag, 'messages', array( 'attributes' => array('title' => $tag), 'query' => array('tags' => $tag) )); } return array( 'data' => implode(', ', $tags), 'class' => 'privatemsg-list-tags', ); } } /** * @addtogroup sql * @{ */ /** * Hook into the query builder to add the tagging info to the correct query */ function privatemsg_filter_privatemsg_sql_list_alter(&$fragments, $account, $argument) { // Check if its a filtered view. if ($argument == 'sent') { $fragments['where'][] = "pm.author = %d"; $fragments['query_args']['where'][] = $account->uid; } $filter = privatemsg_filter_get_filter($account); if ($argument == 'inbox') { $filter['tags'][] = variable_get('privatemsg_filter_inbox_tag', ''); } // Filter the message listing by any set tags. if ($filter) { $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['where'][] = "pmti$count.tag_id = %d"; $fragments['query_args']['where'][] = $tag; $count++; } } 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.recipient = %d AND pmi$count.type = 'user'"; $fragments['query_args']['where'][] = $author->uid; $count++; } } 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'] .'%%'; $fragments['query_args']['where'][] = '%%'. $filter['search'] .'%%'; } else { $fragments['where'][] = "pm.subject LIKE '%s'"; $fragments['query_args']['where'][] = '%%'. $filter['search'] .'%%'; } } } } /** * Hook into the view messages page to add a form for tagging purposes. */ function privatemsg_filter_privatemsg_view_messages_alter(&$content, $thread) { if (count($thread['messages']) > 0 && privatemsg_user_access('tag private messages')) { $content['tags'] = privatemsg_filter_show_tags($thread['thread_id'], !empty($_GET['show_tags_form'])); } } function privatemsg_filter_show_tags($thread_id, $show_form) { global $user; drupal_add_css(drupal_get_path('module', 'privatemsg_filter') . '/privatemsg_filter.css'); $element = array( '#prefix' => '
', '#suffix' => '
', '#weight' => -10, ); $query = _privatemsg_assemble_query(array('tags', 'privatemsg_filter'), $user, array($thread_id)); if (!$show_form) { if (db_result(db_query($query['count'])) == 0) { $element['link'] = array( '#value' => l(t('Tag this conversation'), $_GET['q'], array( 'query' => array('show_tags_form' => TRUE), 'attributes' => array('class' => array('privatemsg-filter-tags-add')), )), ); } else { $element['label'] = array( '#value' => '' . t('Tags:') . '', '#weight' => 0, ); $result = db_query($query['query']); $tags = array(); $element['tags']['#weight'] = 5; while ($tag = db_fetch_object($result)) { $element['tags'][]['#value'] = l($tag->tag, 'messages', array( 'attributes' => array('title' => $tag->tag), 'query' => array('tags' => $tag->tag), )); } $element['link'] = array( '#value' => l(t('(modify tags)'), $_GET['q'], array( 'query' => array('show_tags_form' => TRUE), 'attributes' => array('class' => array('privatemsg-filter-tags-add')), )), '#weight' => 10, ); } return $element; } else { return array('#value' => drupal_get_form('privatemsg_filter_form', $thread_id)); } } /** * Form to show and allow modification of tagging information of a conversation. */ function privatemsg_filter_form(&$form_state, $thread_id) { global $user; // Get a list of current tags for this thread $query = _privatemsg_assemble_query(array('tags', 'privatemsg_filter'), $user, array($thread_id)); $results = db_query($query['query']); $count = db_result(db_query($query['count'])); $tags = ''; while ($tag = db_fetch_array($results)) { $tags .= $tag['tag'] . ', '; } $form = array( '#prefix' => '
', '#suffix' => '
', '#weight' => -10, ); $form['user_id'] = array( '#type' => 'value', '#value' => $user->uid, ); $form['thread_id'] = array( '#type' => 'value', '#value' => $thread_id, ); $form['tags'] = array( '#type' => 'textfield', '#size' => 30, '#default_value' => $tags, '#autocomplete_path' => 'messages/filter/tag-autocomplete', ); $form['modify_tags'] = array( '#type' => 'submit', '#value' => t('Tag this conversation'), ); $form['cancel'] = array( '#value' => l(t('Cancel'), $_GET['q'], array( 'attributes' => array('id' => 'privatemsg-filter-tags-cancel'), )), ); return $form; } function privatemsg_filter_form_submit($form, &$form_state) { $tags = explode(',', $form_state['values']['tags']); // Step 1 - Delete all tag mapping. I cannot think of a better way to remove tags that are no longer in the textfield, so ideas welcome. privatemsg_filter_remove_tags($form_state['values']['thread_id']); // Step 2 - Get the id for each of the tags. $tag_ids = privatemsg_filter_create_tags($tags); // Step 3 - Save all the tagging data. foreach ($tag_ids as $tag_id) { privatemsg_filter_add_tags($form_state['values']['thread_id'], $tag_id); } drupal_set_message(t('Your conversation tags have been saved.')); } /** * Return autocomplete results for tags. * * Most of this code has been lifted/modified from * privatemsg_user_name_autocomplete(). */ function privatemsg_filter_tags_autocomplete($string) { // 1: Parse $string and build a list of tags. $tags = array(); $fragments = explode(',', $string); foreach ($fragments as $index => $tag) { $tag = trim($tag); $tags[$tag] = $tag; } // 2: Find the next tag suggestion. $fragment = array_pop($tags); $matches = array(); if (!empty($fragment)) { $query = _privatemsg_assemble_query(array('tags_autocomplete', 'privatemsg_filter'), $fragment, $tags); $result = db_query_range($query['query'], $fragment, 0, 10); $prefix = count($tags) ? implode(", ", $tags) .", " : ''; // 3: Build proper suggestions and print. while ($tag = db_fetch_object($result)) { $matches[$prefix . $tag->tag .", "] = $tag->tag; } } // convert to object to prevent drupal bug, see http://drupal.org/node/175361 drupal_json((object)$matches); } /** * @addtogroup sql * @{ */ /** * Limit the user autocomplete for the filter widget. * * @param $fragments * Query fragments. * @param $search * Username search string. * @param $names * Array of names that are already part of the autocomplete field. */ function privatemsg_filter_privatemsg_sql_autocomplete_alter(&$fragments, $search, $names) { global $user; // arg(1) is an additional URL argument passed to the URL when only // users that are listed as recipient for threads of that user should be // displayed. // @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.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.recipient = %d AND piu.type = 'user' AND pip.mid = piu.mid"; $fragments['query_args']['join'][] = $user->uid; } } /** * Query definition to fetch tags. * * @param $fragments * Query fragments array. * @param $user * 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(&$fragments, $user = NULL, $threads = NULL, $limit = NULL, $showHidden = FALSE) { $fragments['primary_table'] = '{pm_tags} t'; $fragments['select'][] = 't.tag'; $fragments['select'][] = 't.tag_id'; $fragments['select'][] = 't.public'; if (!empty($threads)) { // If the tag list needs to be for specific threads. $fragments['select'][] = 'ti.thread_id'; $fragments['inner_join'][] = 'INNER JOIN {pm_tags_index} ti on ti.tag_id = t.tag_id'; $fragments['where'][] = 'ti.thread_id IN (' . db_placeholders($threads) . ')'; $fragments['query_args']['where'] += $threads; } else { // Tag usage counter is only used when we select all tags. $fragments['select'][] = 'COUNT(ti.thread_id) as count'; // LEFT JOIN so that unused tags are displayed too. $fragments['inner_join'][] = 'LEFT JOIN {pm_tags_index} ti ON t.tag_id = ti.tag_id'; $fragments['group_by'][] = 't.tag_id'; $fragments['group_by'][] = 't.tag'; $fragments['group_by'][] = 't.public'; } if (!empty($user)) { $fragments['where'][] = 'ti.uid = %d'; $fragments['query_args']['where'][] = $user->uid; } if (!$showHidden) { $fragments['where'][] = 't.hidden = 0 OR t.hidden IS NULL'; } // 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) { $fragments['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) < %d'; $fragments['query_args']['where'][] = $limit; } elseif (!empty($threads) || !empty($user)) { // Only add a sort when we are not loading the tags for the admin page. // Sorting is handled through tablesort_sql() then. $fragments['order_by'][] = 't.tag ASC'; } } /** * Query definition to get autocomplete suggestions for tags * * @param $fragments * Query fragments array. * @param $search * String fragment to use for tag suggestions. * @param $tags * Array of tags not to be used as suggestions. */ function privatemsg_filter_sql_tags_autocomplete(&$fragments, $search, $tags) { global $user; $fragments['primary_table'] = '{pm_tags} pmt'; $fragments['select'][] = 'pmt.tag'; $fragments['where'][] = "pmt.tag LIKE '%s'"; // Escape % to get through the placeholder replacement. $fragments['query_args']['where'][] = $search .'%%'; if (!empty($tags)) { // Exclude tags. $fragments['where'][] = "pmt.tag NOT IN (". db_placeholders($tags, 'text') .")"; $fragments['query_args']['where'] += $tags; } // LEFT JOIN to be able to load public, unused tags. $fragments['inner_join'][] = 'LEFT JOIN {pm_tags_index} pmti ON pmt.tag_id = pmti.tag_id AND pmti.uid = %d'; $fragments['query_args']['join'][] = $user->uid; // Autocomplete should only display Tags used by that user or public tags. // This is done to avoid information disclosure as part of tag names. $fragments['where'][] = '(pmti.uid IS NOT NULL OR pmt.public = 1)'; $fragments['order_by'][] = 'pmt.tag ASC'; } /** * @} */ /** * Implement hook_user(). */ function privatemsg_filter_user($op, &$edit, &$account, $category = NULL) { switch ($op) { case 'delete': // Delete tag information of that user. db_query("DELETE FROM {pm_tags_index} WHERE uid = %d", $account->uid, $account->uid); break; } } /** * Implements hook_privatemsg_message_insert(). */ function privatemsg_filter_privatemsg_message_insert($message) { foreach ($message['recipients'] as $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)); } } /** * Form callback for adding a tag to threads. */ function privatemsg_filter_add_tag_submit($form, &$form_state) { // Check if textfield is not empty. if (empty($form_state['values']['tag-add'])) { return; } $tags = explode(',', $form_state['values']['tag-add']); $tag_ids = privatemsg_filter_create_tags($tags); if (empty($tag_ids)) { return; } $operation = array( 'callback' => 'privatemsg_filter_add_tags', 'callback arguments' => array('tag_id' => $tag_ids), 'sucess message' => t('The selected conversations have been tagged.'), 'undo callback' => 'privatemsg_filter_remove_tags', 'undo callback arguments' => array('tag_id' => $tag_ids), ); privatemsg_operation_execute($operation, $form_state['values']['threads']); } /** * Form callback for removing a tag to threads. */ function privatemsg_filter_remove_tag_submit($form, &$form_state) { $operation = array( 'callback' => 'privatemsg_filter_remove_tags', 'callback arguments' => array('tag_id' => $form_state['values']['tag-remove']), 'success message' => t('The tag has been removed from the selected conversations.'), 'undo callback' => 'privatemsg_filter_add_tags', 'undo callback arguments' => array('tag_id' => $form_state['values']['tag-remove']), ); privatemsg_operation_execute($operation, $form_state['values']['threads']); }