Skip to content
privatemsg.module 60.3 KiB
Newer Older
litwol's avatar
litwol committed
<?php
// $Id$

/**
 * @file
 * Allows users to send private messages to other users.
 */
/**
 * Status constant for read messages.
 */
define('PRIVATEMSG_READ', 0);
/**
 * Status constant for unread messages.
 */
define('PRIVATEMSG_UNREAD', 1);
/**
 * Show unlimited messages in a thread.
 */
define('PRIVATEMSG_UNLIMITED', 'unlimited');
Marco Molinari's avatar
Marco Molinari committed

Zen's avatar
Zen committed
/**
 * Implements hook_permission().
Zen's avatar
Zen committed
 */
function privatemsg_permission() {
litwol's avatar
litwol committed
  return array(
    'administer privatemsg settings' => array(
      'title' => t('Administer privatemsg'),
      'description' => t('Perform maintenance tasks for privatemsg'),
    ),
    'read privatemsg' => array(
      'title' => t('Read private messages'),
      'description' => t('Read private messages'),
    ),
    'read all private messages' => array(
      'title' => t('Read all private messages'),
      'description' => t('Includes messages of other users'),
    ),
    'write privatemsg' => array(
      'title' => t('Write new private messages'),
      'description' => t('Write new private messages'),
    ),
    'delete privatemsg' => array(
      'title' => t('Delete private messages'),
      'description' => t('Delete private messages'),
    ),
/**
 * Generate aray of user objects based on a string.
 *
 *
 * @param $userstring
 *   A string with user id, for example 1,2,4. Returned by the list query
 *
 * @return
 *   Array with user objects.
 */
function _privatemsg_generate_user_array($userstring, $slice = NULL) {
  // Convert user uid list (uid1,uid2,uid3) into an array. If $slice is not NULL
  // pass that as argument to array_slice(). For example, -4 will only load the
  // last four users.
  // This is done to avoid loading user objects that are not displayed, for
  // obvious performance reasons.
  $users = explode(',', $userstring);
  if (!is_null($slice)) {
    $users = array_slice($users, $slice);
  }
  return user_load_multiple($users);
/**
 * Format an array of user objects.
 *
 * @param $part_array
 *   Array with user objects, for example the one returnd by
 *   _privatemsg_generate_user_array.
 *
 * @param $limit
 *   Limit the number of user objects which should be displayed.
 * @param $no_text
 *   When TRUE, don't display the Participants/From text.
 * @return
 *   String with formated user objects, like user1, user2.
 */
function _privatemsg_format_participants($part_array, $limit = NULL, $no_text = FALSE) {
  if (count($part_array) > 0) {
    $limited = FALSE;
    foreach ($part_array as $account) {
        $limited = TRUE;
      $to[] = theme('username', array('account' => $account));
    }

    $limit_string = '';
    if ($limited) {
      $limit_string = t(' and others');
    }


    if ($no_text) {
      return implode(', ', $to) . $limit_string;
    }

    $last = array_pop($to);
    if (count($to) == 0) { // Only one participant
      return t("From !last", array('!last' => $last));
    }
    else { // Multipe participants..
      $participants = implode(', ', $to);
      return t('Participants: !participants and !last', array('!participants' => $participants, '!last' => $last));
litwol's avatar
litwol committed
 * Implementation of hook_menu().
litwol's avatar
litwol committed
function privatemsg_menu() {
litwol's avatar
litwol committed
  $items['messages'] = array(
    'title'            => 'Messages',
    'title callback'   => 'privatemsg_title_callback',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_list', 'list'),
    'file'             => 'privatemsg.pages.inc',
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'type'             => MENU_NORMAL_ITEM,
    'menu_name'        => 'user-menu',
litwol's avatar
litwol committed
  );
  $items['messages/list'] = array(
    'title'            => 'Messages',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_list', 'list'),
    'file'             => 'privatemsg.pages.inc',
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'type'             => MENU_DEFAULT_LOCAL_TASK,
    'weight'           => -10,
    'menu_name'        => 'user-menu',
litwol's avatar
litwol committed
  );
  $items['messages/view/%privatemsg_thread'] = array(
    // Set the third argument to TRUE so that we can show access denied instead
    // of not found.
    'load arguments'   => array(NULL, NULL, TRUE),
litwol's avatar
litwol committed
    'page callback'    => 'privatemsg_view',
    'page arguments'   => array(2),
    'file'             => 'privatemsg.pages.inc',
    'access callback'  => 'privatemsg_view_access',
    'menu_name'        => 'user-menu',
litwol's avatar
litwol committed
  );
  $items['messages/delete/%privatemsg_thread/%privatemsg_message'] = array(
litwol's avatar
litwol committed
    'title'            => 'Delete message',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_delete', 2, 3),
    'file'             => 'privatemsg.pages.inc',
    'access callback'  => 'privatemsg_user_access',
    'access arguments' => array('delete privatemsg'),
litwol's avatar
litwol committed
    'type'             => MENU_CALLBACK,
    'weight'           => -10,
    'menu_name'        => 'user-menu',
litwol's avatar
litwol committed
  );
  $items['messages/new'] = array(
    'title'            => 'Write new message',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_new', 2, 3, NULL),
    'file'             => 'privatemsg.pages.inc',
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'access arguments' => array('write privatemsg'),
    'menu_name'        => 'user-menu',
litwol's avatar
litwol committed
  );
  // Auto-completes available user names & removes duplicates.
  $items['messages/user-name-autocomplete'] = array(
    'page callback'    => 'privatemsg_user_name_autocomplete',
    'file'             => 'privatemsg.pages.inc',
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'access arguments' => array('write privatemsg'),
litwol's avatar
litwol committed
    'type'             => MENU_CALLBACK,
  );
  $items['admin/config/messaging'] = array(
    'title' => 'Messaging',
    'description' => 'Messaging systems.',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('access administration pages'),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/config/messaging/privatemsg'] = array(
litwol's avatar
litwol committed
    'title'            => 'Private messages',
    'description'      => 'Configure private messaging settings.',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_admin_settings'),
    'file'             => 'privatemsg.admin.inc',
litwol's avatar
litwol committed
    'access arguments' => array('administer privatemsg settings'),
    'type'             => MENU_NORMAL_ITEM,
  $items['admin/config/messaging/privatemsg/default'] = array(
    'title'            => 'Private messages',
    'description'      => 'Configure private messaging settings.',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_admin_settings'),
    'file'             => 'privatemsg.admin.inc',
    'access arguments' => array('administer privatemsg settings'),
    'type'             => MENU_DEFAULT_LOCAL_TASK,
    'weight'           => -10,
  );
  $items['messages/undo/action'] = array(
    'title'            => 'Private messages',
    'description'      => 'Undo last thread action',
    'page callback'    => 'privatemsg_undo_action',
    'file'             => 'privatemsg.pages.inc',
    'access arguments' => array('read privatemsg'),
    'type'             => MENU_CALLBACK,
  $items['user/%/messages'] = array(
    'title' => 'Messages',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_list', 'list', 1),
    'file'             => 'privatemsg.pages.inc',
    'access callback'  => 'privatemsg_user_access',
    'access arguments' => array('read all private messages'),
litwol's avatar
litwol committed
  return $items;
 * Privatemsg  wrapper for user_access.
 *
 * Never allows anonymous user access as that doesn't makes sense.
 * @param $permission
 *   Permission string, defaults to read privatemsg
 * @return
 *   TRUE if user has access, FALSE if not
 * @ingroup api
 */
function privatemsg_user_access($permission = 'read privatemsg', $account = NULL) {
  if ( $account === NULL ) {
    global $user;
    $account = $user;
  }
  if (!$account->uid) { // Disallow anonymous access, regardless of permissions
    return FALSE;
  }
  if (privatemsg_is_disabled($account) && ($permission == 'write privatemsg') ) {
    return FALSE;
  }
  if (!user_access($permission, $account)) {
    return FALSE;
  }
  return TRUE;
}

/**
 * Check access to the view messages page.
 *
 * Function to restrict the access of the view messages page to just the
 * messages/view/% pages and not to leave tabs artifact on other lower
 * level pages such as the messages/new/%.
 *
 * @param $thread
 *   A array containing all information about a specific thread, generated by
 *   privatemsg_thread_load().
 *
 * @ingroup api
function privatemsg_view_access($thread) {
  // Do not allow access to threads without messages.
  if (empty($thread['messages'])) {
    // Count all messages, if there
    return FALSE;
  }
  if (privatemsg_user_access('read privatemsg') && arg(1) == 'view') {
    return TRUE;
  }
  return FALSE;
}

/**
 * Checks the status of private messaging for provided user.
 *
 * @param user object to check
 * @return TRUE if user has disabled private messaging, FALSE otherwise
 */
function privatemsg_is_disabled($account) {

  if (!$account || !$account->uid) {
    return FALSE;
  }

  if (!isset($account->privatemsg_disabled)) {
    $account->privatemsg_disabled = (bool)db_query('SELECT 1 FROM {pm_disable} WHERE uid = :uid ', array(':uid' => $account->uid))->fetchField();
  }

  return ($account->privatemsg_disabled);
}

 * Load a thread with all the messages and participants.
 *
 * This function is called by the menu system through the %privatemsg_thread
 * wildcard.
 * @param $thread_id
 *   Thread id, pmi.thread_id or pm.mid of the first message in that thread.
 * @param $account
 *   User object for which the thread should be loaded, defaults to
 *   the current user.
 * @param $start
 *   Message offset from the start of the thread.
 * @param $useAccessDenied
 *   Set to TRUE if the function should forward to the access denied page
 *   instead of not found. This is used by the menu system because that does
 *   load arguments before access checks are made. Defaults to FALSE.
 * @return
 *   $thread object, with keys messages, participants, title and user. messages
 *   contains an array of messages, participants an array of user, subject the
 *   subject of the thread and user the user viewing the thread.
 *
 *   If no messages are found, or the thread_id is invalid, the function returns
 *   FALSE.

 * @ingroup api
function privatemsg_thread_load($thread_id, $account = NULL, $start = NULL, $useAccessDenied = FALSE) {
  $threads = &drupal_static(__FUNCTION__, array());
      $account = clone $user;
    if (!isset($threads[$account->uid])) {
      $threads[$account->uid] = array();
    }
    
    if (!array_key_exists($thread_id, $threads[$account->uid])) {
      // Load the list of participants.
      $thread['participants'] = _privatemsg_assemble_query('participants', $thread_id)->execute()->fetchAllAssoc('uid');
      $thread['read_all'] = FALSE;
      if (!array_key_exists($account->uid, $thread['participants']) && privatemsg_user_access('read all private messages', $account)) {
        $thread['read_all'] = TRUE;
      }

      // Load messages returned by the messages query with privatemsg_message_load_multiple().
      $query = _privatemsg_assemble_query('messages', array($thread_id), $thread['read_all'] ? NULL : $account);
      // Use subquery to bypass group by since it is not possible to alter
      // existing GROUP BY statements.
      $countQuery = db_select($query);
      $countQuery->addExpression('COUNT(*)');
      $thread['message_count'] = $thread['to'] = $countQuery->execute()->fetchField();
      $thread['from'] = 1;
      // Check if we need to limit the messages.
      $max_amount = variable_get('privatemsg_view_max_amount', 20);

      // If there is no start value, select based on get params.
      if (is_null($start)) {
        if (isset($_GET['start']) && $_GET['start'] < $thread['message_count']) {
          $start = $_GET['start'];
        }
        elseif (!variable_get('privatemsg_view_use_max_as_default', FALSE) && $max_amount == PRIVATEMSG_UNLIMITED) {
          $start = PRIVATEMSG_UNLIMITED;
        }
          $start = $thread['message_count'] - (variable_get('privatemsg_view_use_max_as_default', FALSE) ? variable_get('privatemsg_view_default_amount', 10) : $max_amount);
          $last_page = 0;
          $max_amount = $thread['message_count'];
        }
        else {
          // Calculate the number of messages on the "last" page to avoid
          // message overlap.
          // Note - the last page lists the earliest messages, not the latest.
          $paging_count = variable_get('privatemsg_view_use_max_as_default', FALSE) ? $thread['message_count'] - variable_get('privatemsg_view_default_amount', 10) : $thread['message_count'];
          $last_page = $paging_count % $max_amount;
        }

        // Sanity check - we cannot start from a negative number.
        if ($start < 0) {
          $start = 0;
        }
        $thread['start'] = $start;

        //If there are newer messages on the page, show pager link allowing to go to the newer messages.
        if (($start + $max_amount + 1) < $thread['message_count']) {
          $thread['to'] = $start + $max_amount;
          $thread['newer_start'] = $start + $max_amount;
        if ($start - $max_amount >= 0) {
          $thread['older_start'] = $start - $max_amount;
        elseif ($start > 0) {
          $thread['older_start'] = 0;
        }

        // Do not show messages on the last page that would show on the page
        // before. This will only work when using the visual pager.
        if ($start < $last_page && $max_amount != PRIVATEMSG_UNLIMITED && $max_amount < $thread['message_count']) {
          unset($thread['older_start']);
          $thread['to'] = $thread['newer_start'] = $max_amount = $last_page;
          // Start from the first message - this is a specific hack to make sure
          // the message display has sane paging on the last page.
          $start = 0;
        // Visual counts start from 1 instead of zero, so plus one.
        $thread['from'] = $start + 1;
        $query->range($start, $max_amount);
      $thread['messages'] = privatemsg_message_load_multiple($query->execute()->fetchCol(), $thread['read_all'] ? NULL : $account);

      // If there are no messages, don't allow access to the thread.
      if (empty($thread['messages'])) {
        if ($useAccessDenied) {
          // Generate new query with read all to see if the thread does exist.
          $query = _privatemsg_assemble_query('messages', array($thread_id), NULL);
          $exists = $query->countQuery()->execute()->fetchField();
          if (!$exists) {
            // Thread does not exist, display 404.
            $thread = FALSE;
          }
        }
        else {
          $thread = FALSE;
        }
        // General data, assume subject is the same for all messages of that thread.
        $thread['user'] = $account;
        $message = current($thread['messages']);
        $thread['subject'] = $message['subject'];
      }
      $threads[$account->uid][$thread_id] = $thread;
    return $threads[$account->uid][$thread_id];
litwol's avatar
litwol committed

litwol's avatar
litwol committed
 * Implementation of hook_privatemsg_view_template().
 *
 * Allows modules to define different message view template.
 *
 * This hook returns information about available themes for privatemsg viewing.
litwol's avatar
litwol committed
 *
 * array(
 *  'machine_template_name' => 'Human readable template name',
 *  'machine_template_name_2' => 'Human readable template name 2'
 * };
 */
function privatemsg_privatemsg_view_template() {
  return array(
litwol's avatar
litwol committed
    'privatemsg-view' => 'Default view',
litwol's avatar
litwol committed
}
litwol's avatar
litwol committed

/**
 * Implementation of hook_cron().
 *
 * If the flush feature is enabled, a given amount of deleted messages that are
 * old enough are flushed.
 */
function privatemsg_cron() {
  if (variable_get('privatemsg_flush_enabled', FALSE)) {
    $query = _privatemsg_assemble_query('deleted', variable_get('privatemsg_flush_days', 30), variable_get('privatemsg_flush_max', 200));

    foreach ($query->execute()->fetchCol() as $mid) {
      $message = privatemsg_message_load($mid);
      module_invoke_all('privatemsg_message_flush', $message);

      // Delete recipients of the message.
      db_delete('pm_index')
        ->condition('mid', $mid)
        ->execute();
      // Delete message itself.
      db_delete('pm_message')
        ->condition('mid', $mid)
        ->execute();
    }
  }
}

litwol's avatar
litwol committed
function privatemsg_theme() {
  return array(
    'privatemsg_view'    => array(
      'template'         => variable_get('private_message_view_template', 'privatemsg-view'), // 'privatemsg',
litwol's avatar
litwol committed
    ),
    'privatemsg_from'    => array(
litwol's avatar
litwol committed
      'template'         => 'privatemsg-from',
litwol's avatar
litwol committed
    ),
litwol's avatar
litwol committed
      'template'         => 'privatemsg-recipients',
litwol's avatar
litwol committed
    ),
litwol's avatar
litwol committed
    'privatemsg_between' => array(
litwol's avatar
litwol committed
      'template'         => 'privatemsg-between',
    ),
    // Define pattern for header/field templates. The theme system will register all
    // theme functions that start with the defined pattern.
    'privatemsg_list_header'  => array(
      'file'                  => 'privatemsg.theme.inc',
      'path'                  => drupal_get_path('module', 'privatemsg'),
      'pattern'               => 'privatemsg_list_header__',
      'arguments'             => array(),
    ),
    'privatemsg_list_field'   => array(
      'file'                  => 'privatemsg.theme.inc',
      'path'                  => drupal_get_path('module', 'privatemsg'),
      'pattern'               => 'privatemsg_list_field__',
      'arguments'             => array('thread'),
litwol's avatar
litwol committed
    ),
      'file'                  => 'privatemsg.theme.inc',
      'path'                  => drupal_get_path('module', 'privatemsg'),
litwol's avatar
litwol committed

litwol's avatar
litwol committed
function privatemsg_preprocess_privatemsg_view(&$vars) {
litwol's avatar
litwol committed
//  drupal_set_message('<pre>'. print_r($vars,1 ) . '</pre>');

  $vars['mid'] = isset($message['mid']) ? $message['mid'] : NULL;
  $vars['thread_id'] = isset($message['thread_id']) ? $message['thread_id'] : NULL;
  $vars['author_picture'] = theme('user_picture', array('account' => $message['author']));
  $vars['author_name_link'] = theme('username', array('account' => $message['author']));
litwol's avatar
litwol committed
  /**
   * @todo perhaps make this timestamp configurable via admin UI?
   */
  $vars['message_timestamp'] = format_date($message['timestamp'], 'small');
  $vars['message_body'] = check_markup($message['body'], $message['format']);
  if (isset($vars['mid']) && isset($vars['thread_id']) && privatemsg_user_access('delete privatemsg')) {
    $vars['message_actions'][] = array('title' => t('Delete message'), 'href' => 'messages/delete/' . $vars['thread_id'] . '/' . $vars['mid']);
  }
  $vars['message_anchors'][] = 'privatemsg-mid-' . $vars['mid'];
  if (!empty($message['is_new'])) {
    $vars['message_anchors'][] = 'new';
    $vars['new'] = drupal_ucfirst(t('new'));
litwol's avatar
litwol committed
  }

  // call hook_privatemsg_message_view_alter
  drupal_alter('privatemsg_message_view', $vars);
  $vars['message_actions'] = !empty($vars['message_actions']) ? theme('links', array('links' => $vars['message_actions'], 'attributes' => array('class' => array('message-actions')))) : '';
  $vars['anchors'] = '';
  foreach ($vars['message_anchors'] as $anchor) {
    $vars['anchors'].= '<a name="' . $anchor . '"></a>';
  }
litwol's avatar
litwol committed

function privatemsg_preprocess_privatemsg_recipients(&$vars) {
  $vars['participants'] = ''; // assign a default empty value
  if (isset($vars['message']['participants'])) {
    $vars['participants'] = _privatemsg_format_participants($vars['message']['participants']);
litwol's avatar
litwol committed
/**
 * Changes the read/new status of a single message.
litwol's avatar
litwol committed
 *
 * @param $pmid
 *   Message id
 * @param $status
 *   Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD
 * @param $account
 *   User object, defaults to the current user
litwol's avatar
litwol committed
 */
function privatemsg_message_change_status($pmid, $status, $account = NULL) {
  if (!$account) {
    global $user;
    $account = $user;
litwol's avatar
litwol committed
  }
  db_update('pm_index')
    ->fields(array('is_new' => $status))
    ->condition('mid', $pmid)
    ->condition('uid', $account->uid)
    ->execute();
litwol's avatar
litwol committed
}

/**
 * Return number of unread messages for an account.
 *
 * @param $account
 *   Specifiy the user for which the unread count should be loaded.
 *
 * @ingroup api
litwol's avatar
litwol committed
 */
function privatemsg_unread_count($account = NULL) {
  $counts = &drupal_static(__FUNCTION__, array());
litwol's avatar
litwol committed
  if (!$account || $account->uid == 0) {
    global $user;
litwol's avatar
litwol committed
    $account = $user;
litwol's avatar
litwol committed
  }
  if (!isset($counts[$account->uid])) {
    $counts[$account->uid] = _privatemsg_assemble_query('unread_count', $account)
      ->execute()
      ->fetchField();
litwol's avatar
litwol committed
  }
  return $counts[$account->uid];
}

/**
 * Load all participants of a thread.
 *
 * @param $thread_id
 *   Thread ID for wich the participants should be loaded.
 */
function _privatemsg_load_thread_participants($thread_id) {
  $query = _privatemsg_assemble_query('participants', $thread_id);
  return user_load_multiple($query->execute()->fetchCol());
}

/**
 * Extract the valid usernames of a string and loads them.
 *
 * This function is used to parse a string supplied by a username autocomplete
 * field and load all user objects.
 *
 * @param $string
 *   A string in the form "usernameA, usernameB, ...".
 * @return
 *   Array, first element is an array of loaded user objects, second an array
 *   with invalid names.
 *
 */
function _privatemsg_parse_userstring($input) {
  if (is_string($input)) {
    $input = explode(',', $input);
  }

  // Start working through the input array.
litwol's avatar
litwol committed
  $invalid = array();
  $recipients = array();
  foreach ($input as $string) {
    $string = trim($string);
    if (!empty($string)) { // We don't care about white space names.

      // First, check if another module is able to resolve the string into an
      // user object.
      foreach (module_implements('privatemsg_name_lookup') as $module) {
        $function = $module . '_privatemsg_name_lookup';
        if (($recipient = $function($string)) && is_object($recipient)) {
          // If there is a match, continue with the next input string.
          $recipients[$recipient->uid] = $recipient;
          continue 2;
        }
      // Fall back to the default username lookup.
      if (!$error = module_invoke('user', 'validate_name', $string)) {
        // String is a valid username, look it up.
        if ($recipient = user_load_by_name($string)) {
          $recipients[$recipient->uid] = $recipient;
          continue;
        }
litwol's avatar
litwol committed

  return array($recipients, $invalid);
litwol's avatar
litwol committed
}
litwol's avatar
litwol committed

/**
 * Query definition to load a list of threads.
 *
 * @param $account
 *  User object for which the messages are being loaded.
 * @param $argument
 *  string argument which can be used in the query builder to modify the thread listing.
 */
function privatemsg_sql_list($account, $argument = 'list') {
  $query = db_select('pm_message', 'pm')->extend('TableSort')->extend('PagerDefault');
  $query->join('pm_index', 'pmi', 'pm.mid = pmi.mid');

  // Create count query;
  $count_query = db_select('pm_message', 'pm');
  $count_query->addExpression('COUNT(DISTINCT pmi.thread_id)', 'count');
  $count_query->join('pm_index', 'pmi', 'pm.mid = pmi.mid');
  $count_query
    ->condition('pmi.uid', $account->uid)
    ->condition('pmi.deleted', 0);
  $query->setCountQuery($count_query);
litwol's avatar
litwol committed

  $query->addField('pmi', 'thread_id');
  $query->addExpression('MIN(pm.subject)', 'subject');
  $query->addExpression('MAX(pm.timestamp)', 'last_updated');
  $query->addExpression('SUM(pmi.is_new)', 'is_new');

  // Load enabled columns
  $fields = array_filter(variable_get('privatemsg_display_fields', array('participants')));
  if (in_array('count', $fields)) {
    // We only want the distinct number of messages in this thread.
    $query->addExpression('COUNT(distinct pmi.mid)', 'count');
    // Query for a string with uid's, for example "1,6,7". This needs a subquery on PostgreSQL.
    if (db_driver() == 'pgsql') {
      $query->addExpression("array_to_string(array(SELECT DISTINCT pmia.uid
                                                          FROM {pm_index} pmia
                                                          WHERE pmia.thread_id = pmi.thread_id), ',')", 'participants');
      $query->addExpression("(SELECT GROUP_CONCAT(DISTINCT pmia.uid SEPARATOR ',')
                                     FROM {pm_index} pmia
                                     WHERE pmia.thread_id = pmi.thread_id)", 'participants');
    }
  }
  if (in_array('thread_started', $fields)) {
    $query->addExpression('MIN(pm.timestamp)', 'thread_started');
  }
  return $query
    ->condition('pmi.uid', $account->uid)
    ->condition('pmi.deleted', 0)
    ->groupBy('pmi.thread_id')
    ->orderByHeader(_privatemsg_list_headers(array_merge(array('subject', 'last_updated'), $fields)))
    ->limit(variable_get('privatemsg_per_page', 25));
 * Query function for loading a single or multiple messages.
 *   Account for which the messages should be loaded.
function privatemsg_sql_load($pmids, $account = NULL) {
  $query = db_select('pm_message', 'pm')
    ->fields('pm', array('mid', 'author', 'subject', 'body', 'timestamp', 'format'))
    ->fields('pmi', array('is_new', 'thread_id'))
    ->condition('pmi.mid', $pmids)
    ->orderBy('pm.timestamp', 'ASC');
  if($account) {
    $query->condition('pmi.uid', $account->uid);
  }
  $query->join('pm_index', 'pmi', 'pm.mid = pmi.mid');
  return $query;
litwol's avatar
litwol committed
}
/**
 * Query definition to load messages of one or multiple threads.
 *
 * @param $threads
 *   Array with one or multiple thread id's.
 * @param $account
 *   User object for which the messages are being loaded.
 * @param $load_all
 *   Deleted messages are only loaded if this is set to TRUE.
function privatemsg_sql_messages($threads, $account = NULL, $load_all = FALSE) {
  $query = db_select('pm_index', 'pmi');
  $query->addField('pmi', 'mid');
  $query->join('pm_message', 'pm', 'pm.mid = pmi.mid');
  if (!$load_all) {
    $query->condition('pmi.deleted', 0);
  // If there are multiple inserts during the same second (tests, for example)
  // sort by mid second to have them in the same order as they were saved.
    ->condition('pmi.thread_id', $threads)
    ->groupBy('pm.timestamp')
    ->groupBy('pmi.mid')
    // Order by timestamp first.
    ->orderBy('pm.timestamp', 'ASC')
    // If there are multiple inserts during the same second (tests, for example)
    // sort by mid second to have them in the same order as they were saved.
    ->orderBy('pmi.mid', 'ASC');
  if($account) {
    $query->condition('pmi.uid', $account->uid);
  }
  return $query;
Zen's avatar
Zen committed

/**
 * Load all participants of a thread.
 *
 * @param $thread_id
 *   Thread id from which the participants should be loaded.
 */
function privatemsg_sql_participants($thread_id) {
  $query = db_select('pm_index', 'pmi');
  $query->join('users', 'u', 'u.uid = pmi.uid');
  return $query
    ->distinct()
    ->fields('pmi', array('uid'))
    ->fields('u', array('name'))
    ->condition('pmi.thread_id', $thread_id);
function privatemsg_sql_unread_count($account) {
  $query = db_select('pm_index', 'pmi');
  $query->addExpression('COUNT(DISTINCT thread_id)', 'unread_count');
  return $query
    ->condition('pmi.deleted', 0)
    ->condition('pmi.is_new', 1)
    ->condition('pmi.uid', $account->uid);
function privatemsg_sql_autocomplete($search, $names) {
  $query = db_select('users', 'u')
    ->fields('u', array('name'))
    ->condition('u.name', $search . '%', 'LIKE')
    ->condition('u.status', 0, '<>')
    ->where('NOT EXISTS (SELECT 1 FROM {pm_disable} pd WHERE pd.uid=u.uid)')
    ->orderBy('u.name', 'ASC')
    ->range(0, 10);

    $query->condition('u.name', $names, 'NOT IN');
/**
 * Query Builder function to load all messages that should be flushed.
 *
 * @param $days
 *   Select messages older than x days.
 * @param $max
 *   Select no more than $max messages.
 */
function privatemsg_sql_deleted($days, $max) {
  $query = db_select('pm_message', 'pm');
  $query->addField('pm', 'mid');

  $query->join('pm_index', 'pmi', 'pmi.mid = pm.mid');
  return $query
    ->groupBy('pm.mid')
    ->havingCondition('MIN(pmi.deleted)', 0, '>')
    ->havingCondition('MAX(pmi.deleted)', time() - $days * 86400, '<')
    ->range(0, $max);
}

/**
 * Implements hook_user_view().
 */
function privatemsg_user_view(&$account) {
  if (($url = privatemsg_get_link(array($account))) && variable_get('privatemsg_display_profile_links', 1)) {
    $account->content['privatemsg_send_new_message'] = array(
      '#markup' => l(t('Send this user a message'), $url, array('query' => drupal_get_destination())),
      '#weight' => 10,
    );
  }
}
litwol's avatar
litwol committed

/**
 * Implements hook_user_login().
 */
function privatemsg_user_login(&$edit, &$account) {
  if (variable_get('privatemsg_display_loginmessage', TRUE) && privatemsg_user_access()) {
    $count = privatemsg_unread_count();
    if ($count) {
      drupal_set_message(t('You have <a href="@messages">%unread</a>.', array('@messages' => url('messages'), '%unread' => format_plural($count, '1 unread message', '@count unread messages'))));
    }
/**
 * Implements hook_user_cancel().
 */
function privatemsg_user_cancel($edit, $account, $method) {
  switch ($method) {
    case 'user_cancel_reassign':
      db_update('pm_message')
        ->condition('author', $account->uid)
        ->fields(array('author' => 0))
        ->execute();
      break;
    
    case 'user_cancel_delete':
    case 'user_cancel_block_unpublish':
      $mids = db_select('pm_message', 'pm')
        ->fields('pm', array('mid'))
        ->condition('author', $account->uid)
        ->execute()
        ->fetchCol();

      if (!empty($mids)) {
        // Delete recipient entries in {pm_index} of the messages the user wrote.
        db_delete('pm_index')
          ->condition('mid', $mids)
          ->execute();
      }

      // Delete messages the user wrote.
      db_delete('pm_message')
        ->condition('author', $account->uid)
        ->execute();

      // Delete recipient entries of that user.
      db_delete('pm_index')
        ->condition('uid', $account->uid)
        ->execute();
      break;
  }
  // Always delete user settings.
  db_delete('pm_disable')
    ->condition('uid', $account->uid)
    ->execute();
}

/**
 * Implements hook_form_alter().
 */
function privatemsg_form_alter(&$form, &$form_state, $form_id) {
  // We have to use user_acces() because privatemsg_user_access() would
  // return FALSE when privatemsg is disabled.
  if (($form_id == 'user_register_form' || $form_id == 'user_profile_form') && $form['#user_category'] == 'account' && user_access('write privatemsg')) {
    $form['privatemsg'] = array(
      '#type' => 'fieldset',
      '#title' => t('Privatemsg settings'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
      '#weight' => 10,
    );

    $form['privatemsg']['pm_enable'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable Private Messaging'),
      '#default_value' => !privatemsg_is_disabled($form['#user']),
      '#description' => t('Disabling private messages prevents you from sending or receiving messages from other users.'),
    );
  }
}

/**
 * Implements hook_user_insert().
 */
function privatemsg_user_update(&$edit, $account, $category) {
  if ($category == 'account' && isset($edit['pm_enable']) && user_access('write privatemsg')) {
    $current = privatemsg_is_disabled($account);
    $disabled = (!$edit['pm_enable']);
    $edit['pm_enable'] = NULL;

    $account->privatemsg_disabled = $disabled;

    // only perform the save if the value has changed
    if ($current != $disabled) {
      if ($disabled) {
        db_insert('pm_disable')
          ->fields(array('uid' => $account->uid))
          ->execute();
      }
      else {
        db_delete('pm_disable')
          ->condition('uid', $account->uid)
          ->execute();
      }
    }
  }
}

/**
 * Implementation of hook_privatemsg_block_message.
 */
function privatemsg_privatemsg_block_message($author, $recipients) {
  $blocked = array();
  if (privatemsg_is_disabled($author)) {
    $blocked[] = array('uid' => $author->uid,
                       'message' => t('You have disabled private message sending and receiving.'),
                 );