Skip to content
privatemsg.module 61.9 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);
Marco Molinari's avatar
Marco Molinari committed

Zen's avatar
Zen committed
/**
litwol's avatar
litwol committed
 * Implementation of hook_perm().
Zen's avatar
Zen committed
 */
litwol's avatar
litwol committed
function privatemsg_perm() {
  return array(
    'read privatemsg',
    'read all private messages',
litwol's avatar
litwol committed
    'administer privatemsg settings',
litwol's avatar
litwol committed
    'write privatemsg'
/**
 * 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) {
  static $user_cache = array();

  // 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.
  $users = explode(',', $userstring);
  if (!is_null($slice)) {
    $users = array_slice($users, $slice);
  }
  $participants = array();
  foreach ($users as $uid) {
    if (!array_key_exists($uid, $user_cache)) {
      $user_cache[$uid] = user_load($uid);
    if (is_object($user_cache[$uid])) {
      $participants[$uid] = $user_cache[$uid];
/**
 * 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 = 20, $no_text = FALSE) {
  if (count($part_array) > 0) {
    $limited = FALSE;
    foreach ($part_array as $account) {
      if (count($to) >= $limit) {
        $limited = TRUE;
      $to[] = theme('username', $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'),
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'type'             => MENU_NORMAL_ITEM,
litwol's avatar
litwol committed
  );
  $items['messages/list'] = array(
    'title'            => 'Messages',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_list', 'list'),
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'type'             => MENU_DEFAULT_LOCAL_TASK,
    'weight'           => -10,
litwol's avatar
litwol committed
  );
  $items['messages/view/%privatemsg_thread'] = array(
    'title'            => 'Read message',
litwol's avatar
litwol committed
    'page callback'    => 'privatemsg_view',
    'page arguments'   => array(2),
    'access callback'  => 'privatemsg_view_access',
litwol's avatar
litwol committed
    'weight'           => -10,
litwol's avatar
litwol committed
  );
litwol's avatar
litwol committed
  $items['messages/delete/%'] = array(
    'title'            => 'Delete message',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_delete', 2),
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'type'             => MENU_CALLBACK,
    'weight'           => -10,
  );
  $items['messages/new'] = array(
    'title'            => 'Write new message',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('privatemsg_new', 2, 3, NULL),
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'access arguments' => array('write privatemsg'),
    'type'             => MENU_LOCAL_TASK,
    'weight'           => -7,
  );
  // Auto-completes available user names & removes duplicates.
  $items['messages/user-name-autocomplete'] = array(
    'page callback'    => 'privatemsg_user_name_autocomplete',
    'access callback'  => 'privatemsg_user_access',
litwol's avatar
litwol committed
    'access arguments' => array('write privatemsg'),
litwol's avatar
litwol committed
    'type'             => MENU_CALLBACK,
    'weight'           => -10,
  );
  $items['admin/settings/messages'] = array(
    'title'            => 'Private messages',
    'description'      => 'Configure private messaging settings.',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('private_message_settings'),
    'access arguments' => array('administer privatemsg settings'),
    'type'             => MENU_NORMAL_ITEM,
  $items['admin/settings/messages/default'] = array(
    'title'            => 'Private messages',
    'description'      => 'Configure private messaging settings.',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('private_message_settings'),
    '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',
    'access arguments' => array('read privatemsg'),
    'type'             => MENU_CALLBACK,
  );
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 (!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/%.
 *
 * @ingroup api
 */
function privatemsg_view_access() {
  if (privatemsg_user_access('read privatemsg') && arg(1) == 'view') {
    return TRUE;
  }
  return FALSE;
}

 * 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.
 * @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) {

    if (is_null($account)) {
      global $user;
      $account = drupal_clone($user);
    }
    // load messages returned by the messages query with _privatemsg_load().
    $query = _privatemsg_assemble_query('messages', array($thread_id), $account);
    $conversation = db_query($query['query']);
    while ($result = db_fetch_array($conversation)) {
      if ($message = _privatemsg_load($result['mid'], $account)) {
        $thread['messages'][$result['mid']] = $message;
      }
    }
    // if there are no messages, don't allow access to the thread.
    if (empty($thread['messages'])) {
      return 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'];

    // Load the list of participants.
    $query = _privatemsg_assemble_query('participants', $thread_id);
    $participants = db_query($query['query']);
    while ($result = db_fetch_array($participants)) {
      $thread['participants'][$result['uid']] = user_load($result['uid']);
    }
    return $thread;
litwol's avatar
litwol committed

litwol's avatar
litwol committed
function private_message_view_options() {
  $options = module_invoke_all('privatemsg_view_template');
  return $options;
chx's avatar
chx committed
}
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

litwol's avatar
litwol committed
function private_message_settings() {
  $form = array();
litwol's avatar
litwol committed

litwol's avatar
litwol committed
  $form['theming_settings'] = array(
litwol's avatar
litwol committed
    '#type'        => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed'   => TRUE,
    '#title'       => t('Theming settings'),
litwol's avatar
litwol committed
  );
  $form['theming_settings']['private_message_view_template'] = array(
litwol's avatar
litwol committed
    '#type'          => 'radios',
    '#title'         => t('Private message display template'),
litwol's avatar
litwol committed
    '#default_value' => variable_get('private_message_view_template', 'privatemsg-view'),
litwol's avatar
litwol committed
    '#options'       => private_message_view_options(),
  );
  $form['privatemsg_per_page'] = array(
    '#type' => 'select',
    '#title' => t('Messages per page'),
    '#default_value' => variable_get('privatemsg_per_page', 25),
    '#options' => drupal_map_assoc(array(10, 25, 50, 75, 100)),
    '#description' => t('Choose the number of conversations that should be listed per page.'),
litwol's avatar
litwol committed
  );
  $form['privatemsg_display_loginmessage'] = array(
    '#type' => 'checkbox',
    '#title' => t('Inform the user about new messages on login'),
    '#default_value' => variable_get('privatemsg_display_loginmessage', TRUE),
    '#description' => t('This option can safely be disabled if the "New message indication" block is used instead.'),
  );
  $form['privatemsg_listing'] = array(
    '#type' => 'fieldset',
    '#title' => t('Configure listings'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );

  $form['privatemsg_listing']['privatemsg_display_fields'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Configure fields'),
    '#description' => t('Select which columns/fields should be displayed in the message listings. Subject and Last updated cannot be disabled.'),
    '#options' => array(
      'thread_started'  => t('Started'),
      'count'           => t('Answers'),
    ),
    '#default_value' => variable_get('privatemsg_display_fields', array('participants')),
litwol's avatar
litwol committed
  $form['#submit'][] = 'private_message_settings_submit';
  return system_settings_form($form);
}
litwol's avatar
litwol committed

litwol's avatar
litwol committed
function private_message_settings_submit() {
  drupal_rebuild_theme_registry();
}
litwol's avatar
litwol committed
function privatemsg_theme() {
  return array(
    'privatemsg_view'    => array(
litwol's avatar
litwol committed
      'arguments'        => array('message' => NULL),
      'template'         => variable_get('private_message_view_template', 'privatemsg-view'), // 'privatemsg',
litwol's avatar
litwol committed
    ),
    'privatemsg_from'    => array(
litwol's avatar
litwol committed
      'arguments'        => array('author' => NULL),
      'template'         => 'privatemsg-from',
litwol's avatar
litwol committed
    ),
litwol's avatar
litwol committed
    'privatemsg_to'      => array(
      'arguments'        => array('message' => NULL),
      'template'         => 'privatemsg-recipients',
litwol's avatar
litwol committed
    ),
litwol's avatar
litwol committed
    'privatemsg_between' => array(
      'arguments'        => array('recipients' => NULL),
      'template'         => 'privatemsg-between',
    ),
    'privatemsg_list'    => array(
      'file'                  => 'privatemsg.theme.inc',
      'path'                  => drupal_get_path('module', 'privatemsg'),
      'arguments'        => array('form'),
    ),
    // Define pattern for header/field templates. The theme system will register all
    // theme functions that start with the defined pattern.
    'privatemsg_list_header'  => array(
      '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>');

litwol's avatar
litwol committed
  $message = $vars['message'];
litwol's avatar
litwol committed
  $vars['mid'] = isset($message['mid']) ? $message['mid']:null;
litwol's avatar
litwol committed
  $vars['author_picture'] = theme('user_picture', $message['author']);
  $vars['author_name_link'] = theme('username', $message['author']);
litwol's avatar
litwol committed
  /**
   * @todo perhaps make this timestamp configurable via admin UI?
   */
  $vars['message_timestamp'] = format_date($message['timestamp'], 'small');
litwol's avatar
litwol committed
  $vars['message_body'] = check_markup($message['body']);
    $vars['message_actions'][] = array('title' => t('Delete message'), 'href' => 'messages/delete/'. $vars['mid']);
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', $vars['message_actions'], array('class' => 'message-actions')) : '';
litwol's avatar
litwol committed

litwol's avatar
litwol committed
function privatemsg_preprocess_privatemsg_to(&$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
 * List messages.
 * @param $form_state
 *   Form state array
 * @param $argument
 *   An argument to pass through to the query builder.
 * @param $uid
 *   User id messages of another user should be displayed
 *
litwol's avatar
litwol committed
 * @return
function privatemsg_list(&$form_state, $argument = 'list', $uid = NULL) {
  global $user;
litwol's avatar
litwol committed

  // Setting default behavior...
  $account = $user;
  // Because uid is submitted by the menu system, it's a string not a integer.
  if ((int)$uid > 0 && $uid != $user->uid) {
litwol's avatar
litwol committed
    // Trying to view someone else's messages...
    if (!privatemsg_user_access('read all private messages')) {
litwol's avatar
litwol committed
      drupal_set_message(t("You do not have sufficient rights to view someone else's messages"), 'warning');
litwol's avatar
litwol committed
    elseif ($account_check = user_load(array('uid' => $uid))) {
      // Has rights and user_load return an array so user does exist
      $account = $account_check;
litwol's avatar
litwol committed
  }
  // By this point we have figured out for which user we are listing messages and now it is safe to use $account->uid in the listing query.

  $query = _privatemsg_assemble_query('list', $account, $argument);
litwol's avatar
litwol committed
  $result = pager_query($query['query'], variable_get('privatemsg_per_page', 25), 0, $query['count']);
  $threads = array();
  $form['#data'] = array();
  while ($row = db_fetch_array($result)) {
    // Store the raw row data.
    $form['#data'][$row['thread_id']] = $row;
    // Store the themed row data.
    $form['#rows'][$row['thread_id']] = _privatemsg_list_thread($row);
    // store thread id for the checkboxes array
    $threads[$row['thread_id']] = '';
litwol's avatar
litwol committed
  }
  if (empty($form['#data'])) {
    // If no threads are displayed, use these default columns.
    $keys = array('subject', 'author', 'last_updated');
litwol's avatar
litwol committed
  }
  else {
    // Load the keys of the first row in data, we don't know the key
    $keys = array_keys($form['#data'][key($form['#data'])]);
    $form['actions'] = _privatemsg_action_form();
  // Load the themed list headers based on the available data
  $form['#headers'] = _privatemsg_list_headers(!empty($form['#data']), $keys);
  // Define checkboxes, pager and theme
  $form['threads'] = array('#type' => 'checkboxes', '#options' => $threads);
  $form['pager'] = array('#value' => theme('pager'), '#weight' => 20);
  $form['#theme'] = 'privatemsg_list';
  // Store the account for which the threads are displayed.
  $form['#account'] = $account;
  return $form;
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
  }
  $query = "UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND uid = %d";
  db_query($query, $status, $pmid, $account->uid);
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) {
  static $counts = array();
  if (!$account || $account->uid == 0) {
    global $user;
litwol's avatar
litwol committed
    $account = $user;
litwol's avatar
litwol committed
  }
  if ( !isset($counts[$account->uid])) {
    $query = _privatemsg_assemble_query('unread_count', $account);
litwol's avatar
litwol committed
    $counts[$account->uid] = db_result(db_query($query['query']));
  }
  return $counts[$account->uid];
}

/**
 * Menu callback for viewing a thread.
 *
 * @param $thread
 *   A array containing all information about a specific thread, generated by
 *   privatemsg_thread_load().
 * @return
 *   The page content.
 * @see privatemsg_thread_load().
 */
function privatemsg_view($thread) {
  drupal_set_title(check_plain($thread['subject']));
  // Render the participants.
  $content['participants']['#value'] = theme('privatemsg_to', $thread);
litwol's avatar
litwol committed
  $content['participants']['#weight'] = -5;

  // Render the messages.
  $output = '';
  foreach ($thread['messages'] as $pmid => $message) {
    // Set message as read and theme it.
    if (!empty($message['is_new'])) {
      privatemsg_message_change_status($pmid, PRIVATEMSG_READ, $thread['user']);
    }
litwol's avatar
litwol committed
    $output .= theme('privatemsg_view', $message);
  }
  $content['messages']['#value'] = $output;
litwol's avatar
litwol committed
  $content['messages']['#weight'] = 0;

  // Display the reply form if user is allowed to use it.
  if (privatemsg_user_access('write privatemsg')) {
    $content['reply']['#value'] = drupal_get_form('privatemsg_new', $thread['participants'], $thread['subject'], $thread['thread_id']);
litwol's avatar
litwol committed
    $content['reply']['#weight'] = 5;
  }

  //allow other modules to hook into the $content array and alter it
  drupal_alter('privatemsg_view_messages', $content, $thread);
  return drupal_render($content);
litwol's avatar
litwol committed

function privatemsg_new(&$form_state, $recipients = array(), $subject = '', $thread_id = NULL) {
litwol's avatar
litwol committed

litwol's avatar
litwol committed
  $body      = '';
litwol's avatar
litwol committed

  // convert recipients to array of user objects
  if (!empty($recipients) && is_string($recipients) || is_int($recipients)) {
    $recipients = _privatemsg_generate_user_array($recipients);
  }
  elseif (is_object($recipients)) {
    $recipients = array($recipients);
  }
  elseif (empty($recipients) && is_string($recipients)) {
    $recipients = array();
  }

  $usercount = 0;
  $to = array();
  foreach ($recipients as $recipient) {
    if (in_array($recipient->name, $to)) {
      // We already added the recipient to the list, skip him.
      continue;
    }
    // Check if another module is blocking the sending of messages to the recipient by current user.
    $user_blocked = module_invoke_all('privatemsg_block_message', $user, array($recipient));
    if (!count($user_blocked) <> 0 && $recipient->uid) {
      if ($recipient->uid == $user->uid) {
        $usercount++;
        // Skip putting author in the recipients list for now.
        continue;
      }
      $to[] = $recipient->name;
    }
  }

  if (empty($to) && $usercount >= 1) {
    // Assume the user sent message to own account as if the usercount is one or less, then the user sent a message but not to self.
    $to[] = $user->name;
  }


  if (!empty($to)) {
    $recipients_string = implode(', ', $to);
litwol's avatar
litwol committed
  }
litwol's avatar
litwol committed
  if (isset($form_state['values'])) {
    $recipients_string = $form_state['values']['recipient'];
litwol's avatar
litwol committed
    $subject   = $form_state['values']['subject'];
    $body      = $form_state['values']['body'];
  if (!$thread_id && !empty($recipients_string)) {
    drupal_set_title(t('Write new message to %recipient', array('%recipient' => $recipients_string)));
  } elseif (!$thread_id) {
    drupal_set_title(t('Write new message'));
  }
litwol's avatar
litwol committed
  $form = array();
  if (isset($form_state['privatemsg_preview'])) {
    $form['message_header'] = array(
      '#type' => 'fieldset',
litwol's avatar
litwol committed
      '#attributes' => array('class' => 'preview'),
litwol's avatar
litwol committed
    );
    $form['message_header']['message_preview'] = array(
      '#value'  => $form_state['privatemsg_preview'],
    );
litwol's avatar
litwol committed
  $form['privatemsg'] = array(
    '#type'               => 'fieldset',
    '#access'             => privatemsg_user_access('write privatemsg'),
litwol's avatar
litwol committed
  );
  $form['privatemsg']['author'] = array(
    '#type' => 'value',
    '#value' => $user,
litwol's avatar
litwol committed
  );
litwol's avatar
litwol committed
  $form['privatemsg']['recipient']  = array(
litwol's avatar
litwol committed
    '#type'               => 'textfield',
    '#title'              => t('To'),
    '#description'        => t('Separate multiple names with commas.'),
litwol's avatar
litwol committed
    '#required'           => TRUE,
litwol's avatar
litwol committed
    '#size'               => 50,
litwol's avatar
litwol committed
    '#autocomplete_path'  => 'messages/user-name-autocomplete',
    // Do not hardcode #maxlength, make it configurable by number of recipients, not their name length.
litwol's avatar
litwol committed
  );
litwol's avatar
litwol committed
  $form['privatemsg']['subject']    = array(
litwol's avatar
litwol committed
    '#type'               => 'textfield',
    '#title'              => t('Subject'),
    '#size'               => 50,
    '#maxlength'          => 255,
    '#default_value'      => $subject,
litwol's avatar
litwol committed
  );
litwol's avatar
litwol committed
  $form['privatemsg']['body']       = array(
litwol's avatar
litwol committed
    '#type'               => 'textarea',
    '#title'              => t('Message'),
    '#cols'               => 10,
    '#rows'               => 6,
litwol's avatar
litwol committed
    '#default_value'      => $body,
  );
litwol's avatar
litwol committed
  $form['privatemsg']['preview'] = array(
litwol's avatar
litwol committed
    '#type'               => 'submit',
litwol's avatar
litwol committed
    '#value'              => t('Preview message'),
litwol's avatar
litwol committed
    '#submit'             => array('pm_preview'),
litwol's avatar
litwol committed
  );
litwol's avatar
litwol committed
  $form['privatemsg']['submit'] = array(
litwol's avatar
litwol committed
    '#type'               => 'submit',
litwol's avatar
litwol committed
    '#value'              => t('Send message'),
litwol's avatar
litwol committed
    '#submit'             => array('pm_send'),
litwol's avatar
litwol committed
  );
  $url = 'messages';
  if (isset($_REQUEST['destination'])) {
    $url = $_REQUEST['destination'];
  }
litwol's avatar
litwol committed
  $form['privatemsg']['cancel'] = array(
    '#value'              => l(t('Cancel'), $url, array('attributes' => array('id' => 'edit-cancel'))),
litwol's avatar
litwol committed
  );
  $form['#validate'][]    = 'pm_send_validate';

  if (!is_null($thread_id)) {
    $form['privatemsg']['thread_id'] = array(
      '#type' => 'value',
      '#value' => $thread_id,
    );
    $form['privatemsg']['subject'] = array(
          '#type' => 'value',
          '#default_value' => $subject,
    );
    $form['privatemsg']['recipient_display'] = array(
      '#value' =>  '<p>'. t('<strong>Reply to thread</strong>:<br /> Recipients: %to', array('%to' => $recipients_string)) .'</p>',
      '#weight' => -10,
    );
    $form['privatemsg']['recipient']  = array(
      '#type' => 'value',
      '#default_value' => $recipients_string,
    );
    if (empty($recipients_string)) {
      // If there are no valid recipients, unset the message reply form.
      $form['privatemsg']['#access'] = FALSE;
    }
  }
litwol's avatar
litwol committed
  return $form;
litwol's avatar
litwol committed

litwol's avatar
litwol committed
function pm_send_validate($form, &$form_state) {
litwol's avatar
litwol committed
  // The actual message that is being sent, we create this during validation and pass to submit to send out.
  $message = array();
litwol's avatar
litwol committed
  $message['body']      = $form_state['values']['body'];
  $message['subject']   = $form_state['values']['subject'];
  $message['author']    = $form_state['values']['author'];
litwol's avatar
litwol committed
  $message['timestamp'] = time();
litwol's avatar
litwol committed
  if (isset($form_state['values']['thread_id']) && $form_state['values']['thread_id']) {
    $message['thread_id'] = $form_state['values']['thread_id'];
  }
  $trimed_body = trim(truncate_utf8(strip_tags($message['body']), 50, TRUE, TRUE));
  if (empty($message['subject']) && !empty($trimed_body)) {
    $message['subject'] = $trimed_body;
  }
litwol's avatar
litwol committed

  list($message['recipients'], $invalid) = _privatemsg_parse_userstring($form_state['values']['recipient']);

  $validated = _privatemsg_validate_message($message, TRUE);
  foreach ($validated['messages'] as $type => $text) {
    drupal_set_message($text, $type);
  }
  $form_state['validate_built_message'] = $message;
  if (!empty($invalid)) {
    drupal_set_message(t('The following users will not receive this private message: !invalid', array('!invalid' => implode(", ", $invalid))), 'error');
  }
}

/**
 * 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($string) {
litwol's avatar
litwol committed
  // Verify that recipient's name syntax is correct.
  $fragments = explode(',', $string);
litwol's avatar
litwol committed
  $invalid = array();
  $valid   = array();
  foreach ($fragments as $index => $name) {
    $name = trim($name);
litwol's avatar
litwol committed
    if (!empty($name)) { // We don't care about white space names.
litwol's avatar
litwol committed
      if (empty($name) || $error = module_invoke('user', 'validate_name', $name)) {
litwol's avatar
litwol committed
        // These names are invalid due to incorrect user name syntax.
litwol's avatar
litwol committed
        $invalid[$name] = $name;
      }
      else {
litwol's avatar
litwol committed
        $valid[$name] = $name;  // These are valid only due to user name syntax. We still need to check if the user exists and accepts messages.
litwol's avatar
litwol committed

  // Verify users exist and load their accounts.
litwol's avatar
litwol committed
  foreach ($valid as $index => $name) {
    if ($recipient = user_load(array('name' => $name))) {
      $users[$recipient->uid] = $recipient;
litwol's avatar
litwol committed
    }
    else {
litwol's avatar
litwol committed
      // Here we add more invalid names due to the fact that they don't exist.
litwol's avatar
litwol committed
      $invalid[$name] = $name;
    }
litwol's avatar
litwol committed
}
litwol's avatar
litwol committed

litwol's avatar
litwol committed
function pm_send($form, &$form_state) {
  if (_privatemsg_send($form_state['validate_built_message'])) {    // Load usernames to which the message was sent to
    $recipient_names = array();
    foreach ($form_state['validate_built_message']['recipients'] as $recipient) {
      $recipient_names[] = theme('username', $recipient);
    }
    drupal_set_message(t('A message has been sent to !recipients.', array('!recipients' => implode(', ', $recipient_names))));
litwol's avatar
litwol committed
function pm_preview($form, &$form_state) {
Neil Drumm's avatar
Neil Drumm committed

litwol's avatar
litwol committed
    drupal_validate_form($form['form_id']['#value'], $form, $form_state);
    if (!form_get_errors()) {
litwol's avatar
litwol committed
      $form_state['privatemsg_preview'] = theme('privatemsg_view', $form_state['validate_built_message']);
litwol's avatar
litwol committed
    }
litwol's avatar
litwol committed

  $form_state['rebuild'] = TRUE; // this forces our form to be rebuilt instead of being submitted.
chx's avatar
chx committed
}
litwol's avatar
litwol committed

function privatemsg_sql_list(&$fragments, $account, $argument = 'list') {
litwol's avatar
litwol committed
  $fragments['primary_table'] = '{pm_message} pm';
litwol's avatar
litwol committed

  // Load enabled columns
  $fields = array_filter(variable_get('privatemsg_display_fields', array('participants')));
litwol's avatar
litwol committed
  $fragments['select'][]      = 'pmi.thread_id';
  $fragments['select'][]      = 'MIN(pm.subject) as subject';
  $fragments['select'][]      = 'MAX(pm.timestamp) as last_updated';
litwol's avatar
litwol committed
  $fragments['select'][]      = 'MAX(pmi.is_new) as is_new';
  if (in_array('count', $fields)) {
    $fragments['select'][]      = 'COUNT(distinct pmi.mid) as count'; // We only want the distinct number of messages in this thread.
    // Query for a string with uid's, for example "1,6,7". This needs a subquery on PostgreSQL.
    if ($GLOBALS['db_type'] == 'pgsql') {
       $fragments['select'][]      = "array_to_string(array(SELECT DISTINCT textin(int4out(pmia.uid))
                                                            FROM {pm_index} pmia
                                                            WHERE pmia.thread_id = pmi.thread_id), ',') AS participants";
       $fragments['select'][]      = '(SELECT GROUP_CONCAT(DISTINCT pmia.uid SEPARATOR ",")
                                                            FROM {pm_index} pmia
                                                            WHERE pmia.thread_id = pmi.thread_id) AS participants';
    }
  }
  if (in_array('thread_started', $fields)) {
    $fragments['select'][]      = 'MIN(pm.timestamp) as thread_started';
  }
litwol's avatar
litwol committed
  // pm_index needs to be the first join.
  $fragments['inner_join'][]  = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid';
litwol's avatar
litwol committed
  $fragments['where'][]       = 'pmi.uid = %d';
  $fragments['query_args']['where'][]  = $account->uid;
litwol's avatar
litwol committed
  $fragments['where'][]       = 'pmi.deleted = 0';
  $fragments['group_by'][]    = 'pmi.thread_id';

  // We can use tablesort_sql now, but we don't need the ORDER BY part of the query.
  // Because of that, we need to cut off the first 9 characters of the generated string
  $order_by = drupal_substr(tablesort_sql(_privatemsg_list_headers( FALSE, array('subject', 'last_updated') + $fields), 'is_new DESC,'), 9);
  $fragments['order_by'][]  = $order_by;
/**
 * @addtogroup sql
 * @{
 */

/**
 * Query function for load.
 */
function privatemsg_sql_load(&$fragments, $pmid, $account) {
litwol's avatar
litwol committed
//  drupal_set_message('<pre>'. print_r(func_get_args(), 1) . '</pre>');
litwol's avatar
litwol committed
  $fragments['primary_table'] = '{pm_message} pm'; // Our primary table

  $fragments['select'][]      = "pm.mid";
  $fragments['select'][]      = "pm.author";
  $fragments['select'][]      = "pm.subject";
  $fragments['select'][]      = "pm.body";
  $fragments['select'][]      = "pm.timestamp";
  $fragments['select'][]      = "pmi.is_new";

  $fragments['inner_join'][]  = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid';
  $fragments['where'][]       = 'pmi.mid = %d';
  $fragments['query_args']['where'][]  = $pmid;
litwol's avatar
litwol committed
  $fragments['where'][]       = 'pmi.uid = %d';
  $fragments['query_args']['where'][]  = $account->uid;
litwol's avatar
litwol committed
}
/**
 * Query definition to load messages of one or multiple threads.
 *
 * @param $fragments
 *  Query fragments array.
 * @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(&$fragments, $threads, $account, $load_all = FALSE) {
litwol's avatar
litwol committed
  $fragments['primary_table'] = '{pm_index} pmi';

  $fragments['select'][]      = 'DISTINCT(pmi.mid) as mid';
  $fragments['where'][]       = 'pmi.thread_id IN ('. db_placeholders($threads) .')';
  $fragments['query_args']['where']   += $threads;
litwol's avatar
litwol committed
  $fragments['where'][]       = 'pmi.uid = %d';
  $fragments['query_args']['where'][]  = $account->uid;
  if (!$load_all) {
    $fragments['where'][]       = 'pmi.deleted = 0';
  }
litwol's avatar
litwol committed
  $fragments['order_by'][]    = 'pmi.mid ASC';
Zen's avatar
Zen committed

function privatemsg_sql_participants(&$fragments, $thread_id) {
litwol's avatar
litwol committed
  $fragments['primary_table'] = '{pm_index} pmi';
litwol's avatar
litwol committed

  $fragments['select'][]      = 'DISTINCT(pmi.uid) as uid';
  $fragments['where'][]       = 'pmi.thread_id = %d';
  $fragments['query_args']['where'][]  = $thread_id;
function privatemsg_sql_unread_count(&$fragments, $account) {
litwol's avatar
litwol committed
  $fragments['primary_table'] = '{pm_index} pmi';

  $fragments['select'][]      = 'COUNT(DISTINCT thread_id) as unread_count';
  $fragments['where'][]       = 'pmi.deleted = 0';
  $fragments['where'][]       = 'pmi.is_new = 1';
  $fragments['where'][]       = 'pmi.uid = %d';
  $fragments['query_args']['where'][]  = $account->uid;
function privatemsg_sql_autocomplete(&$fragments, $search, $names) {
  $fragments['primary_table'] = '{users} u';
  $fragments['select'][] = 'u.name';
  $fragments['where'][] = "u.name LIKE '%s'";
  $fragments['query_args']['where'][] = $search .'%%';
  if (!empty($names)) {
    $fragments['where'][] = "u.name NOT IN (". db_placeholders($names, 'text') .")";
    $fragments['query_args']['where'] += $names;
  }
  $fragments['where'][] = 'u.status <> 0';
  $fragments['order_by'][] = 'u.name ASC';
}

/**
 * Return autocomplete results for usernames.
litwol's avatar
litwol committed
 * Prevents usernames from being used and/or suggested twice.
 */
function privatemsg_user_name_autocomplete($string) {
  $names = array();
litwol's avatar
litwol committed
  // 1: Parse $string and build list of valid user names.
  $fragments = explode(',', $string);
  foreach ($fragments as $index => $name) {
    $name = trim($name);
    if ($error = module_invoke('user', 'validate_name', $name)) {
litwol's avatar
litwol committed
      // Do nothing if this name does not validate.
    }
    else {
      $names[$name] = $name;
    }
  }
litwol's avatar
litwol committed

  // By using user_validate_user we can ensure that names included in $names are at least logisticaly possible.
  // 2: Find the next user name suggestion.
  $fragment = array_pop($names);
  if (!empty($fragment)) {
    $query = _privatemsg_assemble_query('autocomplete', $fragment, $names);
    $result = db_query_range($query['query'], $fragment, 0, 10);
litwol's avatar
litwol committed
    $prefix = count($names) ? implode(", ", $names) .", " : '';
    // 3: Build proper suggestions and print.
    while ($user = db_fetch_object($result)) {
litwol's avatar
litwol committed
      $matches[$prefix . $user->name .", "] = $user->name;