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) { $to = array(); $limited = FALSE; foreach ($part_array as $account) { if (is_int($limit) && count($to) >= $limit) { $limited = TRUE; break; } $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)); } } return ''; } /** * Implementation of hook_menu(). */ function privatemsg_menu() { $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', 'type' => MENU_NORMAL_ITEM, 'menu_name' => 'user-menu', ); $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', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, 'menu_name' => 'user-menu', ); $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), 'title' => 'Read message', 'page callback' => 'privatemsg_view', 'page arguments' => array(2), 'file' => 'privatemsg.pages.inc', 'access callback' => 'privatemsg_view_access', 'access arguments' => array(2), 'type' => MENU_LOCAL_TASK, 'weight' => -5, 'menu_name' => 'user-menu', ); $items['messages/delete/%privatemsg_thread/%privatemsg_message'] = array( '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'), 'type' => MENU_CALLBACK, 'weight' => -10, 'menu_name' => 'user-menu', ); $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', 'access arguments' => array('write privatemsg'), 'type' => MENU_LOCAL_ACTION, 'weight' => -3, 'menu_name' => 'user-menu', ); // 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', 'access arguments' => array('write privatemsg'), '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( '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_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, 'menu' => 'user-menu', ); $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'), 'type' => MENU_LOCAL_TASK, ); 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()); if ((int)$thread_id > 0) { $thread = array('thread_id' => $thread_id); if (is_null($account)) { global $user; $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; } else { $start = $thread['message_count'] - (variable_get('privatemsg_view_use_max_as_default', FALSE) ? variable_get('privatemsg_view_default_amount', 10) : $max_amount); } } if ($start != PRIVATEMSG_UNLIMITED) { if ($max_amount == PRIVATEMSG_UNLIMITED) { $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; } } else { // 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]; } return FALSE; } /** * Implementation of hook_privatemsg_view_template(). * * Allows modules to define different message view template. * * This hook returns information about available themes for privatemsg viewing. * * array( * 'machine_template_name' => 'Human readable template name', * 'machine_template_name_2' => 'Human readable template name 2' * }; */ function privatemsg_privatemsg_view_template() { return array( 'privatemsg-view' => 'Default view', ); } /** * 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(); } } } function privatemsg_theme() { return array( 'privatemsg_view' => array( 'variables' => array('message' => NULL), 'template' => variable_get('private_message_view_template', 'privatemsg-view'), // 'privatemsg', ), 'privatemsg_from' => array( 'variables' => array('author' => NULL), 'template' => 'privatemsg-from', ), 'privatemsg_recipients' => array( 'variables' => array('message' => NULL), 'template' => 'privatemsg-recipients', ), 'privatemsg_between' => array( 'variables' => array('recipients' => NULL), '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'), ), 'privatemsg_new_block' => array( 'file' => 'privatemsg.theme.inc', 'path' => drupal_get_path('module', 'privatemsg'), 'arguments' => array('count'), ), ); } function privatemsg_preprocess_privatemsg_view(&$vars) { // drupal_set_message('
'. print_r($vars,1 ) . '
'); $message = $vars['message']; $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'])); /** * @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')); } // 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'].= ''; } } 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']); } } /** * Changes the read/new status of a single message. * * @param $pmid * Message id * @param $status * Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD * @param $account * User object, defaults to the current user */ function privatemsg_message_change_status($pmid, $status, $account = NULL) { if (!$account) { global $user; $account = $user; } db_update('pm_index') ->fields(array('is_new' => $status)) ->condition('mid', $pmid) ->condition('uid', $account->uid) ->execute(); } /** * Return number of unread messages for an account. * * @param $account * Specifiy the user for which the unread count should be loaded. * * @ingroup api */ function privatemsg_unread_count($account = NULL) { $counts = &drupal_static(__FUNCTION__, array()); if (!$account || $account->uid == 0) { global $user; $account = $user; } if (!isset($counts[$account->uid])) { $counts[$account->uid] = _privatemsg_assemble_query('unread_count', $account) ->execute() ->fetchField(); } 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. $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; } } $invalid[$string] = $string; } } return array($recipients, $invalid); } /** * @addtogroup sql * @{ */ /** * 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); // Required columns $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'); } if (in_array('participants', $fields)) { // 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'); } else { $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') ->orderBy('MAX(pmi.is_new)', 'DESC') ->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. * * @param $pmids * Array of pmids. * @param $account * 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; } /** * 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. $query ->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; } /** * 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); if (!empty($names)) { $query->condition('u.name', $names, 'NOT IN'); } return $query; } /** * 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, ); } } /** * 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 %unread.', 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.'), ); } foreach($recipients as $recipient) { if (privatemsg_is_disabled($recipient)) { $blocked[] = array('uid' => $recipient->uid, 'message' => t('%recipient has disabled private message receiving.', array('%recipient' => $recipient->name)) ); } } return $blocked; } /** * Implements hook_block_info(). */ function privatemsg_block_info() { $blocks = array(); $blocks['privatemsg-menu'] = array( 'info' => t('Privatemsg links'), 'cache' => DRUPAL_NO_CACHE, ); $blocks['privatemsg-new'] = array( 'info' => t('New message indication'), 'cache' => DRUPAL_NO_CACHE, ); return $blocks; } function privatemsg_block_view($delta) { $block = array(); switch ($delta) { case 'privatemsg-menu': $block = _privatemsg_block_menu(); break; case 'privatemsg-new': $block = _privatemsg_block_new(); break; } return $block; } function privatemsg_title_callback($title = NULL) { $count = privatemsg_unread_count(); if ($count > 0) { return format_plural($count, 'Messages (1 new)', 'Messages (@count new)'); } return t('Messages'); } function _privatemsg_block_new() { $block = array(); if (!privatemsg_user_access()) { return $block; } $count = privatemsg_unread_count(); if ($count) { $block = array( 'subject' => format_plural($count, 'New message', 'New messages'), 'content' => theme('privatemsg_new_block', array('count' => $count)), ); return $block; } return array(); } function _privatemsg_block_menu() { $block = array(); $links = array(); if (privatemsg_user_access('write privatemsg')) { $links[] = l(t('Write new message'), 'messages/new'); } if (privatemsg_user_access('read privatemsg') || privatemsg_user_access('read all private messages') ) { $links[] = l(privatemsg_title_callback(), 'messages'); } if (count($links)) { $block = array( 'subject' => t('Private messages'), 'content' => theme('item_list', array('links' => $links)), ); } return $block; } /** * Delete or restore a message. * * @param $pmid * Message id, pm.mid field. * @param $delete * Either deletes or restores the thread (1 => delete, 0 => restore) * @param $account * User acccount for which the delete action should be carried out - Set to NULL to delete for all users. * * @ingroup api */ function privatemsg_message_change_delete($pmid, $delete, $account = NULL) { $delete_value = 0; if ($delete == TRUE) { $delete_value = time(); } $update = db_update('pm_index') ->fields(array('deleted' => $delete_value)) ->condition('mid', $pmid); if ($account){ $update->condition('uid', $account->uid); } $update->execute(); } /** * Send a new message. * * This functions does send a message in a new thread. * Example: * @code * privatemsg_new_thread(array(user_load(5)), 'The subject', 'The body text'); * @endcode * * @param $recipients * Array of recipients (user objects) * @param $subject * The subject of the new message * @param $body * The body text of the new message * @param $options * Additional options, possible keys: * author => User object of the author * timestamp => Time when the message was sent * * @return * An array with a key success. If TRUE, it also contains a key 'message' with * the created $message array, the same that is passed to the insert hook. * If FALSE, it contains a key 'messages'. This key contains an array where * the key is the error type (error, warning, notice) and an array with * messages of that type. * * It is theoretically possible for success to be TRUE and message to be FALSE. * For example if one of the privatemsg database tables become corrupted. When testing * for success of message being sent it is always best to see if ['message'] is not FALSE * as well as ['success'] is TRUE. * * Example: * @code * array('error' => array('A error message')) * @endcode * * @ingroup api */ function privatemsg_new_thread($recipients, $subject, $body = NULL, $options = array()) { global $user; $author = clone $user; $message = array(); $message['subject'] = $subject; $message['body'] = $body; // Make sure that recipients are keyed by user id and are not added // multiple times. foreach ($recipients as $recipient) { $message['recipients'][$recipient->uid] = $recipient; } // Set custom options, if any. if (!empty($options)) { $message += $options; } // Apply defaults - this will not overwrite existing keys. $message += array( 'author' => $author, 'timestamp' => time(), 'format' => filter_default_format($author), ); $validated = _privatemsg_validate_message($message); if ($validated['success']) { $validated['message'] = _privatemsg_send($message); } return $validated; } /** * Send a reply message * * This functions replies on an existing thread. * * @param $thread_id * Thread id * @param $body * The body text of the new message * @param $options * Additional options, possible keys: * author => User object of the author * timestamp => Time when the message was sent * * @return * An array with a key success and messages. This key contains an array where * the key is the error type (error, warning, notice) and an array with * messages of that type.. If success is TRUE, it also contains a key $message * with the created $message array, the same that is passed to * hook_privatemsg_message_insert(). * * It is theoretically possible for success to be TRUE and message to be FALSE. * For example if one of the privatemsg database tables become corrupted. When testing * for success of message being sent it is always best to see if ['message'] is not FALSE * as well as ['success'] is TRUE. * * Example messages values: * @code * array('error' => array('A error message')) * @endcode * * @ingroup api */ function privatemsg_reply($thread_id, $body, $options = array()) { global $user; $author = clone $user; $message = array(); $message['body'] = $body; // set custom options, if any if (!empty($options)) { $message += $options; } // apply defaults $message += array( 'author' => $author, 'timestamp' => time(), 'format' => filter_default_format($author), ); // We don't know the subject and the recipients, so we need to load them.. // thread_id == mid on the first message of the thread $first_message = privatemsg_message_load($thread_id, $message['author']); if (!$first_message) { return array( 'success' => FALSE, 'messages' => array('error' => array(t('Thread %thread_id not found, unable to answer', array('%thread_id' => $thread_id)))), ); } $message['thread_id'] = $thread_id; // Load participants. $message['recipients'] = _privatemsg_load_thread_participants($thread_id); // Remove author. if (isset($message['recipients'][$message['author']->uid]) && count($message['recipients']) > 1) { unset($message['recipients'][$message['author']->uid]); } $message['subject'] = $first_message['subject']; $validated = _privatemsg_validate_message($message); if ($validated['success']) { $validated['message'] = _privatemsg_send($message); } return $validated; } function _privatemsg_validate_message(&$message, $form = FALSE) { $messages = array('error' => array(), 'warning' => array()); if (!privatemsg_user_access('write privatemsg', $message['author'])) { // no need to do further checks in this case... if ($form) { form_set_error('author', t('User @user is not allowed to write messages', array('@user' => $message['author']->name))); return array( 'success' => FALSE, 'messages' => $messages, ); } else { $messages['error'][] = t('User @user is not allowed to write messages', array('@user' => $message['author']->name)); return array( 'success' => FALSE, 'messages' => $messages, ); } } if (empty($message['subject'])) { if ($form) { form_set_error('subject', t('Disallowed to send a message without subject')); } else { $messages['error'][] = t('Disallowed to send a message without subject'); } } // Don't allow replies without a body. if (!empty($message['thread_id']) && empty($message['body'])) { if ($form) { form_set_error('body', t('Disallowed to send reply without a message.')); } else { $messages['error'][] = t('Disallowed to send reply without a message.'); } } // Check if an allowed format is used. if (!filter_access(filter_format_load($message['format']), $message['author'])) { if ($form) { form_set_error('format', t('You are not allowed to use the specified input format.')); } else { $messages['error'][] = t('User @user is not allowed to use the specified input format.', array('@user' => $message['author']->name)); } } if (empty($message['recipients']) || !is_array($message['recipients'])) { if ($form) { form_set_error('to', t('Disallowed to send a message without atleast one valid recipient')); } else { $messages['error'][] = t('Disallowed to send a message without atleast one valid recipient'); } } if (!empty($message['recipients']) && is_array($message['recipients'])) { foreach(module_invoke_all('privatemsg_block_message', $message['author'], $message['recipients']) as $blocked) { unset($message['recipients'][$blocked['uid']]); if ($form) { drupal_set_message($blocked['message'], 'warning'); } else { $messages['warning'][] = $blocked['message']; } } } // Check again, give another error message if all recipients are blocked if (empty($message['recipients'])) { if ($form) { form_set_error('to', t('Disallowed to send message because all recipients are blocked')); } else { $messages['error'][] = t('Disallowed to send message because all recipients are blocked'); } } $messages += module_invoke_all('privatemsg_message_validate', $message, $form); // Check if there are errors in $messages or if $form is TRUE, there are form errors. $success = empty($messages['error']) || ($form && count((array)form_get_errors()) > 0); return array( 'success' => $success, 'messages' => $messages, ); } /** * Internal function to save a message. * * @param $message * A $message array with the data that should be saved. If a thread_id exists * it will be created as a reply to an existing thread. If not, a new thread * will be created. * * @return * The updated $message array. */ function _privatemsg_send($message) { drupal_alter('privatemsg_message_presave', $message); $query = db_insert('pm_index')->fields(array('mid', 'thread_id', 'uid', 'is_new', 'deleted')); if (isset($message['read_all']) && $message['read_all']) { // The message was sent in read all mode, add the author as recipient to all // existing messages. $query_messages = _privatemsg_assemble_query('messages', array($message['thread_id']), NULL); foreach ($query_messages->execute()->fetchCol() as $mid) { $query->values(array( 'mid' => $mid, 'thread_id' => $message['thread_id'], 'uid' => $message['author']->uid, 'is_new' => 0, 'deleted' => 0, )); } } // 1) Save the message body first. $args = array(); $args['subject'] = $message['subject']; $args['author'] = $message['author']->uid; $args['body'] = $message['body']; $args['format'] = $message['format']; $args['timestamp'] = $message['timestamp']; $mid = db_insert('pm_message') ->fields($args) ->execute(); $message['mid'] = $mid; // Thread ID is the same as the mid if it's the first message in the thread. if (!isset($message['thread_id'])) { $message['thread_id'] = $mid; } // 2) Save message to recipients. // Each recipient gets a record in the pm_index table. foreach ($message['recipients'] as $recipient) { $query->values(array( 'mid' => $mid, 'thread_id' => $message['thread_id'], 'uid' => $recipient->uid, 'is_new' => 1, 'deleted' => 0, )); } // When author is also the recipient, we want to set message to UNREAD. // All other times the message is set to READ. $is_new = isset($message['recipients'][$message['author']->uid]) ? 1 : 0; // Also add a record for the author to the pm_index table. $query->values(array( 'mid' => $mid, 'thread_id' => $message['thread_id'], 'uid' => $message['author']->uid, 'is_new' => $is_new, 'deleted' => 0, )); $query->execute(); module_invoke_all('privatemsg_message_insert', $message); // If we reached here that means we were successful at writing all messages to db. return $message; } /** * Returns a link to send message form for a specific users. * * Contains permission checks of author/recipient, blocking and * if a anonymous user is involved. * * @param $recipient * Recipient of the message * @param $account * Sender of the message, defaults to the current user * * @return * Either FALSE or a URL string * * @ingroup api */ function privatemsg_get_link($recipients, $account = array(), $subject = NULL) { if ($account == NULL) { global $user; $account = $user; } if (!is_array($recipients)) { $recipients = array($recipients); } if (!privatemsg_user_access('write privatemsg', $account) || $account->uid == 0) { return FALSE; } $validated = array(); foreach ($recipients as $recipient) { if (!privatemsg_user_access('read privatemsg', $recipient)) { continue; } if (variable_get('privatemsg_display_link_self', TRUE) == FALSE && $account->uid == $recipient->uid) { continue; } if (count(module_invoke_all('privatemsg_block_message', $account, array($recipient))) > 0) { continue; } $validated[] = $recipient->uid; } if (empty($validated)) { return FALSE; } $url = 'messages/new/'. implode(',', $validated); if (!is_null($subject)) { $url .= '/'. $subject; } return $url; } /** * Load a single message. * * @param $pmid * Message id, pm.mid field * @param $account * For which account the message should be loaded. * Defaults to the current user. * * @ingroup api */ function privatemsg_message_load($pmid, $account = NULL) { $messages = privatemsg_message_load_multiple(array($pmid), $account); return current($messages); } /** * Load multiple messages. * * @param $pmids * Array of Message ids, pm.mid field * @param $account * For which account the message should be loaded. * Defaults to the current user. * * @ingroup api */ function privatemsg_message_load_multiple($pmids, $account = NULL) { // Avoid SQL error that would happen with an empty pm.mid IN () clause. if (empty($pmids)) { return array(); } $result = _privatemsg_assemble_query('load', $pmids, $account)->execute(); $messages = array(); while ($message = $result->fetchAssoc()) { $message['user'] = $account; // Load author of message. if (!($message['author'] = user_load($message['author']))) { // If user does not exist, load anonymous user. $message['author'] = user_load(array('uid' => 0)); } $returned = module_invoke_all('privatemsg_message_load', $message); if (!empty($returned)) { $message = array_merge_recursive($returned, $message); } $messages[$message['mid']] = $message; } return $messages; } /** * Generates a query based on a query id. * * @param $query * Either be a string ('some_id') or an array('group_name', 'query_id'), * if a string is supplied, group_name defaults to 'privatemsg'. * * @return * Array with the keys query and count. count can be used to count the * elements which would be returned by query. count can be used together * with pager_query(). * * @ingroup sql */ function _privatemsg_assemble_query($query) { // Modules will be allowed to choose the prefix for the querybuilder, but if there is not one supplied, 'privatemsg' will be taken by default. if (is_array($query)) { $query_id = $query[0]; $query_group = $query[1]; } else { $query_id = $query; $query_group = 'privatemsg'; } /** * Begin: dynamic arguments */ $args = func_get_args(); unset($args[0]); // We do the merge because we call call_user_func_array and not drupal_alter. // This is necessary because otherwise we would not be able to use $args // correctly (otherwise it doesnt unfold). $query_function = $query_group .'_sql_'. $query_id; if (!function_exists($query_function)) { drupal_set_message(t('Query function %function does not exist', array('%function' => $query_function)), 'error'); return FALSE; } $query = call_user_func_array($query_function, $args); // Add a tag to make it alterable. $query->addTag($query_group . '_' . $query_id); // Add arguments as metadata. foreach ($args as $id => $arg) { $query->addMetaData('arg_' . $id, $arg); } return $query; } /** * Marks one or multiple threads as (un)read. * * @param $threads * Array with thread id's or a single thread id. * @param $status * Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD, sets the new status. * @param $account * User object for which the threads should be deleted, defaults to the current user. */ function privatemsg_thread_change_status($threads, $status, $account = NULL) { if (!is_array($threads)) { $threads = array($threads); } if (empty($account)) { global $user; $account = clone $user; } // Merge status and uid with the threads list. array_merge() will not overwrite/ignore thread_id 1. $params = array_merge(array($status, $account->uid), $threads); db_update('pm_index') ->fields(array('is_new' => $status)) ->condition('thread_id', $threads) ->condition('uid', $account->uid) ->execute(); if ($status == PRIVATEMSG_UNREAD) { drupal_set_message(format_plural(count($threads), 'Marked 1 thread as unread.', 'Marked @count threads as unread.')); } else { drupal_set_message(format_plural(count($threads), 'Marked 1 thread as read.', 'Marked @count threads as read.')); } } /** * Returns a table header definition based on the submitted keys. * * Uses @link theming theme patterns @endlink to theme single headers. * * @param $has_posts * TRUE when there is at least one row. Decides if the select all checkbox should be displayed. * @param $keys * Array with the keys which are present in the query/should be displayed. * @return * Array with header defintions for tablesort_sql and theme('table'). */ function _privatemsg_list_headers($keys) { // theme() doesn't include the theme file for patterns, we need to do it manually. include_once drupal_get_path('module', 'privatemsg') .'/privatemsg.theme.inc'; $header = array(); foreach ($keys as $key) { // First, try to load a specific theme for that header, if not present, use the default. if ($return = theme(array('privatemsg_list_header__' . $key , 'privatemsg_list_header'))) { // The default theme returns nothing, only store the value if we have something. $header[$key] = $return; } } if (count($header) == 0) { // No header definition returned, fallback to the default. $header += _privatemsg_list_headers_fallback($keys); } return $header; } /** * Table header definition for themes that don't support theme patterns. * * @return * Array with the correct headers. */ function _privatemsg_list_headers_fallback($keys) { $header = array(); foreach ($keys as $key) { $theme_function = 'phptemplate_privatemsg_list_header__' .$key; if (function_exists($theme_function)) { $header[$key] = $theme_function(); } } return $header; } /** * Formats all rows (#options) in the privatemsg tableselect thread list. * * Uses @link theming theme patterns @endlink to theme single fields. * * @param $thread * Array with the row data returned by the database. * @return * Row definition for use with theme('table') */ function _privatemsg_list_thread($tableselect) { foreach ($tableselect['#options'] as $id => $thread) { $row = array(); if (!empty($thread['is_new'])) { // Set the css class in the tr tag. $row['#attributes']['class'][] = 'privatemsg-unread'; } foreach ($thread as $key => $data) { // First, try to load a specific theme for that field, if not present, use the default. if ($return = theme(array('privatemsg_list_field__'. $key, 'privatemsg_list_field'), $thread)) { // The default theme returns nothing, only store the value if we have something. $row['data'][$key] = $return; } } if (empty($row['data'])) { $row = _privatemsg_list_thread_fallback($thread); } $tableselect['#options'][$id] = $row; } return $tableselect; } /** * Table row definition for themes that don't support theme patterns. * * @return * Array with row data. */ function _privatemsg_list_thread_fallback($thread) { $row_data = array(); foreach ($thread as $key => $data) { $theme_function = 'phptemplate_privatemsg_list_field__' .$key; if (function_exists($theme_function)) { $row_data[$key] = $theme_function($thread); } } return $row_data; } /** * Execute an operation on a number of threads. * * @param $operation * The operation that should be executed. * @see hook_privatemsg_thread_operations() * @param $threads * An array of thread ids. The array is filtered before used, a checkboxes * array can be directly passed to it. */ function privatemsg_operation_execute($operation, $threads) { // Filter out unchecked threads, this gives us an array of "checked" threads. $threads = array_filter($threads); if (empty($threads)) { // Do not execute anything if there are no checked threads. return; } // Add in callback arguments if present. if (isset($operation['callback arguments'])) { $args = array_merge(array($threads), $operation['callback arguments']); } else { $args = array($threads); } // Execute the chosen action and pass the defined arguments. call_user_func_array($operation['callback'], $args); // Check if that operation has defined an undo callback. if (isset($operation['undo callback']) && $undo_function = $operation['undo callback']) { // Add in callback arguments if present. if (isset($operation['undo callback arguments'])) { $undo_args = array_merge(array($threads), $operation['undo callback arguments']); } else { $undo_args = array($threads); } // Store the undo callback in the session and display a "Undo" link. // @todo: Provide a more flexible solution for such an undo action, operation defined string for example. $_SESSION['privatemsg']['undo callback'] = array('function' => $undo_function, 'args' => $undo_args); $undo = url('messages/undo/action', array('query' => drupal_get_destination())); drupal_set_message(t('The previous action can be undone.', array('!undo' => $undo))); } } /** * Delete or restore one or multiple threads. * * @param $threads * Array with thread id's or a single thread id. * @param $delete * Indicates if the threads should be deleted or restored. 1 => delete, 0 => restore. * @param $account * User acccount for which the delete action should be carried out - Set to NULL to delete for all users. */ function privatemsg_thread_change_delete($threads, $delete, $account = NULL) { if (!is_array($threads)) { $threads = array($threads); } if (empty($account)) { global $user; $account = clone $user; } // Merge status and uid with the threads list. array_merge() will not overwrite/ignore thread_id 1. $params = array_merge(array($delete, $account->uid), $threads); // Load all messages of those threads including the deleted. $query = _privatemsg_assemble_query('messages', $threads, $account, TRUE); // Delete each message. We need to do that to trigger the delete hook. foreach ($query->execute() as $row) { privatemsg_message_change_delete($row->mid, $delete, $account); } if ($delete) { drupal_set_message(format_plural(count($threads), 'Deleted 1 thread.', 'Deleted @count threads.')); } else { drupal_set_message(format_plural(count($threads), 'Restored 1 thread.', 'Restored @count threads.')); } } /** * Implementation of hook_privatemsg_thread_operations(). */ function privatemsg_privatemsg_thread_operations() { $operations = array( 'mark as read' => array( 'label' => t('Mark as read'), 'callback' => 'privatemsg_thread_change_status', 'callback arguments' => array('status' => PRIVATEMSG_READ), 'undo callback' => 'privatemsg_thread_change_status', 'undo callback arguments' => array('status' => PRIVATEMSG_UNREAD), ), 'mark as unread' => array( 'label' => t('Mark as unread'), 'callback' => 'privatemsg_thread_change_status', 'callback arguments' => array('status' => PRIVATEMSG_UNREAD), 'undo callback' => 'privatemsg_thread_change_status', 'undo callback arguments' => array('status' => PRIVATEMSG_READ), ), ); if (privatemsg_user_access('delete privatemsg')) { $operations['delete'] = array( 'callback' => 'privatemsg_thread_change_delete', 'callback arguments' => array('delete' => 1), 'undo callback' => 'privatemsg_thread_change_delete', 'undo callback arguments' => array('delete' => 0), ); } return $operations; } /** * Implements hook_node_view(). */ function privatemsg_node_view($node, $view_mode) { $types = array_filter(variable_get('privatemsg_link_node_types', array())); $url = privatemsg_get_link(user_load($node->uid)); if (in_array($node->type, $types) && !empty($url) && ($view_mode == 'full' || (variable_get('privatemsg_display_on_teaser', 1) && $view_mode == 'teaser'))) { $links['privatemsg_link'] = array( 'title' => t('Send author a message'), 'href' => $url . '/' . t('Message regarding @node', array( '@node' => $node->title['und'][0]['value'])), 'query' => drupal_get_destination(), ); $node->content['links']['privatemsg'] = array( '#theme' => 'links', '#links' => $links, '#attributes' => array('class' => array('links', 'inline')), ); } } /** * Implements hook_comment_view(). */ function privatemsg_comment_view($comment) { $types = array_filter(variable_get('privatemsg_link_node_types', array())); $url = privatemsg_get_link(user_load($comment->uid)); if (in_array(node_load($comment->nid)->type, $types) && !empty($url) && variable_get('privatemsg_display_on_comments', 0)) { $links['privatemsg_link'] = array( 'title' => t('Send author a message'), 'href' => $url . '/' . t('Message regarding @comment', array('@comment' => trim($comment->subject))), 'query' => drupal_get_destination(), ); $comment->content['links']['privatemsg'] = array( '#theme' => 'links', '#links' => $links, '#attributes' => array('class' => array('links', 'inline')), ); } } /** * Implementation of hook_views_api(). */ function privatemsg_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'privatemsg') . '/views', ); }