You are here

privatemsg_filter.module in Privatemsg 6.2

Allows users to tag private messages and to filter based upon those tags.

File

privatemsg_filter/privatemsg_filter.module
View source
<?php

/**
 * @file
 * Allows users to tag private messages and to filter based upon those tags.
 */

/**
 * Implements hook_perm().
 */
function privatemsg_filter_perm() {
  return array(
    'filter private messages',
    'tag private messages',
    'create private message tags',
  );
}

/**
 * Implements hook_menu().
 */
function privatemsg_filter_menu() {
  $url_prefix = variable_get('privatemsg_url_prefix', 'messages');
  $user_arg_position = array_search('%user', explode('/', $url_prefix));
  $items['admin/settings/messages/filter'] = array(
    'title' => '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.'),
  );
  $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' => '<div id="privatemsg-filter-buttons">',
    '#weight' => 10,
    '#submit' => array(
      'privatemsg_filter_dropdown_submit',
    ),
  );
  $form['filter']['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save filter'),
    '#suffix' => '</div>',
    '#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. <a href="@remove_filter_url">Click here to remove this filter</a>.', 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' => '</div>',
    '#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 = privatemsg_get_enabled_headers();
  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'     => '<div class="privatemsg-tag-add-submit">',

      //'#suffix'    => '</div>',
      '#type' => 'submit',
      '#value' => t('Apply Tag'),
      '#submit' => array(
        'privatemsg_filter_add_tag_submit',
      ),
    );
    $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' => '<div class="privatemsg-tag-remove-submit">',
        '#suffix' => '</div>',
        '#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,
    );
  }
}

/**
 * Implements hook_privatemsg_header_info().
 */
function privatemsg_filter_privatemsg_header_info() {
  return array(
    'tags' => array(
      'data' => t('Tags'),
      'class' => 'privatemsg-header-tags',
      '#weight' => -23,
      '#access' => privatemsg_user_access('tag private messages'),
    ),
  );
}

/**
 * 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' => '<div id="privatemsg-filter-tags">',
    '#suffix' => '</div>',
    '#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' => '<span class="privatemsg-filter-tags-label">' . t('Tags:') . '</span>',
        '#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' => '<div id="privatemsg-filter-tags">',
    '#suffix' => '</div>',
    '#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']);
}

Functions

Namesort descending Description
phptemplate_privatemsg_list_field__tags Default theme pattern function to display tags.
privatemsg_filter_add_tags Tag one or multiple threads with a tag.
privatemsg_filter_add_tag_submit Form callback for adding a tag to threads.
privatemsg_filter_create_get_query Creates a GET query based on the selected filters.
privatemsg_filter_create_tags Function to create a tag
privatemsg_filter_dropdown
privatemsg_filter_dropdown_set_active
privatemsg_filter_dropdown_submit
privatemsg_filter_form Form to show and allow modification of tagging information of a conversation.
privatemsg_filter_form_privatemsg_admin_settings_alter Implements hook_form_FORM_ID_alter().
privatemsg_filter_form_privatemsg_list_alter Implements hook_form_FORM_ID_alter().
privatemsg_filter_form_submit
privatemsg_filter_get_filter
privatemsg_filter_get_tags_data
privatemsg_filter_menu Implements hook_menu().
privatemsg_filter_menu_alter Implements hook_menu_alter().
privatemsg_filter_perm Implements hook_perm().
privatemsg_filter_privatemsg_header_info Implements hook_privatemsg_header_info().
privatemsg_filter_privatemsg_message_insert Implements hook_privatemsg_message_insert().
privatemsg_filter_privatemsg_message_recipient_changed Implements hook_privatemsg_message_recipient_changed().
privatemsg_filter_privatemsg_sql_autocomplete_alter Limit the user autocomplete for the filter widget.
privatemsg_filter_privatemsg_sql_list_alter Hook into the query builder to add the tagging info to the correct query
privatemsg_filter_privatemsg_thread_operations Implements hook_privatemsg_thread_operations().
privatemsg_filter_privatemsg_view_messages_alter Hook into the view messages page to add a form for tagging purposes.
privatemsg_filter_remove_tags Remove tag from one or multiple threads.
privatemsg_filter_remove_tag_submit Form callback for removing a tag to threads.
privatemsg_filter_settings_submit Rebuilding the menu if necessary.
privatemsg_filter_show_tags
privatemsg_filter_sql_tags Query definition to fetch tags.
privatemsg_filter_sql_tags_autocomplete Query definition to get autocomplete suggestions for tags
privatemsg_filter_tags_autocomplete Return autocomplete results for tags.
privatemsg_filter_user Implement hook_user().