You are here

privatemsg_filter.module in Privatemsg 7.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_permission().
 */
function privatemsg_filter_permission() {
  return array(
    'filter private messages' => array(
      'title' => t('Filter private messages'),
      'description' => t('Use the search and filter widget'),
    ),
    'tag private messages' => array(
      'title' => t('Tag private messages'),
      'description' => t('Tag private messages'),
    ),
    'create private message tags' => array(
      'title' => t('Create private message tags'),
      'description' => t('Create new private message tags'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function privatemsg_filter_menu() {
  $items['admin/config/messaging/privatemsg/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,
  );
  $items['admin/config/messaging/privatemsg/tags/list'] = array(
    'title' => 'List',
    'description' => 'Configure tags.',
    'page callback' => 'privatemsg_tags_admin',
    'file' => 'privatemsg_filter.admin.inc',
    'access arguments' => array(
      'administer privatemsg settings',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -5,
  );
  $items['admin/config/messaging/privatemsg/tags/add'] = array(
    'title' => 'Add tag',
    'description' => 'Configure tags.',
    '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_ACTION,
  );
  $items['admin/config/messaging/privatemsg/tags/rebuild'] = array(
    'title' => 'Rebuild inbox',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'privatemsg_filter_inbox_rebuild_form',
    ),
    'file' => 'privatemsg_filter.admin.inc',
    'access arguments' => array(
      'administer privatemsg settings',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/messaging/privatemsg/tags/edit/%'] = array(
    'title' => 'Edit tag',
    'description' => 'Configure tags.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'privatemsg_tags_form',
      6,
    ),
    'file' => 'privatemsg_filter.admin.inc',
    'access arguments' => array(
      'administer privatemsg settings',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/messaging/privatemsg/tags/delete/%'] = array(
    'title' => 'Delete tag',
    'description' => 'Configure tags.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'privatemsg_filter_tags_delete',
      6,
    ),
    'file' => 'privatemsg_filter.admin.inc',
    'access arguments' => array(
      'administer privatemsg settings',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['messages/inbox'] = array(
    'title' => 'Inbox',
    'page callback' => 'privatemsg_list_page',
    'page arguments' => array(
      'inbox',
    ),
    'file' => 'privatemsg.pages.inc',
    'file path' => drupal_get_path('module', 'privatemsg'),
    'access callback' => 'privatemsg_user_access',
    'type' => variable_get('privatemsg_filter_default_list', 0) ? MENU_LOCAL_TASK : MENU_DEFAULT_LOCAL_TASK,
    'weight' => -15,
    'menu_name' => 'user-menu',
  );
  $items['messages/sent'] = array(
    'title' => 'Sent messages',
    'page callback' => 'privatemsg_list_page',
    'page arguments' => array(
      'sent',
    ),
    'file' => 'privatemsg.pages.inc',
    'file path' => drupal_get_path('module', 'privatemsg'),
    'access callback' => 'privatemsg_user_access',
    'type' => MENU_LOCAL_TASK,
    'weight' => -12,
    'menu_name' => 'user-menu',
  );
  $items['messages/filter/autocomplete'] = array(
    'page callback' => 'privatemsg_autocomplete',
    'file' => 'privatemsg.pages.inc',
    'file path' => drupal_get_path('module', 'privatemsg'),
    'access callback' => 'privatemsg_user_access',
    'access arguments' => array(
      'write privatemsg',
    ),
    'type' => MENU_CALLBACK,
    'weight' => -10,
  );
  $items['messages/filter/tag-autocomplete'] = array(
    'page callback' => 'privatemsg_filter_tags_autocomplete',
    'file' => 'privatemsg_filter.pages.inc',
    'access callback' => 'privatemsg_user_access',
    'access arguments' => array(
      'tag private messages',
    ),
    'type' => MENU_CALLBACK,
    'weight' => -10,
  );
  return $items;
}

/**
 * Implement hook_menu_alter().
 */
function privatemsg_filter_menu_alter(&$items) {

  // Rename messages to "All messages".
  $items['messages/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['messages']['page arguments'] = array(
      'inbox',
    );
    $items['messages/list']['type'] = MENU_LOCAL_TASK;
  }
}

/**
 * Implements hook_form_FORM_ID_alter() to 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['privatemsg_listing']['privatemsg_filter_searchbody'] = array(
    '#type' => 'checkbox',
    '#title' => t('Search message body'),
    '#description' => t('WARNING: turning on this feature will slow down search performance by a large factor. Gets worse as your messages database increases.'),
    '#default_value' => variable_get('privatemsg_filter_searchbody', FALSE),
  );
  $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_query("SELECT tag_id FROM {pm_tags} WHERE tag = :tag", array(
      ':tag' => $tag,
    ))
      ->fetchField();
    if (empty($tag_id) && privatemsg_user_access('create private message tags')) {
      $tag_id = db_insert('pm_tags')
        ->fields(array(
        'tag' => $tag,
      ))
        ->execute();
    }
    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 = 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.
      db_merge('pm_tags_index')
        ->key(array(
        'tag_id' => $tag_id,
        'uid' => $account->uid,
        'thread_id' => $thread,
      ))
        ->execute();
    }
  }
}

/**
 * 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 (empty($account)) {
    global $user;
    $account = $user;
  }
  if (is_null($tag_ids)) {

    //Delete all tag mapping - all except for the inbox tag if it exists.
    db_delete('pm_tags_index')
      ->condition('uid', $account->uid)
      ->condition('thread_id', $threads)
      ->condition('tag_id', variable_get('privatemsg_filter_inbox_tag', ''), '<>')
      ->execute();
  }
  else {
    if (!is_array($tag_ids)) {
      $tag_ids = array(
        $tag_ids,
      );
    }

    //Delete tag mapping for the specified tag.
    db_delete('pm_tags_index')
      ->condition('uid', $account->uid)
      ->condition('thread_id', $threads)
      ->condition('tag_id', $tag_ids)
      ->execute();
  }
}
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.
  return $tag_data = _privatemsg_assemble_query(array(
    'tags',
    'privatemsg_filter',
  ), $account)
    ->execute()
    ->fetchAllKeyed();
}
function privatemsg_filter_dropdown(&$form_state, $account) {
  $form['#attached']['css'][] = drupal_get_path('module', 'privatemsg_filter') . '/privatemsg_filter.css';
  $form['filter'] = array(
    '#type' => 'fieldset',
    '#title' => t('Filter Messages'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#weight' => -20,
    // 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'),
  );
  $form['filter']['search'] = array(
    '#type' => 'textfield',
    '#title' => variable_get('privatemsg_filter_searchbody', FALSE) ? t('By message text') : 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']['actions'] = array(
    '#type' => 'actions',
    '#attributes' => array(
      'class' => array(
        'privatemsg-filter-actions',
      ),
    ),
  );
  $form['filter']['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Filter'),
    '#weight' => 10,
    '#submit' => array(
      'privatemsg_filter_dropdown_submit',
    ),
  );
  $form['filter']['actions']['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save filter'),
    '#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']['actions']['reset'] = array(
    '#type' => 'submit',
    '#value' => t('Reset'),
    '#weight' => 12,
    '#submit' => array(
      'privatemsg_filter_dropdown_submit',
    ),
  );
}
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'], array(
        'query' => 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 (:tags)';
    $query['tags'] = db_query($sql, array(
      ':tags' => $filter['tags'],
    ))
      ->fetchCol();
    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 = array_shift(privatemsg_user_load_multiple(array(
        $author,
      ))))) {
        $query['author'][] = privatemsg_recipient_format($author_obj, 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() to add a filter widget to the message listing pages.
 */
function privatemsg_filter_form_privatemsg_list_alter(&$form, $form_state) {
  global $user;
  if (privatemsg_user_access('filter private messages') && !empty($form['updated']['list']['#options']) || privatemsg_filter_get_filter($user)) {
    $form += privatemsg_filter_dropdown($form_state, $form['account']['#value']);
  }
  $fields = privatemsg_get_enabled_headers();
  if (privatemsg_user_access('tag private messages') && in_array('tags', $fields) && !empty($form['updated']['list']['#options'])) {

    // Load thread id's of the current list.
    $threads = array_keys($form['updated']['list']['#options']);

    // Fetch all tags of those threads.
    $query = _privatemsg_assemble_query(array(
      'tags',
      'privatemsg_filter',
    ), $user, $threads, 3);

    // Add them to tableselect options.
    foreach ($query
      ->execute() as $tag) {
      $form['updated']['list']['#options'][$tag->thread_id]['tags'][$tag->tag_id] = $tag->tag;
    }

    // Avoid notices for threads without tags.
    foreach ($form['updated']['list']['#options'] as &$thread) {
      if (empty($thread['tags'])) {
        $thread['tags'] = array();
      }
    }
  }
  if (privatemsg_user_access('tag private messages') && !empty($form['updated']['list']['#options'])) {
    $form['updated']['actions']['tag-add'] = array(
      '#type' => 'textfield',
      '#size' => 15,
      '#autocomplete_path' => 'messages/filter/tag-autocomplete',
    );
    $form['updated']['actions']['tag-add-submit'] = array(
      '#type' => 'submit',
      '#value' => t('Apply Tag'),
      '#submit' => array(
        'privatemsg_filter_add_tag_submit',
      ),
      '#ajax' => array(
        'callback' => 'privatemsg_list_js',
        'wrapper' => 'privatemsg-list-form',
        'effect' => 'fade',
      ),
    );
    $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['updated']['actions']['tag-remove'] = array(
        '#type' => 'select',
        '#options' => $options,
        '#default_value' => 0,
        '#ajax' => array(
          'callback' => 'privatemsg_list_js',
          'wrapper' => 'privatemsg-list-form',
          'effect' => 'fade',
        ),
        '#submit' => array(
          'privatemsg_filter_remove_tag_submit',
        ),
        '#executes_submit_callback' => TRUE,
      );
      $form['updated']['actions']['tag-remove-submit'] = array(
        '#type' => 'submit',
        '#value' => t('Remove Tag'),
        '#submit' => array(
          'privatemsg_filter_remove_tag_submit',
        ),
        '#attributes' => array(
          'class' => array(
            'form-item',
          ),
        ),
        '#states' => array(
          'visible' => array(
            // This is never true, button is always hidden when JS is enabled.
            ':input[name=operation]' => array(
              'value' => 'fake',
            ),
          ),
        ),
      );
    }
  }
}

/**
 * Form callback for removing a tag to threads.
 */
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' => array(
        '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 theme_privatemsg_list_field__tags($arguments) {
  $thread = $arguments['thread'];
  if (!empty($thread['tags'])) {
    $tags = array();
    foreach ($thread['tags'] as $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' => array(
        'privatemsg-list-tags',
      ),
    );
  }

  // Return an empty row.
  return array(
    'data' => '',
  );
}

/**
 * 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,
    ),
    'success 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']['list']);
  $form_state['rebuild'] = TRUE;
  $form_state['input'] = array();
}

/**
 * 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']['list']);
  $form_state['rebuild'] = TRUE;
  $form_state['input'] = array();
}

/**
 * Hook into the query builder to add the tagging info to the correct query
 */
function privatemsg_filter_query_privatemsg_list_alter($query) {
  $account = $query
    ->getMetaData('arg_1');
  $argument = $query
    ->getMetaData('arg_2');

  // Add all conditions to the count query too.
  $count_query = $query
    ->getCountQuery();

  // Check if its a filtered view.
  if ($argument == 'sent') {
    $query
      ->condition('pm.author', $account->uid);
    $count_query
      ->condition('pm.author', $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) {
    if (!empty($filter['tags'])) {
      foreach ($filter['tags'] as $tag) {
        $alias = $query
          ->join('pm_tags_index', 'pmti', "%alias.thread_id = pmi.thread_id AND %alias.uid = pmi.recipient AND pmi.type IN ('user', 'hidden')");
        $query
          ->condition($alias . '.tag_id', $tag);
        $alias = $count_query
          ->join('pm_tags_index', 'pmti', "%alias.thread_id = pmi.thread_id AND %alias.uid = pmi.recipient AND pmi.type IN ('user', 'hidden')");
        $count_query
          ->condition($alias . '.tag_id', $tag);
      }
    }
    if (isset($filter['author']) && !empty($filter['author'])) {
      foreach ($filter['author'] as $author) {
        $alias = $query
          ->join('pm_index', 'pmi', '%alias.mid = pm.mid');
        $query
          ->condition($alias . '.recipient', $author->uid);
        $query
          ->condition($alias . '.type', 'user');
        $alias = $count_query
          ->join('pm_index', 'pmi', '%alias.mid = pm.mid');
        $count_query
          ->condition($alias . '.recipient', $author->uid);
        $count_query
          ->condition($alias . '.type', 'user');
      }
    }
    if (!empty($filter['search'])) {
      if (variable_get('privatemsg_filter_searchbody', FALSE)) {
        $search = db_or()
          ->condition('pm.subject', '%' . $filter['search'] . '%', 'LIKE')
          ->condition('pm.body', '%' . $filter['search'] . '%', 'LIKE');

        // Clone the condition so that they are both compiled.
        $query
          ->condition(clone $search);
        $count_query
          ->condition($search);
      }
      else {
        $query
          ->condition('pm.subject', '%' . $filter['search'] . '%', 'LIKE');
        $count_query
          ->condition('pm.subject', '%' . $filter['search'] . '%', 'LIKE');
      }
    }
  }
}

/**
 * Implements hook_privatemsg_view_alter().
 */
function privatemsg_filter_privatemsg_view_alter(&$content) {
  if (privatemsg_user_access('tag private messages')) {
    $content['tags'] = privatemsg_filter_show_tags($content['#thread']['thread_id'], !empty($_GET['show_tags_form']));
  }
}
function privatemsg_filter_show_tags($thread_id, $show_form) {
  global $user;
  $element = array(
    '#prefix' => '<div id="privatemsg-filter-tags">',
    '#suffix' => '</div>',
    '#weight' => -10,
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'privatemsg_filter') . '/privatemsg_filter.css',
      ),
    ),
  );
  if (!$show_form) {
    $query = _privatemsg_assemble_query(array(
      'tags',
      'privatemsg_filter',
    ), $user, array(
      $thread_id,
    ));
    if ($query
      ->countQuery()
      ->execute()
      ->fetchField() == 0) {
      $element['link'] = array(
        '#type' => 'link',
        '#href' => $_GET['q'],
        '#options' => array(
          'query' => array(
            'show_tags_form' => TRUE,
          ),
          'attributes' => array(
            'class' => array(
              'privatemsg-filter-tags-add',
            ),
          ),
        ),
        '#title' => t('Tag this conversation'),
      );
    }
    else {
      $element['label'] = array(
        '#prefix' => '<span class="privatemsg-filter-tags-label">',
        '#suffix' => '</span>',
        '#markup' => t('Tags:'),
      );
      foreach ($query
        ->execute()
        ->fetchCol(1) as $tag) {
        $element['tags'][] = array(
          '#type' => 'link',
          '#title' => $tag,
          '#href' => 'messages',
          '#options' => array(
            'attributes' => array(
              'title' => $tag,
            ),
            'query' => array(
              'tags' => $tag,
            ),
          ),
        );
      }
      $element['link'] = array(
        '#type' => 'link',
        '#href' => $_GET['q'],
        '#options' => array(
          'query' => array(
            'show_tags_form' => TRUE,
          ),
          'attributes' => array(
            'class' => array(
              'privatemsg-filter-tags-modify',
            ),
          ),
        ),
        '#title' => t('(modify tags)'),
      );
    }
    return $element;
  }
  else {
    return drupal_get_form('privatemsg_filter_form', $thread_id) + $element;
  }
}

/**
 * Form to show and allow modification of tagging information for a conversation.
 */
function privatemsg_filter_form($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,
  ));
  $tags = implode(', ', $query
    ->execute()
    ->fetchCol(1));
  $form['user_id'] = array(
    '#type' => 'value',
    '#value' => $user->uid,
  );
  $form['thread_id'] = array(
    '#type' => 'value',
    '#value' => $thread_id,
  );
  $form['tags'] = array(
    '#type' => 'textfield',
    '#title' => t('Tags for this conversation'),
    '#title_display' => 'invisible',
    '#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(
    '#type' => 'link',
    '#href' => $_GET['q'],
    '#title' => t('Cancel'),
    '#attributes' => array(
      'id' => 'privatemsg-filter-tags-cancel',
    ),
    '#weight' => 50,
  );
  return $form;
}

/**
 * Form builder function, display a form to modify tags on a thread.
 */
function privatemsg_filter_form_submit($form, &$form_state) {
  $tags = explode(',', $form_state['values']['tags']);

  // Step 1 - Delete all tag mapping.
  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);
  }
  $form_state['redirect'] = current_path();
  drupal_set_message(t('Your conversation tags have been saved.'));
}

/**
 * Limit the user autocomplete for the filter widget.
 */
function privatemsg_filter_query_privatemsg_autocomplete_alter($query) {
  global $user;
  if (arg(1) == 'filter') {
    $query
      ->join('pm_index', 'pip', "pip.recipient = u.uid AND pip.type = 'user'");
    $query
      ->join('pm_index', 'piu', "piu.recipient = :uid_index AND piu.type = 'user' AND pip.mid = piu.mid", array(
      ':uid_index' => $user->uid,
    ));
  }
}

/**
 * Query definition to get the tags in use by the specified user.
 *
 * @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($user = NULL, $threads = NULL, $limit = NULL, $showHidden = FALSE) {
  $query = db_select('pm_tags', 't')
    ->fields('t', array(
    'tag_id',
    'tag',
    'public',
  ))
    ->orderBy('t.tag', 'ASC');
  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', $threads);
  }
  else {
    $query
      ->addExpression('COUNT(ti.thread_id)', 'count');
    $query
      ->leftJoin('pm_tags_index', 'ti', 'ti.tag_id = t.tag_id');
    $query
      ->groupBy('t.tag_id')
      ->groupBy('t.tag')
      ->groupBy('t.public');
  }
  if (!empty($user)) {
    $query
      ->condition('ti.uid', $user->uid);
  }
  if (!$showHidden) {
    $query
      ->condition(db_or()
      ->condition('t.hidden', 0)
      ->isNull('t.hidden'));
  }

  // 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;
}

/**
 * Query definition to get autocomplete suggestions for tags
 *
 * @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($search, $tags) {
  $query = db_select('pm_tags', 'pmt')
    ->fields('pmt', array(
    'tag',
  ))
    ->condition('pmt.tag', $search . '%%', 'LIKE')
    ->orderBy('pmt.tag', 'ASC')
    ->range(0, 10);
  if (!empty($tags)) {
    $query
      ->condition('pmt.tag', $tags, 'NOT IN');
  }
  return $query;
}

/**
 * Implements hook_user_cancel().
 */
function privatemsg_filter_user_cancel($edit, $account, $method) {

  // Always delete since this is only visible for the user anyway.
  db_delete('pm_tags_index')
    ->condition('uid', $account->uid)
    ->execute();
}

/**
 * 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,
    ));
  }
}

Functions

Namesort descending Description
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 for a conversation.
privatemsg_filter_form_privatemsg_admin_settings_alter Implements hook_form_FORM_ID_alter() to add a filter widget to the message listing pages.
privatemsg_filter_form_privatemsg_list_alter Implements hook_form_FORM_ID_alter() to add a filter widget to the message listing pages.
privatemsg_filter_form_submit Form builder function, display a form to modify tags on a thread.
privatemsg_filter_get_filter
privatemsg_filter_get_tags_data
privatemsg_filter_menu Implements hook_menu().
privatemsg_filter_menu_alter Implement hook_menu_alter().
privatemsg_filter_permission Implements hook_permission().
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_thread_operations Form callback for removing a tag to threads.
privatemsg_filter_privatemsg_view_alter Implements hook_privatemsg_view_alter().
privatemsg_filter_query_privatemsg_autocomplete_alter Limit the user autocomplete for the filter widget.
privatemsg_filter_query_privatemsg_list_alter Hook into the query builder to add the tagging info to the correct query
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 get the tags in use by the specified user.
privatemsg_filter_sql_tags_autocomplete Query definition to get autocomplete suggestions for tags
privatemsg_filter_user_cancel Implements hook_user_cancel().
theme_privatemsg_list_field__tags Default theme pattern function to display tags.