Skip to content
privatemsg.module 99.4 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'),
    ),
    'allow disabling privatemsg' => array(
      'title' => t('Allow disabling private messages'),
      'description' => t("Allows user to disable privatemsg so that they can't recieve or send any private messages.")
    ),
    'reply only privatemsg' => array(
      'title' => t('Reply to private messages'),
      'description' => t('Allows to reply to private messages but not send new ones. Note that the write new private messages permission includes replies.')
    'use tokens in privatemsg' => array(
      'title' => t('Use tokens in private messages'),
      'description' => t("Allows user to use available tokens when sending private messages.")
    ),
    'select text format for privatemsg' => array(
      'title' => t('Select text format for private messages'),
      'description' => t('Allows to choose the text format when sending private messages. Otherwise, the default is used.'),
    ),
/**
 * 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($string, $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.
  if (!is_null($slice)) {
    $users = array_slice($users, $slice);
  }
  $participants = array();
  foreach ($users as $uid) {
    // If it is an integer, it is a user id.
   if ((int)$uid > 0 && ($account = _privatemsg_user_load($uid))) {
      $participants[privatemsg_recipient_key($account)] = $account;
    }
   elseif (strpos($uid, '_') !== FALSE) {
      list($type, $id) = explode('_', $uid);
      $type_info = privatemsg_recipient_get_type($type);
      if ($type_info && isset($type_info['load']) && is_callable($type_info['load'])) {
        if ($participant = reset($type_info['load'](array($id)))) {
          $participants[privatemsg_recipient_key($participant)] = $participant;
        }
      }
    }
/**
 * 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) {

      // Directly address the current user.
      if (isset($account->type) && in_array($account->type, array('hidden', 'user')) && $account->recipient == $user->uid) {
        array_unshift($to, $no_text ? t('You') : t('you'));
        continue;
      }

      // Don't display recipients with type hidden.
      if (isset($account->type) && $account->type == 'hidden') {
        continue;
      }
        $limited = TRUE;
      $to[] = privatemsg_recipient_format($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('Between !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'    => 'privatemsg_list_page',
    'page arguments'   => array('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'    => 'privatemsg_list_page',
    'page arguments'   => array('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/autocomplete'] = array(
    'page callback'    => 'privatemsg_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
    '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/settings'] = array(
    'title'            => 'Private message settings',
    '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'    => 'privatemsg_list_page',
    'page arguments'   => array('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;
/**
 * Implements hook_menu_local_tasks_alter().
 */
function privatemsg_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  // Add action link to 'messages/new' on 'messages' page.
  $add_to_array = array('messages/list', 'messages/inbox', 'messages/sent');
  foreach ($add_to_array as $add_to) {
    if (strpos($root_path, $add_to) !== FALSE) {
    $item = menu_get_item('messages/new');
    if ($item['access']) {
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
      );
    }
    break;
    }
  }
}

 * 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') ) {
    if (arg(0) == 'messages' && variable_get('privatemsg_display_disabled_message', TRUE) && !$disabled_displayed) {
      $disabled_displayed = TRUE;
      drupal_set_message(t('You have disabled Privatemsg and are not allowed to write messages. Go to your <a href="@settings_url">Account settings</a> to enable it again.', array('@settings_url' => url('user/' . $account->uid . '/edit'))), 'warning');
    }
  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 || !isset($account->uid) || !$account->uid) {
    return FALSE;
  }

  if (!isset($account->privatemsg_disabled)) {
    // Make sure we have a fully loaded user object and try to load it if not.
    if ((!empty($account->roles) || $account = user_load($account->uid)) && user_access('allow disabling privatemsg', $account)) {
      $account->privatemsg_disabled = (bool)db_query('SELECT 1 FROM {pm_disable} WHERE uid = :uid ', array(':uid' => $account->uid))->fetchField();
    }
    else {
      $account->privatemsg_disabled = 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.
 * @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_load_thread_participants($thread_id, $account, FALSE, 'view');
      $thread['read_all'] = FALSE;
      if (empty($thread['participants']) && privatemsg_user_access('read all private messages', $account)) {
        $thread['read_all'] = TRUE;
        // Load all participants.
        $thread['participants'] = _privatemsg_load_thread_participants($thread_id, FALSE, FALSE, 'view');
      // 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);
      $conditions = array();
      if (!$thread['read_all']) {
        $conditions['account'] = $account;
      }
      $thread['messages'] = privatemsg_message_load_multiple($query->execute()->fetchCol(), $conditions);

      // 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'] = $thread['subject-original'] = $message->subject;
        if ($message->has_tokens) {
          $thread['subject'] = privatemsg_token_replace($thread['subject'], array('privatemsg_message' => $message), array('sanitize' => TRUE, 'privatemsg-show-span' => FALSE));
        }
      }
      $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();
    }
  }

  $result = db_query_range("SELECT pmi.recipient, pmi.type, pmi.mid FROM {pm_index} pmi WHERE pmi.type NOT IN ('user', 'hidden') AND pmi.is_new = 1 ORDER BY mid ASC", 0, 10);
  // Number of user ids to process for this cron run.
  $total_remaining = variable_get('privatemgs_cron_recipient_per_run', 1000);
  $current_process = variable_get('privatemsg_cron_recipient_process', array());
  foreach ($result as $row) {
    $type = privatemsg_recipient_get_type($row->type);
    if (!$type) {
      continue;
    }
    if (isset($type['load']) && is_callable($type['load'])) {
      $loaded = $type['load'](array($row->recipient));
      if (empty($loaded)) {
        continue;
      }
      $recipient = reset($loaded);
    }

    // Check if we already started to process this recipient.
    $offset = 0;
    if (!empty($current_process) && $current_process['mid'] == $row->mid && $current_process['recipient'] == $row->recipient && $current_process['type'] == $row->type) {
      $offset = $current_process['offset'];
    }

    $load_function = $type['generate recipients'];
    $uids = $load_function($recipient, $total_remaining, $offset);
    if (!empty($uids)) {
      foreach ($uids as $uid) {
        privatemsg_message_change_recipient($row->mid, $uid, 'hidden');
      }
    }
    // If less than the total remaining uids were returned, we are finished.
    if (count($uids) < $total_remaining) {
      $total_remaining -= count($uids);
      db_update('pm_index')
        ->fields(array('is_new' => PRIVATEMSG_READ))
        ->condition('mid', $row->mid)
        ->condition('recipient', $row->recipient)
        ->condition('type', $row->type)
        ->execute();
      // Reset current process if necessary.
      if ($offset > 0) {
        variable_set('privatemsg_cron_recipient_process', array());
      }
    }
    else {
      // We are not yet finished, save current process and break.
      $existing_offset = isset($current_process['offset']) ? $current_process['offset'] : 0;
      $current_process = (array)$row;
      $current_process['offset'] = $existing_offset + count($uids);
      variable_set('privatemsg_cron_recipient_process', $current_process);
      break;
    }
  }
litwol's avatar
litwol committed
function privatemsg_theme() {
litwol's avatar
litwol committed
    '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__',
    ),
    'privatemsg_list_field'   => array(
      'file'                  => 'privatemsg.theme.inc',
      'path'                  => drupal_get_path('module', 'privatemsg'),
      'pattern'               => 'privatemsg_list_field__',
      'variables'             => array('thread' => array()),
litwol's avatar
litwol committed
    ),
      'file'                  => 'privatemsg.theme.inc',
      'path'                  => drupal_get_path('module', 'privatemsg'),
      'variables'             => array('count'),
    'privatemsg_username'  => array(
      'file'                  => 'privatemsg.theme.inc',
      'path'                  => drupal_get_path('module', 'privatemsg'),
      'variables'             => array('recipient' => NULL, 'options' => array()),
    ),
  // Include the theme file to load the theme suggestions.
  module_load_include('inc', 'privatemsg', 'privatemsg.theme');
  $templates += drupal_find_theme_functions($templates, array('theme'));
  return $templates;
litwol's avatar
litwol committed

function template_preprocess_privatemsg_view(&$vars) {
litwol's avatar
litwol committed

  $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));
  // Directly address the current user if he is the author.
  if ($user->uid == $message->author->uid){
    $vars['author_name_link'] = t('You');
  }
  else {
    $vars['author_name_link'] = privatemsg_recipient_format($message->author);
  }
  $vars['message_timestamp'] = privatemsg_format_date($message->timestamp);

  $message->content = array(
    '#view_mode' => 'message',
    'body' => array(
      '#markup' => check_markup($message->body, $message->format),
      '#weight' => -4,
    ),
  );

  if ($message->has_tokens) {
    // Replace tokens including option to add a notice if the user is not a
    // recipient.
    $message->content['body']['#markup'] = privatemsg_token_replace($message->content['body']['#markup'], array('privatemsg_message' => $message), array('privatemsg-token-notice' => TRUE, 'sanitize' => TRUE));
  }

  // Build fields content.
  field_attach_prepare_view('privatemsg_message', array($vars['mid'] => $message), 'message');
  $message->content += field_attach_view('privatemsg_message', $message, 'message');
  // Render message body.
  $vars['message_body'] =  drupal_render($message->content);
  if (isset($vars['mid']) && isset($vars['thread_id']) && privatemsg_user_access('delete privatemsg')) {
    $vars['message_actions'][] = array('title' => t('Delete'), 'href' => 'messages/delete/' . $vars['thread_id'] . '/' . $vars['mid']);
  }
  $vars['message_anchors'][] = 'privatemsg-mid-' . $vars['mid'];
    $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('privatemsg-message-actions', 'links', 'inline')))) : '';
  $vars['anchors'] = '';
  foreach ($vars['message_anchors'] as $anchor) {
    $vars['anchors'].= '<a name="' . $anchor . '"></a>';
  }
litwol's avatar
litwol committed

function template_preprocess_privatemsg_recipients(&$vars) {
  $vars['participants'] = ''; // assign a default empty value
  if (isset($vars['thread']['participants'])) {
    $vars['participants'] = _privatemsg_format_participants($vars['thread']['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('recipient', $account->uid)
    ->condition('type', array('hidden', 'user'))
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.
 * @param $account
 *   For which account should the messages be loaded. *
 * @param $ignore_hidden
 *   Ignores hidden participants.
 * @param $access
 *   Which access permission should be checked (write or view).
 *
 * @return
 *   Array with all visible/writable participants for that thread.
function _privatemsg_load_thread_participants($thread_id, $account, $ignore_hidden = TRUE, $access = 'write') {
  $query = _privatemsg_assemble_query('participants', $thread_id, $account);
  $participants = array();
  $to_load = array();
  foreach ($query->execute() as $participant) {
    if ($ignore_hidden && $participant->type == 'hidden') {
      continue;
    }
    if ($participant->type == 'user' || $participant->type == 'hidden') {
      if ($participant = _privatemsg_user_load($participant->recipient)) {
        $participants[privatemsg_recipient_key($participant)] = $participant;
      }
    }
    elseif (privatemsg_recipient_access($participant->type, $access, $participant)) {
      $to_load[$participant->type][] = $participant->recipient;
    }
  }

  // Now, load all non-user recipients.
  foreach ($to_load as $type => $ids) {
    $type_info = privatemsg_recipient_get_type($type);
    if (isset($type_info['load']) && is_callable($type_info['load'])) {
      $loaded = $type_info['load']($ids);
      if (is_array($loaded)) {
        $participants += $loaded;
      }
    }
  }
  if ($access == 'write' && $account) {
    // Remove author if loading participants for writing and when he is not the
    // only recipient.
    if (isset($participants['user_' . $account->uid]) && count($participants) > 1) {
      unset($participants['user_' . $account->uid]);
    }
  }
  return $participants;
/**
 * 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 $type
 *   Array of recipient types this should be limited to.
 *
 * @return
 *   Array, first element is an array of loaded user objects, second an array
 *   with invalid names.
 *
 */
function _privatemsg_parse_userstring($input, $types_limitations = array()) {
  if (is_string($input)) {
    $input = explode(',', $input);
  }

  // Start working through the input array.
litwol's avatar
litwol committed
  $invalid = array();
  foreach ($input as $string) {
    $string = trim($string);
    // Ignore spaces.
    if (!empty($string)) {

      // First, collect all matches.
      $matches = array();

      // Remember if a possible match denies access.
      $access_denied = FALSE;

      // Load recipient types.
      $types = privatemsg_recipient_get_types();

      foreach (module_implements('privatemsg_name_lookup') as $module) {
        $function = $module . '_privatemsg_name_lookup';
        $return = $function($string);
        if (isset($return) && is_array($return)) {
         foreach ($return as $recipient) {
           // Save recipients under their key to merge recipients which were
           // loaded multiple times.
           if (empty($recipient->type)) {
             $recipient->type = 'user';
             $recipient->recipient = $recipient->uid;
           }
           $matches[privatemsg_recipient_key($recipient)] = $recipient;
         }
        // Check permissions, remove any recipients the user doesn't have write
        // access for.
        if (!privatemsg_recipient_access($recipient->type, 'write', $recipient)) {
          unset($matches[$key]);
          $access_denied = TRUE;
        // Appliy limitations.
        if (!empty($types_limitations) && !in_array($recipient->type, $types_limitations)) {
          unset($matches[$key]);
        }

      // Allow modules to alter the found matches.
      drupal_alter('privatemsg_name_lookup_matches', $matches, $string);

      // Check if there are any matches.
      $number_of_matches = count($matches);
      switch ($number_of_matches) {
        case 1:
          // Only a single match found, add to recipients.
          $recipients += $matches;
          break;
        case 0:
          // No match found, check if access was denied.
          if ($access_denied) {
            // There were possible matches, but access was denied.
            $denieds[$string] = $string;
          }
          else {
            // The string does not contain any valid recipients.
            $invalid[$string] = $string;
          }
          break;

        default:
          // Multiple matches were found. The user has to specify which one he
          // meant.
          $duplicates[$string] = $matches;
          break;
      }
  // Todo: Provide better API.
  return array($recipients, $invalid, $duplicates, $denieds);
}
litwol's avatar
litwol committed

/**
 * Implements hook_privatemsg_name_lookup().
 */
function privatemsg_privatemsg_name_lookup($string) {
  // Remove optonal user specifier.
  $string = trim(str_replace(t('[user]'), '', $string));
  // 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)) {
      $recipient->recipient = $recipient->uid;
      $recipient->type = 'user';
      return array(privatemsg_recipient_key($recipient) => $recipient);
    }
  }
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.recipient', $account->uid)
    ->condition('pmi.type', array('hidden', 'user'))
    ->condition('pmi.deleted', 0);
  $query->setCountQuery($count_query);
litwol's avatar
litwol committed