privatemsg.module

  1. 1 privatemsg.module
  2. 7-1 privatemsg.module
  3. 7-2 privatemsg.module
  4. 6-2 privatemsg.module

Allows users to send private messages to other users.

File

privatemsg.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Allows users to send private messages to other users.
  5. */
  6. /**
  7. * Status constant for read messages.
  8. */
  9. define('PRIVATEMSG_READ', 0);
  10. /**
  11. * Status constant for unread messages.
  12. */
  13. define('PRIVATEMSG_UNREAD', 1);
  14. /**
  15. * Show unlimited messages in a thread.
  16. */
  17. define('PRIVATEMSG_UNLIMITED', 'unlimited');
  18. /**
  19. * Implements hook_permission().
  20. */
  21. function privatemsg_permission() {
  22. return array(
  23. 'administer privatemsg settings' => array(
  24. 'title' => t('Administer privatemsg'),
  25. 'description' => t('Perform maintenance tasks for privatemsg'),
  26. ),
  27. 'read privatemsg' => array(
  28. 'title' => t('Read private messages'),
  29. 'description' => t('Read private messages'),
  30. ),
  31. 'read all private messages' => array(
  32. 'title' => t('Read all private messages'),
  33. 'description' => t('Includes messages of other users'),
  34. ),
  35. 'write privatemsg' => array(
  36. 'title' => t('Write new private messages'),
  37. 'description' => t('Write new private messages'),
  38. ),
  39. 'delete privatemsg' => array(
  40. 'title' => t('Delete private messages'),
  41. 'description' => t('Delete private messages'),
  42. ),
  43. 'allow disabling privatemsg' => array(
  44. 'title' => t('Allow disabling private messages'),
  45. 'description' => t("Allows user to disable privatemsg so that they can't receive or send any private messages.")
  46. ),
  47. 'reply only privatemsg' => array(
  48. 'title' => t('Reply to private messages'),
  49. 'description' => t('Allows to reply to private messages but not send new ones. Note that the write new private messages permission includes replies.')
  50. ),
  51. 'use tokens in privatemsg' => array(
  52. 'title' => t('Use tokens in private messages'),
  53. 'description' => t("Allows user to use available tokens when sending private messages.")
  54. ),
  55. 'select text format for privatemsg' => array(
  56. 'title' => t('Select text format for private messages'),
  57. 'description' => t('Allows to choose the text format when sending private messages. Otherwise, the default is used.'),
  58. ),
  59. );
  60. }
  61. /**
  62. * Generate array of user objects based on a string.
  63. *
  64. *
  65. * @param $userstring
  66. * A string with user id, for example 1,2,4. Returned by the list query.
  67. *
  68. * @return
  69. * Array with user objects.
  70. */
  71. function _privatemsg_generate_user_array($string, $slice = NULL) {
  72. // Convert user uid list (uid1,uid2,uid3) into an array. If $slice is not NULL
  73. // pass that as argument to array_slice(). For example, -4 will only load the
  74. // last four users.
  75. // This is done to avoid loading user objects that are not displayed, for
  76. // obvious performance reasons.
  77. $users = explode(',', $string);
  78. if (!is_null($slice)) {
  79. $users = array_slice($users, $slice);
  80. }
  81. $participants = array();
  82. foreach ($users as $uid) {
  83. // If it is an integer, it is a user id.
  84. if ((int)$uid > 0) {
  85. $user_ids = privatemsg_user_load_multiple(array($uid));
  86. if ($account = array_shift($user_ids)) {
  87. $participants[privatemsg_recipient_key($account)] = $account;
  88. }
  89. }
  90. elseif (strpos($uid, '_') !== FALSE) {
  91. list($type, $id) = explode('_', $uid);
  92. $type_info = privatemsg_recipient_get_type($type);
  93. if ($type_info && isset($type_info['load']) && is_callable($type_info['load'])) {
  94. $temp_load = $type_info['load'](array($id));
  95. if ($participant = array_shift($temp_load)) {
  96. $participants[privatemsg_recipient_key($participant)] = $participant;
  97. }
  98. }
  99. }
  100. }
  101. return $participants;
  102. }
  103. /**
  104. * Format an array of user objects.
  105. *
  106. * @param $part_array
  107. * Array with user objects, for example the one returned by
  108. * _privatemsg_generate_user_array.
  109. *
  110. * @param $limit
  111. * Limit the number of user objects which should be displayed.
  112. * @param $no_text
  113. * When TRUE, don't display the Participants/From text.
  114. * @return
  115. * String with formatted user objects, like user1, user2.
  116. */
  117. function _privatemsg_format_participants($part_array, $limit = NULL, $no_text = FALSE) {
  118. global $user;
  119. if (count($part_array) > 0) {
  120. $to = array();
  121. $limited = FALSE;
  122. foreach ($part_array as $account) {
  123. // Directly address the current user.
  124. if (isset($account->type) && in_array($account->type, array('hidden', 'user')) && $account->recipient == $user->uid) {
  125. array_unshift($to, $no_text ? t('You') : t('you'));
  126. continue;
  127. }
  128. // Don't display recipients with type hidden.
  129. if (isset($account->type) && $account->type == 'hidden') {
  130. continue;
  131. }
  132. if (is_int($limit) && count($to) >= $limit) {
  133. $limited = TRUE;
  134. break;
  135. }
  136. $to[] = privatemsg_recipient_format($account);
  137. }
  138. $limit_string = '';
  139. if ($limited) {
  140. $limit_string = t(' and others');
  141. }
  142. if ($no_text) {
  143. return implode(', ', $to) . $limit_string;
  144. }
  145. $last = array_pop($to);
  146. if (count($to) == 0) { // Only one participant
  147. return t("From !last", array('!last' => $last));
  148. }
  149. else { // Multiple participants..
  150. $participants = implode(', ', $to);
  151. return t('Between !participants and !last', array('!participants' => $participants, '!last' => $last));
  152. }
  153. }
  154. return '';
  155. }
  156. /**
  157. * Implements hook_menu().
  158. */
  159. function privatemsg_menu() {
  160. $items['messages'] = array(
  161. 'title' => 'Messages',
  162. 'title callback' => 'privatemsg_title_callback',
  163. 'page callback' => 'privatemsg_list_page',
  164. 'page arguments' => array('list'),
  165. 'file' => 'privatemsg.pages.inc',
  166. 'access callback' => 'privatemsg_user_access',
  167. 'type' => MENU_NORMAL_ITEM,
  168. 'menu_name' => 'user-menu',
  169. );
  170. $items['messages/list'] = array(
  171. 'title' => 'Messages',
  172. 'page callback' => 'privatemsg_list_page',
  173. 'page arguments' => array('list'),
  174. 'file' => 'privatemsg.pages.inc',
  175. 'access callback' => 'privatemsg_user_access',
  176. 'type' => MENU_DEFAULT_LOCAL_TASK,
  177. 'weight' => -10,
  178. 'menu_name' => 'user-menu',
  179. );
  180. $items['messages/view/%privatemsg_thread'] = array(
  181. // Set the third argument to TRUE so that we can show access denied instead
  182. // of not found.
  183. 'load arguments' => array(NULL, NULL, TRUE),
  184. 'title' => 'Read message',
  185. 'page callback' => 'privatemsg_view',
  186. 'page arguments' => array(2),
  187. 'file' => 'privatemsg.pages.inc',
  188. 'access callback' => 'privatemsg_view_access',
  189. 'access arguments' => array(2),
  190. 'type' => MENU_LOCAL_TASK,
  191. 'weight' => -5,
  192. 'menu_name' => 'user-menu',
  193. );
  194. $items['messages/delete/%privatemsg_thread/%privatemsg_message'] = array(
  195. 'title' => 'Delete message',
  196. 'page callback' => 'drupal_get_form',
  197. 'page arguments' => array('privatemsg_delete', 2, 3),
  198. 'file' => 'privatemsg.pages.inc',
  199. 'access callback' => 'privatemsg_user_access',
  200. 'access arguments' => array('delete privatemsg'),
  201. 'type' => MENU_CALLBACK,
  202. 'weight' => -10,
  203. 'menu_name' => 'user-menu',
  204. );
  205. $items['messages/new'] = array(
  206. 'title' => 'Write new message',
  207. 'page callback' => 'drupal_get_form',
  208. 'page arguments' => array('privatemsg_new', 2, 3, NULL),
  209. 'file' => 'privatemsg.pages.inc',
  210. 'access callback' => 'privatemsg_user_access',
  211. 'access arguments' => array('write privatemsg'),
  212. 'type' => MENU_LOCAL_ACTION,
  213. 'weight' => -3,
  214. 'menu_name' => 'user-menu',
  215. );
  216. // Auto-completes available user names & removes duplicates.
  217. $items['messages/autocomplete'] = array(
  218. 'page callback' => 'privatemsg_autocomplete',
  219. 'file' => 'privatemsg.pages.inc',
  220. 'access callback' => 'privatemsg_user_access',
  221. 'access arguments' => array('write privatemsg'),
  222. 'type' => MENU_CALLBACK,
  223. );
  224. $items['admin/config/messaging'] = array(
  225. 'title' => 'Messaging',
  226. 'description' => 'Messaging systems.',
  227. 'page callback' => 'system_admin_menu_block_page',
  228. 'access arguments' => array('access administration pages'),
  229. 'file' => 'system.admin.inc',
  230. 'file path' => drupal_get_path('module', 'system'),
  231. );
  232. $items['admin/config/messaging/privatemsg'] = array(
  233. 'title' => 'Private message settings',
  234. 'description' => 'Configure private messaging settings.',
  235. 'page callback' => 'drupal_get_form',
  236. 'page arguments' => array('privatemsg_admin_settings'),
  237. 'file' => 'privatemsg.admin.inc',
  238. 'access arguments' => array('administer privatemsg settings'),
  239. 'type' => MENU_NORMAL_ITEM,
  240. );
  241. $items['admin/config/messaging/privatemsg/settings'] = array(
  242. 'title' => 'Private message settings',
  243. 'description' => 'Configure private messaging settings.',
  244. 'page callback' => 'drupal_get_form',
  245. 'page arguments' => array('privatemsg_admin_settings'),
  246. 'file' => 'privatemsg.admin.inc',
  247. 'access arguments' => array('administer privatemsg settings'),
  248. 'type' => MENU_DEFAULT_LOCAL_TASK,
  249. 'weight' => -10,
  250. );
  251. $items['messages/undo/action'] = array(
  252. 'title' => 'Private messages',
  253. 'description' => 'Undo last thread action',
  254. 'page callback' => 'privatemsg_undo_action',
  255. 'file' => 'privatemsg.pages.inc',
  256. 'access arguments' => array('read privatemsg'),
  257. 'type' => MENU_CALLBACK,
  258. 'menu' => 'user-menu',
  259. );
  260. $items['user/%/messages'] = array(
  261. 'title' => 'Messages',
  262. 'page callback' => 'privatemsg_list_page',
  263. 'page arguments' => array('list', 1),
  264. 'file' => 'privatemsg.pages.inc',
  265. 'access callback' => 'privatemsg_user_access',
  266. 'access arguments' => array('read all private messages'),
  267. 'type' => MENU_LOCAL_TASK,
  268. );
  269. return $items;
  270. }
  271. /**
  272. * Implements hook_menu_local_tasks_alter().
  273. */
  274. function privatemsg_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  275. // Add action link to 'messages/new' on 'messages' page.
  276. $add_to_array = array('messages/list', 'messages/inbox', 'messages/sent');
  277. foreach ($add_to_array as $add_to) {
  278. if (strpos($root_path, $add_to) !== FALSE) {
  279. $item = menu_get_item('messages/new');
  280. if ($item['access']) {
  281. $data['actions']['output'][] = array(
  282. '#theme' => 'menu_local_action',
  283. '#link' => $item,
  284. );
  285. }
  286. break;
  287. }
  288. }
  289. }
  290. /**
  291. * Privatemsg wrapper for user_access.
  292. *
  293. * Never allows anonymous user access as that doesn't makes sense.
  294. *
  295. * @param $permission
  296. * Permission string, defaults to read privatemsg
  297. *
  298. * @return
  299. * TRUE if user has access, FALSE if not
  300. *
  301. * @ingroup api
  302. */
  303. function privatemsg_user_access($permission = 'read privatemsg', $account = NULL) {
  304. static $disabled_displayed = FALSE;
  305. if ( $account === NULL ) {
  306. global $user;
  307. $account = $user;
  308. }
  309. if (!$account->uid) { // Disallow anonymous access, regardless of permissions
  310. return FALSE;
  311. }
  312. if (privatemsg_is_disabled($account) && ($permission == 'write privatemsg') ) {
  313. if (arg(0) == 'messages' && variable_get('privatemsg_display_disabled_message', TRUE) && !$disabled_displayed) {
  314. $disabled_displayed = TRUE;
  315. 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');
  316. }
  317. return FALSE;
  318. }
  319. if (!user_access($permission, $account)) {
  320. return FALSE;
  321. }
  322. return TRUE;
  323. }
  324. /**
  325. * Check access to the view messages page.
  326. *
  327. * Function to restrict the access of the view messages page to just the
  328. * messages/view/% pages and not to leave tabs artifact on other lower
  329. * level pages such as the messages/new/%.
  330. *
  331. * @param $thread
  332. * A array containing all information about a specific thread, generated by
  333. * privatemsg_thread_load().
  334. *
  335. * @ingroup api
  336. */
  337. function privatemsg_view_access($thread) {
  338. // Do not allow access to threads without messages.
  339. if (empty($thread['messages'])) {
  340. // Count all messages, if there
  341. return FALSE;
  342. }
  343. if (privatemsg_user_access('read privatemsg') && arg(1) == 'view') {
  344. return TRUE;
  345. }
  346. return FALSE;
  347. }
  348. /**
  349. * Checks the status of private messaging for provided user.
  350. *
  351. * @param user object to check
  352. * @return TRUE if user has disabled private messaging, FALSE otherwise
  353. */
  354. function privatemsg_is_disabled($account) {
  355. if (!$account || !isset($account->uid) || !$account->uid) {
  356. return FALSE;
  357. }
  358. if (!isset($account->privatemsg_disabled)) {
  359. // Make sure we have a fully loaded user object and try to load it if not.
  360. if ((!empty($account->roles) || $account = user_load($account->uid)) && user_access('allow disabling privatemsg', $account)) {
  361. $account->privatemsg_disabled = (bool)db_query('SELECT 1 FROM {pm_disable} WHERE uid = :uid ', array(':uid' => $account->uid))->fetchField();
  362. }
  363. else {
  364. $account->privatemsg_disabled = FALSE;
  365. }
  366. }
  367. return $account->privatemsg_disabled;
  368. }
  369. /**
  370. * Load a thread with all the messages and participants.
  371. *
  372. * This function is called by the menu system through the %privatemsg_thread
  373. * wildcard.
  374. *
  375. * @param $thread_id
  376. * Thread id, pmi.thread_id or pm.mid of the first message in that thread.
  377. * @param $account
  378. * User object for which the thread should be loaded, defaults to
  379. * the current user.
  380. * @param $start
  381. * Message offset from the start of the thread.
  382. * @param $useAccessDenied
  383. * Set to TRUE if the function should forward to the access denied page
  384. * instead of not found. This is used by the menu system because that does
  385. * load arguments before access checks are made. Defaults to FALSE.
  386. *
  387. * @return
  388. * $thread object, with keys messages, participants, title and user. messages
  389. * contains an array of messages, participants an array of user, subject the
  390. * subject of the thread and user the user viewing the thread.
  391. *
  392. * If no messages are found, or the thread_id is invalid, the function returns
  393. * FALSE.
  394. * @ingroup api
  395. */
  396. function privatemsg_thread_load($thread_id, $account = NULL, $start = NULL, $useAccessDenied = FALSE) {
  397. $threads = &drupal_static(__FUNCTION__, array());
  398. $thread_id = (int)$thread_id;
  399. if ($thread_id > 0) {
  400. $thread = array('thread_id' => $thread_id);
  401. if (is_null($account)) {
  402. global $user;
  403. $account = clone $user;
  404. }
  405. if (!isset($threads[$account->uid])) {
  406. $threads[$account->uid] = array();
  407. }
  408. if (!array_key_exists($thread_id, $threads[$account->uid])) {
  409. // Load the list of participants.
  410. $thread['participants'] = _privatemsg_load_thread_participants($thread_id, $account, FALSE, 'view');
  411. $thread['read_all'] = FALSE;
  412. if (empty($thread['participants']) && privatemsg_user_access('read all private messages', $account)) {
  413. $thread['read_all'] = TRUE;
  414. // Load all participants.
  415. $thread['participants'] = _privatemsg_load_thread_participants($thread_id, FALSE, FALSE, 'view');
  416. }
  417. // Load messages returned by the messages query with privatemsg_message_load_multiple().
  418. $query = _privatemsg_assemble_query('messages', array($thread_id), $thread['read_all'] ? NULL : $account);
  419. // Use subquery to bypass group by since it is not possible to alter
  420. // existing GROUP BY statements.
  421. $countQuery = db_select($query);
  422. $countQuery->addExpression('COUNT(*)');
  423. $thread['message_count'] = $thread['to'] = $countQuery->execute()->fetchField();
  424. $thread['from'] = 1;
  425. // Check if we need to limit the messages.
  426. $max_amount = variable_get('privatemsg_view_max_amount', 20);
  427. // If there is no start value, select based on get params.
  428. if (is_null($start)) {
  429. if (isset($_GET['start']) && $_GET['start'] < $thread['message_count']) {
  430. $start = $_GET['start'];
  431. }
  432. elseif (!variable_get('privatemsg_view_use_max_as_default', FALSE) && $max_amount == PRIVATEMSG_UNLIMITED) {
  433. $start = PRIVATEMSG_UNLIMITED;
  434. }
  435. else {
  436. $start = $thread['message_count'] - (variable_get('privatemsg_view_use_max_as_default', FALSE) ? variable_get('privatemsg_view_default_amount', 10) : $max_amount);
  437. }
  438. }
  439. if ($start != PRIVATEMSG_UNLIMITED) {
  440. if ($max_amount == PRIVATEMSG_UNLIMITED) {
  441. $last_page = 0;
  442. $max_amount = $thread['message_count'];
  443. }
  444. else {
  445. // Calculate the number of messages on the "last" page to avoid
  446. // message overlap.
  447. // Note - the last page lists the earliest messages, not the latest.
  448. $paging_count = variable_get('privatemsg_view_use_max_as_default', FALSE) ? $thread['message_count'] - variable_get('privatemsg_view_default_amount', 10) : $thread['message_count'];
  449. $last_page = $paging_count % $max_amount;
  450. }
  451. // Sanity check - we cannot start from a negative number.
  452. if ($start < 0) {
  453. $start = 0;
  454. }
  455. $thread['start'] = $start;
  456. //If there are newer messages on the page, show pager link allowing to go to the newer messages.
  457. if (($start + $max_amount + 1) < $thread['message_count']) {
  458. $thread['to'] = $start + $max_amount;
  459. $thread['newer_start'] = $start + $max_amount;
  460. }
  461. if ($start - $max_amount >= 0) {
  462. $thread['older_start'] = $start - $max_amount;
  463. }
  464. elseif ($start > 0) {
  465. $thread['older_start'] = 0;
  466. }
  467. // Do not show messages on the last page that would show on the page
  468. // before. This will only work when using the visual pager.
  469. if ($start < $last_page && $max_amount != PRIVATEMSG_UNLIMITED && $max_amount < $thread['message_count']) {
  470. unset($thread['older_start']);
  471. $thread['to'] = $thread['newer_start'] = $max_amount = $last_page;
  472. // Start from the first message - this is a specific hack to make sure
  473. // the message display has sane paging on the last page.
  474. $start = 0;
  475. }
  476. // Visual counts start from 1 instead of zero, so plus one.
  477. $thread['from'] = $start + 1;
  478. $query->range($start, $max_amount);
  479. }
  480. $conditions = array();
  481. if (!$thread['read_all']) {
  482. $conditions['account'] = $account;
  483. }
  484. $thread['messages'] = privatemsg_message_load_multiple($query->execute()->fetchCol(), $conditions);
  485. // If there are no messages, don't allow access to the thread.
  486. if (empty($thread['messages'])) {
  487. if ($useAccessDenied) {
  488. // Generate new query with read all to see if the thread does exist.
  489. $query = _privatemsg_assemble_query('messages', array($thread_id), NULL);
  490. $exists = $query->countQuery()->execute()->fetchField();
  491. if (!$exists) {
  492. // Thread does not exist, display 404.
  493. $thread = FALSE;
  494. }
  495. }
  496. else {
  497. $thread = FALSE;
  498. }
  499. }
  500. else {
  501. // General data, assume subject is the same for all messages of that thread.
  502. $thread['user'] = $account;
  503. $message = current($thread['messages']);
  504. $thread['subject'] = $thread['subject-original'] = $message->subject;
  505. if ($message->has_tokens) {
  506. $thread['subject'] = privatemsg_token_replace($thread['subject'], array('privatemsg_message' => $message), array('sanitize' => TRUE, 'privatemsg-show-span' => FALSE));
  507. }
  508. }
  509. $threads[$account->uid][$thread_id] = $thread;
  510. }
  511. return $threads[$account->uid][$thread_id];
  512. }
  513. return FALSE;
  514. }
  515. /**
  516. * Implements hook_privatemsg_view_template().
  517. *
  518. * Allows modules to define different message view template.
  519. *
  520. * This hook returns information about available themes for privatemsg viewing.
  521. *
  522. * array(
  523. * 'machine_template_name' => 'Human readable template name',
  524. * 'machine_template_name_2' => 'Human readable template name 2'
  525. * };
  526. */
  527. function privatemsg_privatemsg_view_template() {
  528. return array(
  529. 'privatemsg-view' => 'Default view',
  530. );
  531. }
  532. /**
  533. * Implements hook_cron().
  534. *
  535. * If the flush feature is enabled, a given amount of deleted messages that are
  536. * old enough are flushed.
  537. */
  538. function privatemsg_cron() {
  539. if (variable_get('privatemsg_flush_enabled', FALSE)) {
  540. $query = _privatemsg_assemble_query('deleted', variable_get('privatemsg_flush_days', 30), variable_get('privatemsg_flush_max', 200));
  541. foreach ($query->execute()->fetchCol() as $mid) {
  542. $message = privatemsg_message_load($mid);
  543. module_invoke_all('privatemsg_message_flush', $message);
  544. // Delete recipients of the message.
  545. db_delete('pm_index')
  546. ->condition('mid', $mid)
  547. ->execute();
  548. // Delete message itself.
  549. db_delete('pm_message')
  550. ->condition('mid', $mid)
  551. ->execute();
  552. }
  553. }
  554. // Number of user ids to process for this cron run.
  555. $total_remaining = variable_get('privatemgs_cron_recipient_per_run', 1000);
  556. $current_process = variable_get('privatemsg_cron_recipient_process', array());
  557. // Instead of doing the order by in the database, which can be slow, we load
  558. // all results and the do the handling there. Additionally, explicitly specify
  559. // the desired types. If there are more than a few dozen results the site is
  560. // unhealthy anyway because this cron is unable to keep up with the
  561. // unprocessed recipients.
  562. $rows = array();
  563. // Get all type keys except user.
  564. $types = privatemsg_recipient_get_types();
  565. unset($types['user']);
  566. $types = array_keys($types);
  567. // If there are no other recipient types, there is nothing to do.
  568. if (empty($types)) {
  569. return;
  570. }
  571. $result = db_query("SELECT pmi.recipient, pmi.type, pmi.mid FROM {pm_index} pmi WHERE pmi.type IN (:types) AND pmi.is_new = 1", array(':types' => $types));
  572. foreach ($result as $row) {
  573. // If this is equal to the row that is currently processed, add it first in
  574. // the array.
  575. if (!empty($current_process) && $current_process['mid'] == $row->mid && $current_process['type'] == $row->type && $current_process['recipient'] == $row->recipient) {
  576. array_unshift($rows, $row);
  577. }
  578. else {
  579. $rows[] = $row;
  580. }
  581. }
  582. foreach ($rows as $row) {
  583. $type = privatemsg_recipient_get_type($row->type);
  584. if (isset($type['load']) && is_callable($type['load'])) {
  585. $loaded = $type['load'](array($row->recipient));
  586. if (empty($loaded)) {
  587. continue;
  588. }
  589. $recipient = reset($loaded);
  590. }
  591. // Check if we already started to process this recipient.
  592. $offset = 0;
  593. if (!empty($current_process) && $current_process['mid'] == $row->mid && $current_process['recipient'] == $row->recipient && $current_process['type'] == $row->type) {
  594. $offset = $current_process['offset'];
  595. }
  596. $load_function = $type['generate recipients'];
  597. $uids = $load_function($recipient, $total_remaining, $offset);
  598. if (!empty($uids)) {
  599. foreach ($uids as $uid) {
  600. privatemsg_message_change_recipient($row->mid, $uid, 'hidden');
  601. }
  602. }
  603. // If less than the total remaining uids were returned, we are finished.
  604. if (count($uids) < $total_remaining) {
  605. $total_remaining -= count($uids);
  606. db_update('pm_index')
  607. ->fields(array('is_new' => PRIVATEMSG_READ))
  608. ->condition('mid', $row->mid)
  609. ->condition('recipient', $row->recipient)
  610. ->condition('type', $row->type)
  611. ->execute();
  612. // Reset current process if necessary.
  613. if ($offset > 0) {
  614. variable_set('privatemsg_cron_recipient_process', array());
  615. }
  616. }
  617. else {
  618. // We are not yet finished, save current process and break.
  619. $existing_offset = isset($current_process['offset']) ? $current_process['offset'] : 0;
  620. $current_process = (array)$row;
  621. $current_process['offset'] = $existing_offset + count($uids);
  622. variable_set('privatemsg_cron_recipient_process', $current_process);
  623. break;
  624. }
  625. }
  626. }
  627. function privatemsg_theme() {
  628. $templates = array(
  629. 'privatemsg_view' => array(
  630. 'variables' => array('message' => NULL),
  631. 'template' => variable_get('private_message_view_template', 'privatemsg-view'), // 'privatemsg',
  632. ),
  633. 'privatemsg_from' => array(
  634. 'variables' => array('author' => NULL),
  635. 'template' => 'privatemsg-from',
  636. ),
  637. 'privatemsg_recipients' => array(
  638. 'variables' => array('message' => NULL),
  639. 'template' => 'privatemsg-recipients',
  640. ),
  641. 'privatemsg_between' => array(
  642. 'variables' => array('recipients' => NULL),
  643. 'template' => 'privatemsg-between',
  644. ),
  645. // Define pattern for header/field templates. The theme system will register all
  646. // theme functions that start with the defined pattern.
  647. 'privatemsg_list_header' => array(
  648. 'file' => 'privatemsg.theme.inc',
  649. 'path' => drupal_get_path('module', 'privatemsg'),
  650. 'pattern' => 'privatemsg_list_header__',
  651. 'variables' => array(),
  652. ),
  653. 'privatemsg_list_field' => array(
  654. 'file' => 'privatemsg.theme.inc',
  655. 'path' => drupal_get_path('module', 'privatemsg'),
  656. 'pattern' => 'privatemsg_list_field__',
  657. 'variables' => array('thread' => array()),
  658. ),
  659. 'privatemsg_new_block' => array(
  660. 'file' => 'privatemsg.theme.inc',
  661. 'path' => drupal_get_path('module', 'privatemsg'),
  662. 'variables' => array('count'),
  663. ),
  664. 'privatemsg_username' => array(
  665. 'file' => 'privatemsg.theme.inc',
  666. 'path' => drupal_get_path('module', 'privatemsg'),
  667. 'variables' => array('recipient' => NULL, 'options' => array()),
  668. ),
  669. );
  670. // Include the theme file to load the theme suggestions.
  671. module_load_include('inc', 'privatemsg', 'privatemsg.theme');
  672. $templates += drupal_find_theme_functions($templates, array('theme'));
  673. return $templates;
  674. }
  675. function template_preprocess_privatemsg_view(&$vars) {
  676. global $user;
  677. $message = $vars['message'];
  678. $vars['mid'] = isset($message->mid) ? $message->mid : NULL;
  679. $vars['message_classes'] = isset($message->classes) ? $message->classes : array();
  680. $vars['thread_id'] = isset($message->thread_id) ? $message->thread_id : NULL;
  681. $vars['author_picture'] = theme('user_picture', array('account' => $message->author));
  682. // Directly address the current user if he is the author.
  683. if ($user->uid == $message->author->uid) {
  684. $vars['author_name_link'] = t('You');
  685. }
  686. else {
  687. $vars['author_name_link'] = privatemsg_recipient_format($message->author);
  688. }
  689. $vars['message_timestamp'] = privatemsg_format_date($message->timestamp);
  690. $message->content = array(
  691. '#view_mode' => 'message',
  692. 'body' => array(
  693. '#markup' => check_markup($message->body, $message->format),
  694. '#weight' => -4,
  695. ),
  696. );
  697. if ($message->has_tokens) {
  698. // Replace tokens including option to add a notice if the user is not a
  699. // recipient.
  700. $message->content['body']['#markup'] = privatemsg_token_replace($message->content['body']['#markup'], array('privatemsg_message' => $message), array('privatemsg-token-notice' => TRUE, 'sanitize' => TRUE));
  701. }
  702. // Build fields content.
  703. field_attach_prepare_view('privatemsg_message', array($vars['mid'] => $message), 'message');
  704. $message->content += field_attach_view('privatemsg_message', $message, 'message');
  705. // Render message body.
  706. $vars['message_body'] = drupal_render($message->content);
  707. if (isset($vars['mid']) && isset($vars['thread_id']) && privatemsg_user_access('delete privatemsg')) {
  708. $vars['message_actions'][] = array('title' => t('Delete'), 'href' => 'messages/delete/' . $vars['thread_id'] . '/' . $vars['mid']);
  709. }
  710. $vars['message_anchors'][] = 'privatemsg-mid-' . $vars['mid'];
  711. if (!empty($message->is_new)) {
  712. $vars['message_anchors'][] = 'new';
  713. $vars['new'] = drupal_ucfirst(t('new'));
  714. }
  715. // call hook_privatemsg_message_view_alter
  716. drupal_alter('privatemsg_message_view', $vars);
  717. $vars['message_actions'] = !empty($vars['message_actions']) ? theme('links', array('links' => $vars['message_actions'], 'attributes' => array('class' => array('privatemsg-message-actions', 'links', 'inline')))) : '';
  718. $vars['anchors'] = '';
  719. foreach ($vars['message_anchors'] as $anchor) {
  720. $vars['anchors'] .= '<a name="' . $anchor . '"></a>';
  721. }
  722. }
  723. function template_preprocess_privatemsg_recipients(&$vars) {
  724. $vars['participants'] = ''; // assign a default empty value
  725. if (isset($vars['thread']['participants'])) {
  726. $vars['participants'] = _privatemsg_format_participants($vars['thread']['participants']);
  727. }
  728. }
  729. /**
  730. * Changes the read/new status of a single message.
  731. *
  732. * @param $pmid
  733. * Message id
  734. * @param $status
  735. * Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD
  736. * @param $account
  737. * User object, defaults to the current user
  738. */
  739. function privatemsg_message_change_status($pmid, $status, $account = NULL) {
  740. if (!$account) {
  741. global $user;
  742. $account = $user;
  743. }
  744. db_update('pm_index')
  745. ->fields(array('is_new' => $status))
  746. ->condition('mid', $pmid)
  747. ->condition('recipient', $account->uid)
  748. ->condition('type', array('hidden', 'user'))
  749. ->execute();
  750. // Allows modules to respond to the status change.
  751. module_invoke_all('privatemsg_message_status_changed', $pmid, $status, $account);
  752. }
  753. /**
  754. * Return number of unread messages for an account.
  755. *
  756. * @param $account
  757. * Specify the user for which the unread count should be loaded.
  758. *
  759. * @ingroup api
  760. */
  761. function privatemsg_unread_count($account = NULL) {
  762. $counts = &drupal_static(__FUNCTION__, array());
  763. if (!$account || $account->uid == 0) {
  764. global $user;
  765. $account = $user;
  766. }
  767. if (!isset($counts[$account->uid])) {
  768. $counts[$account->uid] = _privatemsg_assemble_query('unread_count', $account)
  769. ->execute()
  770. ->fetchField();
  771. }
  772. return $counts[$account->uid];
  773. }
  774. /**
  775. * Load all participants of a thread.
  776. *
  777. * @param $thread_id
  778. * Thread ID for which the participants should be loaded.
  779. * @param $account
  780. * For which account should the messages be loaded. *
  781. * @param $ignore_hidden
  782. * Ignores hidden participants.
  783. * @param $access
  784. * Which access permission should be checked (write or view).
  785. *
  786. * @return
  787. * Array with all visible/writable participants for that thread.
  788. */
  789. function _privatemsg_load_thread_participants($thread_id, $account, $ignore_hidden = TRUE, $access = 'write') {
  790. $query = _privatemsg_assemble_query('participants', $thread_id, $account);
  791. $participants = array();
  792. $to_load = array();
  793. foreach ($query->execute() as $participant) {
  794. if ($ignore_hidden && $participant->type == 'hidden') {
  795. continue;
  796. }
  797. elseif (privatemsg_recipient_access($participant->type, $access, $participant)) {
  798. $to_load[$participant->type][] = $participant->recipient;
  799. }
  800. }
  801. // Now, load all non-user recipients.
  802. foreach ($to_load as $type => $ids) {
  803. $type_info = privatemsg_recipient_get_type($type);
  804. if (isset($type_info['load']) && is_callable($type_info['load'])) {
  805. $loaded = $type_info['load']($ids);
  806. if (is_array($loaded)) {
  807. $participants += $loaded;
  808. }
  809. }
  810. }
  811. if ($access == 'write' && $account) {
  812. // Remove author if loading participants for writing and when he is not the
  813. // only recipient.
  814. if (isset($participants['user_' . $account->uid]) && count($participants) > 1) {
  815. unset($participants['user_' . $account->uid]);
  816. }
  817. }
  818. return $participants;
  819. }
  820. /**
  821. * Extract the valid usernames of a string and loads them.
  822. *
  823. * This function is used to parse a string supplied by a username autocomplete
  824. * field and load all user objects.
  825. *
  826. * @param $string
  827. * A string in the form "usernameA, usernameB, ...".
  828. * @return $type
  829. * Array of recipient types this should be limited to.
  830. *
  831. * @return
  832. * Array, first element is an array of loaded user objects, second an array
  833. * with invalid names.
  834. *
  835. */
  836. function _privatemsg_parse_userstring($input, $types_limitations = array()) {
  837. if (is_string($input)) {
  838. $input = explode(',', $input);
  839. }
  840. // Start working through the input array.
  841. $invalid = array();
  842. $recipients = array();
  843. $duplicates = array();
  844. $denieds = array();
  845. foreach ($input as $string) {
  846. $string = trim($string);
  847. // Ignore spaces.
  848. if (!empty($string)) {
  849. // First, collect all matches.
  850. $matches = array();
  851. // Remember if a possible match denies access.
  852. $access_denied = FALSE;
  853. // Collect matches from hook implementations.
  854. foreach (module_implements('privatemsg_name_lookup') as $module) {
  855. $function = $module . '_privatemsg_name_lookup';
  856. $return = $function($string);
  857. if (isset($return) && is_array($return)) {
  858. foreach ($return as $recipient) {
  859. // Save recipients under their key to merge recipients which were
  860. // loaded multiple times.
  861. if (empty($recipient->type)) {
  862. $recipient->type = 'user';
  863. $recipient->recipient = $recipient->uid;
  864. }
  865. $matches[privatemsg_recipient_key($recipient)] = $recipient;
  866. }
  867. }
  868. }
  869. foreach ($matches as $key => $recipient) {
  870. // Check permissions, remove any recipients the user doesn't have write
  871. // access for.
  872. if (!privatemsg_recipient_access($recipient->type, 'write', $recipient)) {
  873. unset($matches[$key]);
  874. $access_denied = TRUE;
  875. }
  876. // Apply limitations.
  877. if (!empty($types_limitations) && !in_array($recipient->type, $types_limitations)) {
  878. unset($matches[$key]);
  879. }
  880. }
  881. // Allow modules to alter the found matches.
  882. drupal_alter('privatemsg_name_lookup_matches', $matches, $string);
  883. // Check if there are any matches.
  884. $number_of_matches = count($matches);
  885. switch ($number_of_matches) {
  886. case 1:
  887. // Only a single match found, add to recipients.
  888. $recipients += $matches;
  889. break;
  890. case 0:
  891. // No match found, check if access was denied.
  892. if ($access_denied) {
  893. // There were possible matches, but access was denied.
  894. $denieds[$string] = $string;
  895. }
  896. else {
  897. // The string does not contain any valid recipients.
  898. $invalid[$string] = $string;
  899. }
  900. break;
  901. default:
  902. // Multiple matches were found. The user has to specify which one he
  903. // meant.
  904. $duplicates[$string] = $matches;
  905. break;
  906. }
  907. }
  908. }
  909. // Todo: Provide better API.
  910. return array($recipients, $invalid, $duplicates, $denieds);
  911. }
  912. /**
  913. * Implements hook_privatemsg_name_lookup().
  914. */
  915. function privatemsg_privatemsg_name_lookup($string) {
  916. // Remove optional user specifier.
  917. $string = trim(str_replace('[user]', '', $string));
  918. // Fall back to the default username lookup.
  919. if (!$error = module_invoke('user', 'validate_name', $string)) {
  920. // String is a valid username, look it up.
  921. if ($recipient = user_load_by_name($string)) {
  922. $recipient->recipient = $recipient->uid;
  923. $recipient->type = 'user';
  924. return array(privatemsg_recipient_key($recipient) => $recipient);
  925. }
  926. }
  927. }
  928. /**
  929. * @addtogroup sql
  930. * @{
  931. */
  932. /**
  933. * Query definition to load a list of threads.
  934. *
  935. * @param $account
  936. * User object for which the messages are being loaded.
  937. * @param $argument
  938. * string argument which can be used in the query builder to modify the thread listing.
  939. *
  940. * @see hook_query_privatemsg_list_alter()
  941. */
  942. function privatemsg_sql_list($account, $argument = 'list') {
  943. $query = db_select('pm_message', 'pm')->extend('TableSort')->extend('PagerDefault');
  944. $query->join('pm_index', 'pmi', 'pm.mid = pmi.mid');
  945. // Create count query;
  946. $count_query = db_select('pm_message', 'pm');
  947. $count_query->addExpression('COUNT(DISTINCT pmi.thread_id)', 'count');
  948. $count_query->join('pm_index', 'pmi', 'pm.mid = pmi.mid');
  949. $count_query
  950. ->condition('pmi.recipient', $account->uid)
  951. ->condition('pmi.type', array('hidden', 'user'))
  952. ->condition('pmi.deleted', 0);
  953. $query->setCountQuery($count_query);
  954. // Required columns
  955. $query->addField('pmi', 'thread_id');
  956. $query->addExpression('MIN(pm.subject)', 'subject');
  957. $query->addExpression('MAX(pm.timestamp)', 'last_updated');
  958. $query->addExpression('MAX(pm.has_tokens)', 'has_tokens');
  959. $query->addExpression('SUM(pmi.is_new)', 'is_new');
  960. // Load enabled columns
  961. $fields = array_filter(variable_get('privatemsg_display_fields', array('participants')));
  962. if (in_array('count', $fields)) {
  963. // We only want the distinct number of messages in this thread.
  964. $query->addExpression('COUNT(distinct pmi.mid)', 'count');
  965. }
  966. if (in_array('participants', $fields)) {
  967. // Query for a string with uids, for example "1,6,7". This needs a subquery on PostgreSQL.
  968. if (db_driver() == 'pgsql') {
  969. $query->addExpression("array_to_string(array(SELECT DISTINCT pmia.type || '_' || pmia.recipient
  970. FROM {pm_index} pmia
  971. WHERE pmia.type <> 'hidden' AND pmia.thread_id = pmi.thread_id AND pmia.recipient <> :current), ',')", 'participants', array(':current' => $account->uid));
  972. }
  973. else {
  974. $query->addExpression("(SELECT GROUP_CONCAT(DISTINCT CONCAT(pmia.type, '_', pmia.recipient))
  975. FROM {pm_index} pmia
  976. WHERE pmia.type <> 'hidden' AND pmia.thread_id = pmi.thread_id AND pmia.recipient <> :current)", 'participants', array(':current' => $account->uid));
  977. }
  978. }
  979. if (in_array('thread_started', $fields)) {
  980. $query->addExpression('MIN(pm.timestamp)', 'thread_started');
  981. }
  982. return $query
  983. ->condition('pmi.recipient', $account->uid)
  984. ->condition('pmi.type', array('hidden', 'user'))
  985. ->condition('pmi.deleted', 0)
  986. ->groupBy('pmi.thread_id')
  987. ->orderByHeader(_privatemsg_list_headers(array_merge(array('subject', 'last_updated'), $fields)))
  988. ->limit(variable_get('privatemsg_per_page', 25));
  989. }
  990. /**
  991. * Query definition to load messages of one or multiple threads.
  992. *
  993. * @param $threads
  994. * Array with one or multiple thread id's.
  995. * @param $account
  996. * User object for which the messages are being loaded.
  997. * @param $load_all
  998. * Deleted messages are only loaded if this is set to TRUE.
  999. *
  1000. * @see hook_query_privatemsg_messages_alter()
  1001. */
  1002. function privatemsg_sql_messages($threads, $account = NULL, $load_all = FALSE) {
  1003. $query = db_select('pm_index', 'pmi');
  1004. $query->addField('pmi', 'mid');
  1005. $query->join('pm_message', 'pm', 'pm.mid = pmi.mid');
  1006. if (!$load_all) {
  1007. $query->condition('pmi.deleted', 0);
  1008. }
  1009. // If there are multiple inserts during the same second (tests, for example)
  1010. // sort by mid second to have them in the same order as they were saved.
  1011. $query
  1012. ->condition('pmi.thread_id', $threads)
  1013. ->groupBy('pm.timestamp')
  1014. ->groupBy('pmi.mid')
  1015. // Order by timestamp first.
  1016. ->orderBy('pm.timestamp', 'ASC')
  1017. // If there are multiple inserts during the same second (tests, for example)
  1018. // sort by mid second to have them in the same order as they were saved.
  1019. ->orderBy('pmi.mid', 'ASC');
  1020. if ($account) {
  1021. $query
  1022. ->condition('pmi.recipient', $account->uid)
  1023. ->condition('pmi.type', array('hidden', 'user'));
  1024. }
  1025. return $query;
  1026. }
  1027. /**
  1028. * Load all participants of a thread.
  1029. *
  1030. * @param $thread_id
  1031. * Thread id from which the participants should be loaded.
  1032. * @param $account
  1033. * User account that should be considered when loading participants.
  1034. *
  1035. * @see hook_query_privatemsg_participants_alter()
  1036. */
  1037. function privatemsg_sql_participants($thread_id, $account = NULL) {
  1038. $query = db_select('pm_index', 'pmi');
  1039. $query->leftJoin('users', 'u', "u.uid = pmi.recipient AND pmi.type IN ('user', 'hidden')");
  1040. $query
  1041. ->fields('pmi', array('recipient', 'type'))
  1042. ->fields('u', array('name'))
  1043. ->condition('pmi.thread_id', $thread_id);
  1044. // If an account is provided, limit participants.
  1045. if ($account) {
  1046. $query->condition(db_or()
  1047. ->condition('pmi.type', 'hidden', '<>')
  1048. ->condition(db_and()
  1049. ->condition('pmi.type', 'hidden')
  1050. ->condition('pmi.recipient', $account->uid)
  1051. ));
  1052. // Only load recipients of messages which are visible for that user.
  1053. $query->where('(SELECT 1 FROM {pm_index} pmiu WHERE pmi.mid = pmiu.mid AND pmiu.recipient = :recipient LIMIT 1) = 1', array(':recipient' => $account->uid));
  1054. }
  1055. else {
  1056. // If not, only limit participants to visible ones.
  1057. $query->condition('pmi.type', 'hidden', '<>');
  1058. }
  1059. return $query
  1060. ->groupBy('pmi.recipient')
  1061. ->groupBy('u.name')
  1062. ->groupBy('pmi.type');
  1063. }
  1064. /**
  1065. * Count threads with unread messages.
  1066. *
  1067. * @param $account
  1068. * User account for which should be checked.
  1069. *
  1070. * @see hook_query_privatemsg_unread_count_alter()
  1071. */
  1072. function privatemsg_sql_unread_count($account) {
  1073. $query = db_select('pm_index', 'pmi');
  1074. $query->addExpression('COUNT(DISTINCT thread_id)', 'unread_count');
  1075. return $query
  1076. ->condition('pmi.deleted', 0)
  1077. ->condition('pmi.is_new', 1)
  1078. ->condition('pmi.recipient', $account->uid)
  1079. ->condition('pmi.type', array('hidden', 'user'));
  1080. }
  1081. /**
  1082. * Looks up autocomplete suggestions for users.
  1083. *
  1084. * @param $search
  1085. * The string that is being searched for.
  1086. * @param $names
  1087. * Array of names which are already selected and should be excluded.
  1088. *
  1089. * @see hook_query_privatemsg_autocomplete_alter()
  1090. */
  1091. function privatemsg_sql_autocomplete($search, $names) {
  1092. $query = db_select('users', 'u')
  1093. ->fields('u', array('uid'))
  1094. ->condition('u.name', $search . '%', 'LIKE')
  1095. ->condition('u.status', 0, '<>')
  1096. ->where('NOT EXISTS (SELECT 1 FROM {pm_disable} pd WHERE pd.uid=u.uid)')
  1097. ->orderBy('u.name', 'ASC')
  1098. ->range(0, 10);
  1099. if (!empty($names)) {
  1100. $query->condition('u.name', $names, 'NOT IN');
  1101. }
  1102. return $query;
  1103. }
  1104. /**
  1105. * Query Builder function to load all messages that should be flushed.
  1106. *
  1107. * @param $days
  1108. * Select messages older than x days.
  1109. * @param $max
  1110. * Select no more than $max messages.
  1111. *
  1112. * @see hook_query_privatemsg_deleted_alter()
  1113. */
  1114. function privatemsg_sql_deleted($days, $max) {
  1115. $query = db_select('pm_message', 'pm');
  1116. $query->addField('pm', 'mid');
  1117. $query->join('pm_index', 'pmi', 'pmi.mid = pm.mid');
  1118. return $query
  1119. ->groupBy('pm.mid')
  1120. ->having('MIN(pmi.deleted) > 0 AND MAX(pmi.deleted) < :old', array(':old' => REQUEST_TIME - $days * 86400))
  1121. ->range(0, $max);
  1122. }
  1123. /**
  1124. * @}
  1125. */
  1126. /**
  1127. * Implements hook_user_view().
  1128. */
  1129. function privatemsg_user_view($account) {
  1130. if (($url = privatemsg_get_link(array($account))) && variable_get('privatemsg_display_profile_links', 1)) {
  1131. $account->content['privatemsg_send_new_message'] = array(
  1132. '#type' => 'link',
  1133. '#title' => t('Send this user a private message'),
  1134. '#href' => $url,
  1135. '#weight' => 10,
  1136. '#options' => array(
  1137. 'query' => drupal_get_destination(),
  1138. 'title' => t('Send this user a private message'),
  1139. 'attributes' => array('class' => 'privatemsg-send-link privatemsg-send-link-profile'),
  1140. ),
  1141. );
  1142. }
  1143. }
  1144. /**
  1145. * Implements hook_user_login().
  1146. */
  1147. function privatemsg_user_login(&$edit, $account) {
  1148. if (variable_get('privatemsg_display_loginmessage', TRUE) && privatemsg_user_access()) {
  1149. $count = privatemsg_unread_count();
  1150. if ($count) {
  1151. drupal_set_message(format_plural($count, 'You have <a href="@messages">1 unread message</a>.', 'You have <a href="@messages">@count unread messages</a>', array('@messages' => url('messages'))));
  1152. }
  1153. }
  1154. }
  1155. /**
  1156. * Implements hook_user_cancel().
  1157. */
  1158. function privatemsg_user_cancel($edit, $account, $method) {
  1159. switch ($method) {
  1160. case 'user_cancel_reassign':
  1161. db_update('pm_message')
  1162. ->condition('author', $account->uid)
  1163. ->fields(array('author' => 0))
  1164. ->execute();
  1165. break;
  1166. case 'user_cancel_block_unpublish':
  1167. _privatemsg_delete_data($account);
  1168. break;
  1169. }
  1170. // Always delete user settings.
  1171. db_delete('pm_disable')
  1172. ->condition('uid', $account->uid)
  1173. ->execute();
  1174. }
  1175. /**
  1176. * Implements hook_user_delete().
  1177. */
  1178. function privatemsg_user_delete($account) {
  1179. _privatemsg_delete_data($account);
  1180. // Always delete user settings.
  1181. db_delete('pm_disable')
  1182. ->condition('uid', $account->uid)
  1183. ->execute();
  1184. }
  1185. /**
  1186. * Delete all message data from a user.
  1187. */
  1188. function _privatemsg_delete_data($account) {
  1189. $mids = db_select('pm_message', 'pm')
  1190. ->fields('pm', array('mid'))
  1191. ->condition('author', $account->uid)
  1192. ->execute()
  1193. ->fetchCol();
  1194. if (!empty($mids)) {
  1195. // Delete recipient entries in {pm_index} of the messages the user wrote.
  1196. db_delete('pm_index')
  1197. ->condition('mid', $mids)
  1198. ->execute();
  1199. }
  1200. // Delete messages the user wrote.
  1201. db_delete('pm_message')
  1202. ->condition('author', $account->uid)
  1203. ->execute();
  1204. // Delete recipient entries of that user.
  1205. db_delete('pm_index')
  1206. ->condition('recipient', $account->uid)
  1207. ->condition('type', array('user', 'hidden'))
  1208. ->execute();
  1209. // DELETE any disable flag for user.
  1210. db_delete('pm_disable')
  1211. ->condition('uid', $account->uid)
  1212. ->execute();
  1213. }
  1214. /**
  1215. * Implements hook_form_alter().
  1216. */
  1217. function privatemsg_form_alter(&$form, &$form_state, $form_id) {
  1218. if (($form_id == 'user_register_form' || $form_id == 'user_profile_form') && $form['#user_category'] == 'account') {
  1219. // Create array to be able to merge in fieldset and avoid overwriting
  1220. // already added options.
  1221. if (!isset($form['privatemsg'])) {
  1222. $form['privatemsg'] = array();
  1223. }
  1224. // Always create the fieldset in case other modules want to add
  1225. // Privatemsg-related settings through hook_form_alter(). If it's still
  1226. // empty after the build process, the after build function will remove it.
  1227. $form['privatemsg'] += array(
  1228. '#type' => 'fieldset',
  1229. '#title' => t('Private messages'),
  1230. '#collapsible' => TRUE,
  1231. '#collapsed' => FALSE,
  1232. '#weight' => 10,
  1233. '#after_build' => array('privatemsg_account_fieldset_remove_if_empty'),
  1234. );
  1235. // We have to use user_access() because privatemsg_user_access() would
  1236. // return FALSE when privatemsg is disabled.
  1237. if ((user_access('write privatemsg') || user_access('read privatemsg')) && user_access('allow disabling privatemsg')) {
  1238. $form['privatemsg']['pm_enable'] = array(
  1239. '#type' => 'checkbox',
  1240. '#title' => t('Enable private messages'),
  1241. '#default_value' => !privatemsg_is_disabled($form['#user']),
  1242. '#description' => t('Disabling private messages prevents you from sending or receiving messages from other users.'),
  1243. '#weight' => -10,
  1244. );
  1245. }
  1246. }
  1247. }
  1248. /**
  1249. * Hides the settings fieldset if there are no options to be displayed.
  1250. */
  1251. function privatemsg_account_fieldset_remove_if_empty($element) {
  1252. if (count(element_children($element)) == 0) {
  1253. $element['#access'] = FALSE;
  1254. }
  1255. return $element;
  1256. }
  1257. /**
  1258. * Implements hook_user_insert().
  1259. */
  1260. function privatemsg_user_update(&$edit, $account, $category) {
  1261. if (isset($edit['pm_enable']) && (user_access('write privatemsg') || user_access('read privatemsg')) && user_access('allow disabling privatemsg')) {
  1262. $current = privatemsg_is_disabled($account);
  1263. $disabled = (!$edit['pm_enable']);
  1264. $edit['pm_enable'] = NULL;
  1265. $account->privatemsg_disabled = $disabled;
  1266. // only perform the save if the value has changed
  1267. if ($current != $disabled) {
  1268. if ($disabled) {
  1269. db_insert('pm_disable')
  1270. ->fields(array('uid' => $account->uid))
  1271. ->execute();
  1272. }
  1273. else {
  1274. db_delete('pm_disable')
  1275. ->condition('uid', $account->uid)
  1276. ->execute();
  1277. }
  1278. }
  1279. }
  1280. }
  1281. /**
  1282. * Implements hook_privatemsg_block_message().
  1283. */
  1284. function privatemsg_privatemsg_block_message($author, $recipients, $context = array()) {
  1285. $blocked = array();
  1286. if (privatemsg_is_disabled($author)) {
  1287. $blocked[] = array(
  1288. 'recipient' => 'user_' . $author->uid,
  1289. 'message' => t('You have disabled private message sending and receiving.'),
  1290. );
  1291. }
  1292. foreach ($recipients as $recipient) {
  1293. if (privatemsg_is_disabled($recipient)) {
  1294. $blocked[] = array(
  1295. 'recipient' => 'user_' . $recipient->uid,
  1296. 'message' => t('%recipient has disabled private message receiving.', array('%recipient' => privatemsg_recipient_format($recipient, array('plain' => TRUE)))),
  1297. );
  1298. }
  1299. // Do not send private messages to blocked users.
  1300. else if (isset($recipient->status) && (!$recipient->status)) {
  1301. $blocked[] = array(
  1302. 'recipient' => 'user_' . $recipient->uid,
  1303. 'message' => t('%recipient has disabled his or her account.', array('%recipient' => privatemsg_recipient_format($recipient, array('plain' => TRUE)))),
  1304. );
  1305. }
  1306. }
  1307. return $blocked;
  1308. }
  1309. /**
  1310. * Implements hook_block_info().
  1311. */
  1312. function privatemsg_block_info() {
  1313. $blocks = array();
  1314. $blocks['privatemsg-menu'] = array(
  1315. 'info' => t('Privatemsg links'),
  1316. 'cache' => DRUPAL_NO_CACHE,
  1317. );
  1318. $blocks['privatemsg-new'] = array(
  1319. 'info' => t('New message indication'),
  1320. 'cache' => DRUPAL_NO_CACHE,
  1321. );
  1322. return $blocks;
  1323. }
  1324. /**
  1325. * Implements hook_block_view().
  1326. */
  1327. function privatemsg_block_view($delta) {
  1328. $block = array();
  1329. switch ($delta) {
  1330. case 'privatemsg-menu':
  1331. $block = _privatemsg_block_menu();
  1332. break;
  1333. case 'privatemsg-new':
  1334. $block = _privatemsg_block_new();
  1335. break;
  1336. }
  1337. return $block;
  1338. }
  1339. /**
  1340. * Implements hook_block_configure().
  1341. */
  1342. function privatemsg_block_configure($delta = '') {
  1343. if ($delta == 'privatemsg-new') {
  1344. $form['notification'] = array(
  1345. '#type' => 'checkbox',
  1346. '#title' => t('Display block when there are no new messages'),
  1347. '#default_value' => variable_get('privatemsg_no_messages_notification', 0),
  1348. '#description' => t('Enable this to have this block always displayed, even if there are no new messages'),
  1349. );
  1350. return $form;
  1351. }
  1352. }
  1353. /**
  1354. * Implements hook_block_save().
  1355. */
  1356. function privatemsg_block_save($delta = '', $edit = array()) {
  1357. if ($delta == 'privatemsg-new') {
  1358. variable_set('privatemsg_no_messages_notification', $edit['notification']);
  1359. }
  1360. }
  1361. function privatemsg_title_callback($title = NULL) {
  1362. $count = privatemsg_unread_count();
  1363. if ($count > 0) {
  1364. return format_plural($count, 'Messages (1 new)', 'Messages (@count new)');
  1365. }
  1366. return t('Messages');
  1367. }
  1368. function _privatemsg_block_new() {
  1369. $block = array();
  1370. if (!privatemsg_user_access()) {
  1371. return $block;
  1372. }
  1373. $count = privatemsg_unread_count();
  1374. if ($count || variable_get('privatemsg_no_messages_notification', 0)) {
  1375. $block = array(
  1376. 'subject' => $count ? format_plural($count, 'New message', 'New messages') : t('No new messages'),
  1377. 'content' => theme('privatemsg_new_block', array('count' => $count)),
  1378. );
  1379. return $block;
  1380. }
  1381. return array();
  1382. }
  1383. function _privatemsg_block_menu() {
  1384. $block = array();
  1385. $links = array();
  1386. if (privatemsg_user_access('write privatemsg')) {
  1387. $links[] = l(t('Write new message'), 'messages/new');
  1388. }
  1389. if (privatemsg_user_access('read privatemsg') || privatemsg_user_access('read all private messages') ) {
  1390. $links[] = l(privatemsg_title_callback(), 'messages');
  1391. }
  1392. if (count($links)) {
  1393. $block = array(
  1394. 'subject' => t('Private messages'),
  1395. 'content' => theme('item_list', array('items' => $links)),
  1396. );
  1397. }
  1398. return $block;
  1399. }
  1400. /**
  1401. * Delete or restore a message.
  1402. *
  1403. * @param $pmid
  1404. * Message id, pm.mid field.
  1405. * @param $delete
  1406. * Either deletes or restores the thread (1 => delete, 0 => restore)
  1407. * @param $account
  1408. * User account for which the delete action should be carried out.
  1409. * Set to NULL to delete for all users.
  1410. *
  1411. * @ingroup api
  1412. */
  1413. function privatemsg_message_change_delete($pmid, $delete, $account = NULL) {
  1414. $delete_value = 0;
  1415. if ($delete == TRUE) {
  1416. $delete_value = REQUEST_TIME;
  1417. }
  1418. $update = db_update('pm_index')
  1419. ->fields(array('deleted' => $delete_value))
  1420. ->condition('mid', $pmid);
  1421. if ($account) {
  1422. $update
  1423. ->condition('recipient', $account->uid)
  1424. ->condition('type', array('user', 'hidden'));
  1425. }
  1426. $update->execute();
  1427. }
  1428. /**
  1429. * Send a new message.
  1430. *
  1431. * This functions does send a message in a new thread.
  1432. * Example:
  1433. * @code
  1434. * privatemsg_new_thread(array(user_load(5)), 'The subject', 'The body text');
  1435. * @endcode
  1436. *
  1437. * @param $recipients
  1438. * Array of recipients (user objects)
  1439. * @param $subject
  1440. * The subject of the new message
  1441. * @param $body
  1442. * The body text of the new message
  1443. * @param $options
  1444. * Additional options, possible keys:
  1445. * author => User object of the author
  1446. * timestamp => Time when the message was sent
  1447. *
  1448. * @return
  1449. * An array with a key success. If TRUE, it also contains a key 'message' with
  1450. * the created $message array, the same that is passed to the insert hook.
  1451. * If FALSE, it contains a key 'messages'. This key contains an array where
  1452. * the key is the error type (error, warning, notice) and an array with
  1453. * messages of that type.
  1454. *
  1455. * It is theoretically possible for success to be TRUE and message to be FALSE.
  1456. * For example if one of the privatemsg database tables become corrupted. When testing
  1457. * for success of message being sent it is always best to see if ['message'] is not FALSE
  1458. * as well as ['success'] is TRUE.
  1459. *
  1460. * Example:
  1461. * @code
  1462. * array('error' => array('A error message'))
  1463. * @endcode
  1464. *
  1465. * @ingroup api
  1466. */
  1467. function privatemsg_new_thread($recipients, $subject, $body = NULL, $options = array()) {
  1468. global $user;
  1469. $author = clone $user;
  1470. $message = (object)$options;
  1471. $message->subject = $subject;
  1472. $message->body = $body;
  1473. // Make sure that recipients are keyed correctly and are not added
  1474. // multiple times.
  1475. foreach ($recipients as $recipient) {
  1476. if (!isset($recipient->type)) {
  1477. $recipient->type = 'user';
  1478. $recipient->recipient = $recipient->uid;
  1479. }
  1480. $message->recipients[privatemsg_recipient_key($recipient)] = $recipient;
  1481. }
  1482. // Apply defaults - this will not overwrite existing keys.
  1483. if (!isset($message->author)) {
  1484. $message->author = $author;
  1485. }
  1486. if (!isset($message->timestamp)) {
  1487. $message->timestamp = REQUEST_TIME;
  1488. }
  1489. if (!isset($message->format)) {
  1490. $message->format = filter_default_format($message->author);
  1491. }
  1492. $validated = _privatemsg_validate_message($message);
  1493. if ($validated['success']) {
  1494. $validated['message'] = _privatemsg_send($message);
  1495. if ($validated['message'] !== FALSE) {
  1496. _privatemsg_handle_recipients($validated['message']->mid, $validated['message']->recipients, FALSE);
  1497. }
  1498. }
  1499. return $validated;
  1500. }
  1501. /**
  1502. * Send a reply message
  1503. *
  1504. * This functions replies on an existing thread.
  1505. *
  1506. * @param $thread_id
  1507. * Thread id
  1508. * @param $body
  1509. * The body text of the new message
  1510. * @param $options
  1511. * Additional options, possible keys:
  1512. * author => User object of the author
  1513. * timestamp => Time when the message was sent
  1514. *
  1515. * @return
  1516. * An array with a key success and messages. This key contains an array where
  1517. * the key is the error type (error, warning, notice) and an array with
  1518. * messages of that type.. If success is TRUE, it also contains a key $message
  1519. * with the created $message array, the same that is passed to
  1520. * hook_privatemsg_message_insert().
  1521. *
  1522. * It is theoretically possible for success to be TRUE and message to be FALSE.
  1523. * For example if one of the privatemsg database tables become corrupted. When testing
  1524. * for success of message being sent it is always best to see if ['message'] is not FALSE
  1525. * as well as ['success'] is TRUE.
  1526. *
  1527. * Example messages values:
  1528. * @code
  1529. * array('error' => array('A error message'))
  1530. * @endcode
  1531. *
  1532. * @ingroup api
  1533. */
  1534. function privatemsg_reply($thread_id, $body, $options = array()) {
  1535. global $user;
  1536. $author = clone $user;
  1537. $message = (object)$options;
  1538. $message->body = $body;
  1539. // Apply defaults - this will not overwrite existing keys.
  1540. if (!isset($message->author)) {
  1541. $message->author = $author;
  1542. }
  1543. if (!isset($message->timestamp)) {
  1544. $message->timestamp = REQUEST_TIME;
  1545. }
  1546. if (!isset($message->format)) {
  1547. $message->format = filter_default_format($message->author);
  1548. }
  1549. // We don't know the subject and the recipients, so we need to load them..
  1550. // thread_id == mid on the first message of the thread
  1551. $first_message = privatemsg_message_load($thread_id, $message->author);
  1552. if (!$first_message) {
  1553. return array(
  1554. 'success' => FALSE,
  1555. 'messages' => array('error' => array(t('Thread %thread_id not found, unable to answer', array('%thread_id' => $thread_id)))),
  1556. );
  1557. }
  1558. $message->thread_id = $thread_id;
  1559. // Load participants
  1560. $message->recipients = _privatemsg_load_thread_participants($thread_id, $message->author);
  1561. $message->subject = $first_message->subject;
  1562. $validated = _privatemsg_validate_message($message);
  1563. if ($validated['success']) {
  1564. $validated['message'] = _privatemsg_send($message);
  1565. if ($validated['message'] !== FALSE) {
  1566. _privatemsg_handle_recipients($validated['message']->mid, $validated['message']->recipients, FALSE);
  1567. }
  1568. }
  1569. return $validated;
  1570. }
  1571. function _privatemsg_validate_message(&$message, $form = FALSE) {
  1572. $messages = array('error' => array(), 'warning' => array());
  1573. if (!(privatemsg_user_access('write privatemsg', $message->author) || (privatemsg_user_access('reply only privatemsg', $message->author) && isset($message->thread_id)))) {
  1574. // no need to do further checks in this case...
  1575. if ($form) {
  1576. form_set_error('author', t('You are not allowed to write messages.'));
  1577. return array(
  1578. 'success' => FALSE,
  1579. 'messages' => $messages,
  1580. );
  1581. }
  1582. else {
  1583. $messages['error'][] = t('@user is not allowed to write messages.', array('@user' => privatemsg_recipient_format($message->author, array('plain' => TRUE))));
  1584. return array(
  1585. 'success' => FALSE,
  1586. 'messages' => $messages,
  1587. );
  1588. }
  1589. }
  1590. // Prevent subjects which only consist of a space as these can not be clicked.
  1591. $message->subject = trim($message->subject);
  1592. if (empty($message->subject)) {
  1593. if ($form) {
  1594. form_set_error('subject', t('You must include a subject line with your message.'));
  1595. }
  1596. else {
  1597. $messages['error'][] = t('A subject must be included with the message.');
  1598. }
  1599. }
  1600. // Don't allow replies without a body.
  1601. if (!empty($message->thread_id) && ($message->body === NULL || $message->body === '') ) {
  1602. if ($form) {
  1603. form_set_error('body', t('You must include a message in your reply.'));
  1604. }
  1605. else {
  1606. $messages['error'][] = t('A message must be included in your reply.');
  1607. }
  1608. }
  1609. // Check if an allowed format is used.
  1610. if (!filter_access(filter_format_load($message->format), $message->author)) {
  1611. if ($form) {
  1612. form_set_error('format', t('You are not allowed to use the specified format.'));
  1613. }
  1614. else {
  1615. $messages['error'][] = t('@user is not allowed to use the specified input format.', array('@user' => privatemsg_recipient_format($message->author, array('plain' => TRUE))));
  1616. }
  1617. }
  1618. if (empty($message->recipients) || !is_array($message->recipients)) {
  1619. if ($form) {
  1620. form_set_error('recipient', t('You must include at least one valid recipient.'));
  1621. }
  1622. else {
  1623. $messages['error'][] = t('At least one valid recipient must be included with the message.');
  1624. }
  1625. }
  1626. if (!empty($message->recipients) && is_array($message->recipients)) {
  1627. foreach (module_invoke_all('privatemsg_block_message', $message->author, $message->recipients, (array)$message) as $blocked) {
  1628. unset($message->recipients[$blocked['recipient']]);
  1629. if ($form) {
  1630. drupal_set_message($blocked['message'], 'warning');
  1631. }
  1632. else {
  1633. $messages['warning'][] = $blocked['message'];
  1634. }
  1635. }
  1636. }
  1637. // Check again, give another error message if all recipients are blocked
  1638. if (empty($message->recipients)) {
  1639. if ($form) {
  1640. form_set_error('recipient', t('You are not allowed to send this message because all recipients are blocked.'));
  1641. }
  1642. else {
  1643. $messages['error'][] = t('The message cannot be sent because all recipients are blocked.');
  1644. }
  1645. }
  1646. // Verify if message has tokens and user is allowed to use them.
  1647. $message->has_tokens = privatemsg_user_access('use tokens in privatemsg', $message->author) && count(token_scan($message->subject . $message->body));
  1648. $messages = array_merge_recursive(module_invoke_all('privatemsg_message_validate', $message, $form), $messages);
  1649. // Check if there are errors in $messages or if $form is TRUE, there are form errors.
  1650. $success = empty($messages['error']) || ($form && count((array)form_get_errors()) > 0);
  1651. return array(
  1652. 'success' => $success,
  1653. 'messages' => $messages,
  1654. );
  1655. }
  1656. /**
  1657. * Internal function to save a message.
  1658. *
  1659. * @param $message
  1660. * A $message array with the data that should be saved. If a thread_id exists
  1661. * it will be created as a reply to an existing thread. If not, a new thread
  1662. * will be created.
  1663. *
  1664. * @return
  1665. * The updated $message array.
  1666. */
  1667. function _privatemsg_send($message) {
  1668. $transaction = db_transaction();
  1669. try {
  1670. drupal_alter('privatemsg_message_presave', $message);
  1671. field_attach_presave('privatemsg_message', $message);
  1672. $query = db_insert('pm_index')->fields(array('mid', 'thread_id', 'recipient', 'type', 'is_new', 'deleted'));
  1673. if (isset($message->read_all) && $message->read_all && isset($message->thread_id)) {
  1674. // The message was sent in read all mode, add the author as recipient to all
  1675. // existing messages.
  1676. $query_messages = _privatemsg_assemble_query('messages', array($message->thread_id), NULL);
  1677. foreach ($query_messages->execute()->fetchCol() as $mid) {
  1678. $query->values(array(
  1679. 'mid' => $mid,
  1680. 'thread_id' => $message->thread_id,
  1681. 'recipient' => $message->author->uid,
  1682. 'type' => 'user',
  1683. 'is_new' => 0,
  1684. 'deleted' => 0,
  1685. ));
  1686. }
  1687. }
  1688. // 1) Save the message body first.
  1689. $args = array();
  1690. $args['subject'] = $message->subject;
  1691. $args['author'] = $message->author->uid;
  1692. $args['body'] = $message->body;
  1693. $args['format'] = $message->format;
  1694. $args['timestamp'] = $message->timestamp;
  1695. $args['has_tokens'] = (int)$message->has_tokens;
  1696. $mid = db_insert('pm_message')
  1697. ->fields($args)
  1698. ->execute();
  1699. $message->mid = $mid;
  1700. // Thread ID is the same as the mid if it's the first message in the thread.
  1701. if (!isset($message->thread_id)) {
  1702. $message->thread_id = $mid;
  1703. }
  1704. // 2) Save message to recipients.
  1705. // Each recipient gets a record in the pm_index table.
  1706. foreach ($message->recipients as $recipient) {
  1707. $query->values(array(
  1708. 'mid' => $mid,
  1709. 'thread_id' => $message->thread_id,
  1710. 'recipient' => $recipient->recipient,
  1711. 'type' => $recipient->type,
  1712. 'is_new' => 1,
  1713. 'deleted' => 0,
  1714. ));
  1715. }
  1716. // We only want to add the author to the pm_index table, if the message has
  1717. // not been sent directly to him.
  1718. if (!isset($message->recipients['user_' . $message->author->uid])) {
  1719. $query->values(array(
  1720. 'mid' => $mid,
  1721. 'thread_id' => $message->thread_id,
  1722. 'recipient' => $message->author->uid,
  1723. 'type' => 'user',
  1724. 'is_new' => 0,
  1725. 'deleted' => 0,
  1726. ));
  1727. }
  1728. $query->execute();
  1729. module_invoke_all('privatemsg_message_insert', $message);
  1730. field_attach_insert('privatemsg_message', $message);
  1731. } catch (Exception $exception) {
  1732. $transaction->rollback();
  1733. watchdog_exception('privatemsg', $exception);
  1734. throw $exception;
  1735. }
  1736. // If we reached here that means we were successful at writing all messages to db.
  1737. return $message;
  1738. }
  1739. /**
  1740. * Returns a link to send message form for a specific users.
  1741. *
  1742. * Contains permission checks of author/recipient, blocking and
  1743. * if a anonymous user is involved.
  1744. *
  1745. * @param $recipient
  1746. * Recipient of the message
  1747. * @param $account
  1748. * Sender of the message, defaults to the current user
  1749. *
  1750. * @return
  1751. * Either FALSE or a URL string
  1752. *
  1753. * @ingroup api
  1754. */
  1755. function privatemsg_get_link($recipients, $account = array(), $subject = NULL) {
  1756. if ($account == NULL) {
  1757. global $user;
  1758. $account = $user;
  1759. }
  1760. if (!is_array($recipients)) {
  1761. $recipients = array($recipients);
  1762. }
  1763. if (!privatemsg_user_access('write privatemsg', $account) || $account->uid == 0) {
  1764. return FALSE;
  1765. }
  1766. $validated = array();
  1767. foreach ($recipients as $recipient) {
  1768. if (!privatemsg_user_access('read privatemsg', $recipient)) {
  1769. continue;
  1770. }
  1771. if (variable_get('privatemsg_display_link_self', TRUE) == FALSE && $account->uid == $recipient->uid) {
  1772. continue;
  1773. }
  1774. if (count(module_invoke_all('privatemsg_block_message', $account, array(privatemsg_recipient_key($recipient) => $recipient))) > 0) {
  1775. continue;
  1776. }
  1777. $validated[] = $recipient->uid;
  1778. }
  1779. if (empty($validated)) {
  1780. return FALSE;
  1781. }
  1782. $url = 'messages/new/' . implode(',', $validated);
  1783. if (!is_null($subject)) {
  1784. // Explicitly encode the / so that it will be encoded twice to work around
  1785. // the the menu_system.
  1786. $url .= '/' . str_replace('/', '%2F', $subject);
  1787. }
  1788. return $url;
  1789. }
  1790. /**
  1791. * Load a single message.
  1792. *
  1793. * @param $pmid
  1794. * Message id, pm.mid field
  1795. * @param $account
  1796. * For which account the message should be loaded.
  1797. * Defaults to the current user.
  1798. *
  1799. * @ingroup api
  1800. */
  1801. function privatemsg_message_load($pmid, $account = NULL) {
  1802. // If $pmid is object or array - do nothing
  1803. // (fixing conflict with message_load() function in message module).
  1804. if(is_array($pmid) || is_object($pmid)) {
  1805. return NULL;
  1806. }
  1807. $conditions = array();
  1808. if ($account) {
  1809. $conditions['account'] = $account;
  1810. }
  1811. $messages = privatemsg_message_load_multiple(array($pmid), $conditions);
  1812. return current($messages);
  1813. }
  1814. /**
  1815. * Load multiple messages.
  1816. *
  1817. * @param $pmids
  1818. * Array of Message ids, pm.mid field
  1819. * @param $account
  1820. * For which account the message should be loaded.
  1821. * Defaults to the current user.
  1822. *
  1823. * @ingroup api
  1824. */
  1825. function privatemsg_message_load_multiple(array $pmids, array $conditions = array(), $reset = FALSE) {
  1826. $result = entity_load('privatemsg_message', $pmids, $conditions, $reset);
  1827. return $result;
  1828. }
  1829. /**
  1830. * Generates a query based on a query id.
  1831. *
  1832. * @param $query
  1833. * Either be a string ('some_id') or an array('group_name', 'query_id'),
  1834. * if a string is supplied, group_name defaults to 'privatemsg'.
  1835. *
  1836. * @return SelectQuery
  1837. * Array with the keys query and count. count can be used to count the
  1838. * elements which would be returned by query. count can be used together
  1839. * with pager_query().
  1840. *
  1841. * @ingroup sql
  1842. */
  1843. function _privatemsg_assemble_query($query) {
  1844. // Modules will be allowed to choose the prefix for the query builder,
  1845. // but if there is not one supplied, 'privatemsg' will be taken by default.
  1846. if (is_array($query)) {
  1847. $query_id = $query[0];
  1848. $query_group = $query[1];
  1849. }
  1850. else {
  1851. $query_id = $query;
  1852. $query_group = 'privatemsg';
  1853. }
  1854. /**
  1855. * Begin: dynamic arguments
  1856. */
  1857. $args = func_get_args();
  1858. unset($args[0]);
  1859. // We do the merge because we call call_user_func_array and not drupal_alter.
  1860. // This is necessary because otherwise we would not be able to use $args
  1861. // correctly (otherwise it doesn't unfold).
  1862. $query_function = $query_group . '_sql_' . $query_id;
  1863. if (!function_exists($query_function)) {
  1864. drupal_set_message(t('Query function %function does not exist', array('%function' => $query_function)), 'error');
  1865. return FALSE;
  1866. }
  1867. $query = call_user_func_array($query_function, $args);
  1868. // Add a tag to make it alterable.
  1869. $query->addTag($query_group . '_' . $query_id);
  1870. // Add arguments as metadata.
  1871. foreach ($args as $id => $arg) {
  1872. $query->addMetaData('arg_' . $id, $arg);
  1873. }
  1874. return $query;
  1875. }
  1876. /**
  1877. * Marks one or multiple threads as (un)read.
  1878. *
  1879. * @param $threads
  1880. * Array with thread id's or a single thread id.
  1881. * @param $status
  1882. * Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD, sets the new status.
  1883. * @param $account
  1884. * User object for which the threads should be deleted, defaults to the current user.
  1885. */
  1886. function privatemsg_thread_change_status($threads, $status, $account = NULL) {
  1887. if (!is_array($threads)) {
  1888. $threads = array($threads);
  1889. }
  1890. if (empty($account)) {
  1891. global $user;
  1892. $account = clone $user;
  1893. }
  1894. // Merge status and uid with the existing thread list.
  1895. $params = array(
  1896. ':status' => $status,
  1897. ':recipient' => $account->uid,
  1898. ':threads' => $threads,
  1899. );
  1900. // Record which messages will change status.
  1901. $result = db_query("SELECT mid FROM {pm_index} WHERE is_new <> :status AND recipient = :recipient and type IN ('user', 'hidden') AND thread_id IN (:threads)", $params);
  1902. $changed = $result->fetchCol();
  1903. // Update the status of the threads.
  1904. db_update('pm_index')
  1905. ->fields(array('is_new' => $status))
  1906. ->condition('thread_id', $threads)
  1907. ->condition('recipient', $account->uid)
  1908. ->condition('type', array('user', 'hidden'))
  1909. ->execute();
  1910. // Allow modules to respond to the status changes.
  1911. foreach ($changed as $mid) {
  1912. module_invoke_all('privatemsg_message_status_changed', $mid, $status, $account);
  1913. }
  1914. if ($status == PRIVATEMSG_UNREAD) {
  1915. drupal_set_message(format_plural(count($threads), 'Marked 1 thread as unread.', 'Marked @count threads as unread.'));
  1916. }
  1917. else {
  1918. drupal_set_message(format_plural(count($threads), 'Marked 1 thread as read.', 'Marked @count threads as read.'));
  1919. }
  1920. }
  1921. /**
  1922. * Returns a table header definition based on the submitted keys.
  1923. *
  1924. * Uses @link theming theme patterns @endlink to theme single headers.
  1925. *
  1926. * @param $has_posts
  1927. * TRUE when there is at least one row. Decides if the select all checkbox should be displayed.
  1928. * @param $keys
  1929. * Array with the keys which are present in the query/should be displayed.
  1930. * @return
  1931. * Array with header definitions for tablesort_sql and theme('table').
  1932. */
  1933. function _privatemsg_list_headers($keys) {
  1934. // theme() doesn't include the theme file for patterns, we need to do it manually.
  1935. include_once drupal_get_path('module', 'privatemsg') . '/privatemsg.theme.inc';
  1936. $header = array();
  1937. foreach ($keys as $key) {
  1938. // First, try to load a specific theme for that header, if not present, use the default.
  1939. if ($return = theme('privatemsg_list_header__' . $key)) {
  1940. // The default theme returns nothing, only store the value if we have something.
  1941. $header[$key] = $return;
  1942. }
  1943. }
  1944. uasort($header, 'element_sort');
  1945. // Remove weight column or it will show up in the markup.
  1946. foreach ($header as $key => $element) {
  1947. if (isset($header[$key]['#weight'])) {
  1948. unset($header[$key]['#weight']);
  1949. }
  1950. }
  1951. return $header;
  1952. }
  1953. /**
  1954. * Formats all rows (#options) in the privatemsg tableselect thread list.
  1955. *
  1956. * Uses @link theming theme patterns @endlink to theme single fields.
  1957. *
  1958. * @param $thread
  1959. * Array with the row data returned by the database.
  1960. * @return
  1961. * Row definition for use with theme('table')
  1962. */
  1963. function _privatemsg_list_thread($tableselect) {
  1964. foreach ($tableselect['#options'] as $id => $thread) {
  1965. $row = array();
  1966. if (!empty($thread['is_new'])) {
  1967. // Set the css class in the tr tag.
  1968. $row['#attributes']['class'][] = 'privatemsg-unread';
  1969. }
  1970. foreach ($thread as $key => $data) {
  1971. // First, try to load a specific theme for that field, if not present, use the default.
  1972. if ($return = theme('privatemsg_list_field__' . $key, array('thread' => $thread))) {
  1973. // The default theme returns nothing, only store the value if we have something.
  1974. $row[$key] = $return;
  1975. }
  1976. }
  1977. $tableselect['#options'][$id] = $row;
  1978. }
  1979. return $tableselect;
  1980. }
  1981. /**
  1982. * Execute an operation on a number of threads.
  1983. *
  1984. * @param $operation
  1985. * The operation that should be executed.
  1986. * @see hook_privatemsg_thread_operations()
  1987. * @param $threads
  1988. * An array of thread ids. The array is filtered before used, a checkboxes
  1989. * array can be directly passed to it.
  1990. */
  1991. function privatemsg_operation_execute($operation, $threads, $account = NULL) {
  1992. // Filter out unchecked threads, this gives us an array of "checked" threads.
  1993. $threads = array_filter($threads);
  1994. if (empty($threads)) {
  1995. // Do not execute anything if there are no checked threads.
  1996. drupal_set_message(t('You must first select one (or more) messages before you can take that action.'), 'warning');
  1997. return FALSE;
  1998. }
  1999. // Add in callback arguments if present.
  2000. if (isset($operation['callback arguments'])) {
  2001. $args = array_merge(array($threads), $operation['callback arguments']);
  2002. }
  2003. else {
  2004. $args = array($threads);
  2005. }
  2006. // Add the user object to the arguments.
  2007. if ($account) {
  2008. $args[] = $account;
  2009. }
  2010. // Execute the chosen action and pass the defined arguments.
  2011. call_user_func_array($operation['callback'], $args);
  2012. if (!empty($operation['success message'])) {
  2013. drupal_set_message($operation['success message']);
  2014. }
  2015. // Check if that operation has defined an undo callback.
  2016. if (isset($operation['undo callback']) && $undo_function = $operation['undo callback']) {
  2017. // Add in callback arguments if present.
  2018. if (isset($operation['undo callback arguments'])) {
  2019. $undo_args = array_merge(array($threads), $operation['undo callback arguments']);
  2020. }
  2021. else {
  2022. $undo_args = array($threads);
  2023. }
  2024. // Avoid saving the complete user object in the session.
  2025. if ($account) {
  2026. $undo_args['account'] = $account->uid;
  2027. }
  2028. // Store the undo callback in the session and display a "Undo" link.
  2029. // @todo: Provide a more flexible solution for such an undo action, operation defined string for example.
  2030. $_SESSION['privatemsg']['undo callback'] = array('function' => $undo_function, 'args' => $undo_args);
  2031. $undo = url('messages/undo/action', array('query' => drupal_get_destination()));
  2032. drupal_set_message(t('The previous action can be <a href="!undo">undone</a>.', array('!undo' => $undo)));
  2033. }
  2034. // Allow modules to respond to the operation.
  2035. module_invoke_all('privatemsg_operation_executed', $operation, $threads, $account);
  2036. return TRUE;
  2037. }
  2038. /**
  2039. * Delete or restore one or multiple threads.
  2040. *
  2041. * @param $threads
  2042. * Array with thread id's or a single thread id.
  2043. * @param $delete
  2044. * Indicates if the threads should be deleted or restored. 1 => delete, 0 => restore.
  2045. * @param $account
  2046. * User account for which the delete action should be carried out - Set to NULL to delete for all users.
  2047. */
  2048. function privatemsg_thread_change_delete($threads, $delete, $account = NULL) {
  2049. if (!is_array($threads)) {
  2050. $threads = array($threads);
  2051. }
  2052. if (empty($account)) {
  2053. global $user;
  2054. $account = clone $user;
  2055. }
  2056. // Load all messages of those threads including the deleted.
  2057. $query = _privatemsg_assemble_query('messages', $threads, $account, TRUE);
  2058. // Delete each message. We need to do that to trigger the delete hook.
  2059. foreach ($query->execute() as $row) {
  2060. privatemsg_message_change_delete($row->mid, $delete, $account);
  2061. }
  2062. if ($delete) {
  2063. drupal_set_message(format_plural(count($threads), 'Deleted 1 thread.', 'Deleted @count threads.'));
  2064. }
  2065. else {
  2066. drupal_set_message(format_plural(count($threads), 'Restored 1 thread.', 'Restored @count threads.'));
  2067. }
  2068. }
  2069. /**
  2070. * Implements hook_privatemsg_thread_operations().
  2071. */
  2072. function privatemsg_privatemsg_thread_operations() {
  2073. $operations = array(
  2074. 'mark as read' => array(
  2075. 'label' => t('Mark as read'),
  2076. 'callback' => 'privatemsg_thread_change_status',
  2077. 'callback arguments' => array('status' => PRIVATEMSG_READ),
  2078. 'undo callback' => 'privatemsg_thread_change_status',
  2079. 'undo callback arguments' => array('status' => PRIVATEMSG_UNREAD),
  2080. ),
  2081. 'mark as unread' => array(
  2082. 'label' => t('Mark as unread'),
  2083. 'callback' => 'privatemsg_thread_change_status',
  2084. 'callback arguments' => array('status' => PRIVATEMSG_UNREAD),
  2085. 'undo callback' => 'privatemsg_thread_change_status',
  2086. 'undo callback arguments' => array('status' => PRIVATEMSG_READ),
  2087. ),
  2088. );
  2089. if (privatemsg_user_access('delete privatemsg')) {
  2090. $operations['delete'] = array(
  2091. 'label' => t('Delete'),
  2092. 'callback' => 'privatemsg_thread_change_delete',
  2093. 'callback arguments' => array('delete' => 1),
  2094. 'undo callback' => 'privatemsg_thread_change_delete',
  2095. 'undo callback arguments' => array('delete' => 0),
  2096. 'button' => TRUE,
  2097. );
  2098. }
  2099. return $operations;
  2100. }
  2101. /**
  2102. * Implements hook_entity_info().
  2103. */
  2104. function privatemsg_entity_info() {
  2105. return array(
  2106. 'privatemsg_message' => array(
  2107. 'label' => t('Privatemsg'),
  2108. 'base table' => 'pm_message',
  2109. 'fieldable' => TRUE,
  2110. 'controller class' => 'PrivatemsgMessageController',
  2111. 'uri callback' => 'privatemsg_message_uri_callback',
  2112. 'entity keys' => array(
  2113. 'id' => 'mid',
  2114. ),
  2115. 'bundles' => array(
  2116. 'privatemsg_message' => array(
  2117. 'label' => t('Private message'),
  2118. 'admin' => array(
  2119. 'path' => 'admin/config/messaging/privatemsg',
  2120. 'access arguments' => array('administer privatemsg settings'),
  2121. ),
  2122. ),
  2123. ),
  2124. ),
  2125. );
  2126. }
  2127. /**
  2128. * Returns the URI for a private message.
  2129. *
  2130. * @param $message
  2131. * Private message object.
  2132. *
  2133. * @return
  2134. * URI array as defined by hook_entity_info().
  2135. */
  2136. function privatemsg_message_uri_callback($message) {
  2137. $uri = array();
  2138. if (isset($message->mid) && isset($message->thread_id)) {
  2139. $uri = array(
  2140. 'path' => 'messages/view/' . $message->thread_id,
  2141. 'options' => array(),
  2142. );
  2143. // Add message fragment, if necessary.
  2144. if ($message->mid != $message->thread_id) {
  2145. $uri['options']['fragment'] = 'privatemsg-mid-' . $message->mid;
  2146. }
  2147. }
  2148. return $uri;
  2149. }
  2150. /**
  2151. * Implements hook_build_modes().
  2152. */
  2153. function privatemsg_build_modes($obj_type) {
  2154. $modes = array();
  2155. if ($obj_type == 'privatemsg_message') {
  2156. $modes['message'] = t('Message');
  2157. }
  2158. return $modes;
  2159. }
  2160. /**
  2161. * Implements hook_node_view().
  2162. */
  2163. function privatemsg_node_view($node, $view_mode) {
  2164. $types = array_filter(variable_get('privatemsg_link_node_types', array()));
  2165. if (in_array($node->type, $types) && ($view_mode == 'full' || (variable_get('privatemsg_display_on_teaser', 1) && $view_mode == 'teaser'))) {
  2166. $url = privatemsg_get_link(user_load($node->uid));
  2167. if (!empty($url)){
  2168. $node->content['links']['#links']['privatemsg_link'] = array(
  2169. 'title' => t('Send author a message'),
  2170. 'href' => $url . '/' . t('Message regarding @node', array('@node' => $node->title)),
  2171. 'query' => drupal_get_destination(),
  2172. 'attributes' => array('class' => 'privatemsg-send-link privatemsg-send-link-node'),
  2173. );
  2174. }
  2175. }
  2176. }
  2177. /**
  2178. * Implements hook_comment_view().
  2179. */
  2180. function privatemsg_comment_view($comment) {
  2181. $types = array_filter(variable_get('privatemsg_link_node_types', array()));
  2182. if (in_array(node_load($comment->nid)->type, $types) && variable_get('privatemsg_display_on_comments', 0)) {
  2183. $url = privatemsg_get_link(user_load($comment->uid));
  2184. if (!empty($url)){
  2185. $links['privatemsg_link'] = array(
  2186. 'title' => t('Send author a message'),
  2187. 'href' => $url . '/' . t('Message regarding @comment', array('@comment' => trim($comment->subject))),
  2188. 'query' => drupal_get_destination(),
  2189. );
  2190. $comment->content['links']['privatemsg'] = array(
  2191. '#theme' => 'links',
  2192. '#links' => $links,
  2193. '#attributes' => array('class' => array('privatemsg-send-link', 'privatemsg-send-link-node', 'links', 'inline')),
  2194. );
  2195. }
  2196. }
  2197. }
  2198. /**
  2199. * Implements hook_views_api().
  2200. */
  2201. function privatemsg_views_api() {
  2202. return array(
  2203. 'api' => 2,
  2204. 'path' => drupal_get_path('module', 'privatemsg') . '/views',
  2205. );
  2206. }
  2207. /**
  2208. * Privatemsg wrapper function for user_load_multiple().
  2209. *
  2210. * The function adds the privatemsg specific recipient id (uid)
  2211. * and recipient type to the user object.
  2212. *
  2213. * @param $uid
  2214. * Which uid to load. Can either be a single id or an array of uids.
  2215. * @return
  2216. * If existing for the passed in uid, the user object with the recipient
  2217. * and type properties.
  2218. */
  2219. function privatemsg_user_load_multiple($uids) {
  2220. $accounts = array();
  2221. foreach (user_load_multiple($uids) as $account) {
  2222. $account->recipient = $account->uid;
  2223. $account->type = 'user';
  2224. $accounts[privatemsg_recipient_key($account)] = $account;
  2225. }
  2226. return $accounts;
  2227. }
  2228. /**
  2229. * Return key for a recipient object used for arrays.
  2230. * @param $recipient
  2231. * Recipient object, must have type and recipient properties.
  2232. * @return
  2233. * A string that looks like type_id.
  2234. *
  2235. * @ingroup types
  2236. */
  2237. function privatemsg_recipient_key($recipient) {
  2238. if (empty($recipient->type)) {
  2239. return 'user_' . $recipient->uid;
  2240. }
  2241. return $recipient->type . '_' . $recipient->recipient;
  2242. }
  2243. /**
  2244. * Returns an array of defined recipient types.
  2245. *
  2246. * @return
  2247. * Array of recipient types
  2248. * @see hook_privatemsg_recipient_type_info()
  2249. *
  2250. * @ingroup types
  2251. */
  2252. function privatemsg_recipient_get_types() {
  2253. $types = &drupal_static(__FUNCTION__, NULL);
  2254. if ($types === NULL) {
  2255. $types = module_invoke_all('privatemsg_recipient_type_info');
  2256. if (!is_array($types)) {
  2257. $types = array();
  2258. }
  2259. drupal_alter('privatemsg_recipient_type_info', $types);
  2260. uasort($types, 'element_sort');
  2261. }
  2262. return $types;
  2263. }
  2264. /**
  2265. * Return a single recipient type information.
  2266. * @param $type
  2267. * Name of the recipient type.
  2268. * @return
  2269. * Array with the recipient type definition. NULL if the type doesn't exist.
  2270. *
  2271. * @ingroup types
  2272. */
  2273. function privatemsg_recipient_get_type($type) {
  2274. $types = privatemsg_recipient_get_types();
  2275. if (!is_string($type)) {
  2276. exit;
  2277. }
  2278. if (isset($types[$type])) {
  2279. return $types[$type];
  2280. }
  2281. }
  2282. /**
  2283. * Add or remove a recipient to an existing message.
  2284. *
  2285. * @param $mid
  2286. * Message id for which the recipient should be added.
  2287. * @param $recipient
  2288. * Recipient id that should be added, for example uid.
  2289. * @param $type
  2290. * Type of the recipient, defaults to hidden.
  2291. * @param $add
  2292. * If TRUE, adds the recipient, if FALSE, removes it.
  2293. */
  2294. function privatemsg_message_change_recipient($mid, $uid, $type = 'user', $add = TRUE) {
  2295. // The message is statically cached, so only a single load is necessary.
  2296. $message = privatemsg_message_load($mid);
  2297. $thread_id = $message->thread_id;
  2298. if ($add) {
  2299. // Only add the recipient if he does not block the author.
  2300. $recipient = user_load($uid);
  2301. $context = ($thread_id == $mid) ? array() : array('thread_id' => $thread_id);
  2302. $user_blocked = module_invoke_all('privatemsg_block_message', $message->author, array(privatemsg_recipient_key($recipient) => $recipient), $context);
  2303. if (count($user_blocked) <> 0) {
  2304. return;
  2305. }
  2306. // Make sure to only add a recipient once. The types user and hidden are
  2307. // considered equal here.
  2308. $query = db_select('pm_index', 'pmi');
  2309. $query->addExpression('1');
  2310. $exists = $query
  2311. ->condition('mid', $mid)
  2312. ->condition('recipient', $uid)
  2313. ->condition('type', ($type == 'user' || $type == 'hidden') ? array('user', 'hidden') : $type)
  2314. ->execute()
  2315. ->fetchField();
  2316. if (!$exists) {
  2317. db_insert('pm_index')
  2318. ->fields(array(
  2319. 'mid' => $mid,
  2320. 'thread_id' => $thread_id,
  2321. 'recipient' => $uid,
  2322. 'type' => $type,
  2323. 'is_new' => 1,
  2324. 'deleted' => 0,
  2325. ))
  2326. ->execute();
  2327. }
  2328. }
  2329. else {
  2330. db_delete('pm_index')
  2331. >condition('mid', $mid)
  2332. ->condition('recipient', $uid)
  2333. ->condition('type', $type)
  2334. ->execute();
  2335. }
  2336. module_invoke_all('privatemsg_message_recipient_changed', $mid, $thread_id, $uid, $type, $add);
  2337. }
  2338. /**
  2339. * Handle the non-user recipients of a new message.
  2340. *
  2341. * Either process them directly if they have less than a certain amount of users
  2342. * or, if enabled, add them to a batch.
  2343. *
  2344. * @param $mid
  2345. * Message id for which the recipients are processed.
  2346. * @param $recipients
  2347. * Array of recipients.
  2348. * @param $use_batch
  2349. * Use batch API to process recipients.
  2350. */
  2351. function _privatemsg_handle_recipients($mid, $recipients, $use_batch = TRUE) {
  2352. $batch = array(
  2353. 'title' => t('Processing recipients'),
  2354. 'operations' => array(),
  2355. 'file' => drupal_get_path('module', 'privatemsg') . '/privatemsg.pages.inc',
  2356. 'progress_message' => t('Processing recipients'),
  2357. );
  2358. $small_threshold = variable_get('privatemsg_recipient_small_threshold', 100);
  2359. foreach ($recipients as $recipient) {
  2360. // Add a batch operation to press non-user recipient types.
  2361. if ($recipient->type != 'user' && $recipient->type != 'hidden') {
  2362. $type = privatemsg_recipient_get_type($recipient->type);
  2363. // Count the recipients, if there are less than small_threshold, process
  2364. // them right now.
  2365. $count_function = $type['count'];
  2366. if (!is_callable($count_function)) {
  2367. db_update('pm_index')
  2368. ->fields(array('is_new' => PRIVATEMSG_READ))
  2369. ->condition('mid', $mid)
  2370. ->condition('recipient', $recipient->recipient)
  2371. ->condition('type', $recipient->type)
  2372. ->execute();
  2373. drupal_set_message(t('Recipient type %type is not correctly implemented', array('%type' => $recipient->type)), 'error');
  2374. continue;
  2375. }
  2376. $count = $count_function($recipient);
  2377. if ($count < $small_threshold) {
  2378. $load_function = $type['generate recipients'];
  2379. if (!is_callable($load_function)) {
  2380. db_update('pm_index')
  2381. ->fields(array('is_new' => PRIVATEMSG_READ))
  2382. ->condition('mid', $mid)
  2383. ->condition('recipient', $recipient->recipient)
  2384. ->condition('type', $recipient->type)
  2385. ->execute();
  2386. drupal_set_message(t('Recipient type %type is not correctly implemented', array('%type' => $recipient->type)), 'error');
  2387. continue;
  2388. }
  2389. $uids = $load_function($recipient, $small_threshold, 0);
  2390. if (!empty($uids)) {
  2391. foreach ($uids as $uid) {
  2392. privatemsg_message_change_recipient($mid, $uid, 'hidden');
  2393. }
  2394. }
  2395. db_update('pm_index')
  2396. ->fields(array('is_new' => PRIVATEMSG_READ))
  2397. ->condition('mid', $mid)
  2398. ->condition('recipient', $recipient->recipient)
  2399. ->condition('type', $recipient->type)
  2400. ->execute();
  2401. continue;
  2402. }
  2403. if ($use_batch) {
  2404. $batch['operations'][] = array('privatemsg_load_recipients', array($mid, $recipient));
  2405. }
  2406. }
  2407. }
  2408. // Set batch if there are outstanding operations.
  2409. if ($use_batch && !empty($batch['operations'])) {
  2410. batch_set($batch);
  2411. }
  2412. }
  2413. /**
  2414. * This function is used to test if the current user has write/view access
  2415. * for a specific recipient type.
  2416. *
  2417. * @param $type_name
  2418. * The name of the recipient type.
  2419. * @param $permission
  2420. * Which permission should be checked: 'write' or 'view'.
  2421. * @param $recipient
  2422. * Optionally pass in a recipient for which the permission should be checked.
  2423. * This only has effect if a the recipient type defines a callback function
  2424. * and is simply passed through in that case.
  2425. *
  2426. * @return
  2427. * TRUE if the user has that permission (or not permission is defined) and
  2428. * FALSE if not.
  2429. *
  2430. * @ingroup types
  2431. */
  2432. function privatemsg_recipient_access($type_name, $permission, $recipient = NULL) {
  2433. if (($type = privatemsg_recipient_get_type($type_name))) {
  2434. // First check if a callback function is defined.
  2435. if (!empty($type[$permission . ' callback']) && is_callable($type[$permission . ' callback'])) {
  2436. $callback = $type[$permission . ' callback'];
  2437. return $callback($recipient);
  2438. }
  2439. if (isset($type[$permission . ' access'])) {
  2440. if (is_bool($type[$permission . ' access'])) {
  2441. return $type[$permission . ' access'];
  2442. }
  2443. return user_access($type[$permission . ' access']);
  2444. }
  2445. }
  2446. // If no access permission is defined, access is allowed.
  2447. return TRUE;
  2448. }
  2449. /**
  2450. * Format a single participant.
  2451. *
  2452. * @param $participant
  2453. * The participant object to format.
  2454. *
  2455. * @ingroup types.
  2456. */
  2457. function privatemsg_recipient_format($recipient, $options = array()) {
  2458. if (!isset($recipient->type)) {
  2459. $recipient->type = 'user';
  2460. $recipient->recipient = $recipient->uid;
  2461. }
  2462. $type = privatemsg_recipient_get_type($recipient->type);
  2463. if (isset($type['format'])) {
  2464. return theme($type['format'], array('recipient' => $recipient, 'options' => $options));
  2465. }
  2466. return NULL;
  2467. }
  2468. /**
  2469. * Implements hook_privatemsg_recipient_type_info().
  2470. */
  2471. function privatemsg_privatemsg_recipient_type_info() {
  2472. return array(
  2473. 'user' => array(
  2474. 'name' => t('User'),
  2475. 'description' => t('Enter a user name to write a message to a user.'),
  2476. 'load' => 'privatemsg_user_load_multiple',
  2477. 'format' => 'privatemsg_username',
  2478. 'autocomplete' => 'privatemsg_user_autocomplete',
  2479. // Make sure this comes always last.
  2480. '#weight' => 50,
  2481. ),
  2482. );
  2483. }
  2484. /**
  2485. * Implements callback_recipient_autocomplete().
  2486. */
  2487. function privatemsg_user_autocomplete($fragment, $names, $limit) {
  2488. // First, load all possible uids.
  2489. $uids = _privatemsg_assemble_query('autocomplete', $fragment, $names)
  2490. ->range(0, $limit)
  2491. ->execute()
  2492. ->fetchCol();
  2493. $query = _privatemsg_assemble_query('autocomplete', $fragment, $names);
  2494. $query->preExecute();
  2495. $query->getArguments();
  2496. // Load the corresponding users, make sure to not load any duplicates.
  2497. $accounts = user_load_multiple(array_unique($uids));
  2498. // Return them in an array with the correct recipient key.
  2499. $suggestions = array();
  2500. foreach ($accounts as $account) {
  2501. $account->type = 'user';
  2502. $account->recipient = $account->uid;
  2503. $suggestions[privatemsg_recipient_key($account)] = $account;
  2504. }
  2505. return $suggestions;
  2506. }
  2507. /**
  2508. * Implements hook_field_extra_fields().
  2509. */
  2510. function privatemsg_field_extra_fields() {
  2511. $extra['user']['user'] = array(
  2512. 'form' => array(
  2513. 'privatemsg' => array(
  2514. 'label' => 'Private msg',
  2515. 'description' => t('Private messages'),
  2516. 'weight' => 5,
  2517. ),
  2518. ),
  2519. 'display' => array(
  2520. 'privatemsg_send_new_message' => array(
  2521. 'label' => 'Private msg',
  2522. 'description' => t('Private messages'),
  2523. 'weight' => 5,
  2524. ),
  2525. ),
  2526. );
  2527. $extra['privatemsg_message']['privatemsg_message'] = array(
  2528. 'form' => array(
  2529. 'recipient' => array(
  2530. 'label' => t('To'),
  2531. 'description' => t('Recipient field'),
  2532. 'weight' => -10,
  2533. ),
  2534. 'subject' => array(
  2535. 'label' => t('Message subject'),
  2536. 'description' => t('Message subject'),
  2537. 'weight' => -5,
  2538. ),
  2539. 'body' => array(
  2540. 'label' => t('Message body'),
  2541. 'description' => t('Message body'),
  2542. 'weight' => -3,
  2543. ),
  2544. 'token' => array(
  2545. 'label' => t('Token browser'),
  2546. 'description' => t('Displays usable tokens in a table for those who are allowed to use tokens in private messages.'),
  2547. 'weight' => -1,
  2548. ),
  2549. ),
  2550. 'display' => array(
  2551. 'body' => array(
  2552. 'label' => t('Message body'),
  2553. 'description' => t('Message body'),
  2554. 'weight' => -4,
  2555. ),
  2556. )
  2557. );
  2558. return $extra;
  2559. }
  2560. /**
  2561. * Implements hook_file_download_access().
  2562. */
  2563. function privatemsg_file_download_access($field, $entity_type, $entity) {
  2564. global $user;
  2565. if ($entity_type == 'privatemsg_message') {
  2566. // Users with read all private messages permission can view all files too.
  2567. if (user_access('read all private messages')) {
  2568. return TRUE;
  2569. }
  2570. // Check if user is a recipient of this message.
  2571. return (bool)db_query_range("SELECT 1 FROM {pm_index} WHERE recipient = :uid AND type IN ('user', 'hidden') AND mid = :mid", 0, 1, array(':uid' => $user->uid, ':mid' => $entity->mid))->fetchField();
  2572. }
  2573. }
  2574. /**
  2575. * Implements hook_token_info().
  2576. */
  2577. function privatemsg_token_info() {
  2578. $type = array(
  2579. 'name' => t('Private message'),
  2580. 'description' => t('Tokens related to private messages.'),
  2581. 'needs-data' => 'privatemsg_message',
  2582. );
  2583. // Tokens for private messages.
  2584. $message['mid'] = array(
  2585. 'name' => t("Message ID"),
  2586. 'description' => t("The unique ID of the message."),
  2587. );
  2588. $message['thread-id'] = array(
  2589. 'name' => t("Thread ID"),
  2590. 'description' => t("The unique ID of the thread."),
  2591. );
  2592. $message['url'] = array(
  2593. 'name' => t("URL"),
  2594. 'description' => t("URL that points to the message."),
  2595. );
  2596. $message['subject'] = array(
  2597. 'name' => t("Subject"),
  2598. 'description' => t("The subject of the message."),
  2599. );
  2600. $message['body'] = array(
  2601. 'name' => t("Body"),
  2602. 'description' => t("The body of the message."),
  2603. );
  2604. // Chained tokens for nodes.
  2605. $message['timestamp'] = array(
  2606. 'name' => t("Date created"),
  2607. 'description' => t("The date the message was sent."),
  2608. 'type' => 'date',
  2609. );
  2610. $message['author'] = array(
  2611. 'name' => t("Author"),
  2612. 'description' => t("The author of the message."),
  2613. 'type' => 'user',
  2614. );
  2615. $message['recipient'] = array(
  2616. 'name' => t("Recipient"),
  2617. 'description' => t("The recipient of the message."),
  2618. 'type' => 'user',
  2619. );
  2620. return array(
  2621. 'types' => array('privatemsg_message' => $type),
  2622. 'tokens' => array('privatemsg_message' => $message),
  2623. );
  2624. }
  2625. /**
  2626. * Implements hook_tokens().
  2627. */
  2628. function privatemsg_tokens($type, $tokens, array $data = array(), array $options = array()) {
  2629. global $user;
  2630. $url_options = array('absolute' => TRUE);
  2631. if (isset($options['language'])) {
  2632. $url_options['language'] = $options['language'];
  2633. $language_code = $options['language']->language;
  2634. }
  2635. else {
  2636. $language_code = NULL;
  2637. }
  2638. $recipient = $user;
  2639. if (isset($data['privatemsg_recipient'])) {
  2640. $recipient = $data['privatemsg_recipient'];
  2641. }
  2642. $sanitize = !empty($options['sanitize']);
  2643. $replacements = array();
  2644. if ($type == 'privatemsg_message' && !empty($data['privatemsg_message'])) {
  2645. $message = $data['privatemsg_message'];
  2646. foreach ($tokens as $name => $original) {
  2647. switch ($name) {
  2648. case 'mid':
  2649. $replacements[$original] = $message->mid;
  2650. break;
  2651. case 'thread-id':
  2652. $replacements[$original] = $message->thread_id;
  2653. break;
  2654. case 'subject':
  2655. // Avoid recursion.
  2656. if (empty($options['privatemsg_recursion'])) {
  2657. $subject = privatemsg_token_replace($message->subject, $data, $options + array('privatemsg_recursion' => 1));
  2658. }
  2659. else {
  2660. $subject = $message->subject;
  2661. }
  2662. $replacements[$original] = $sanitize ? check_plain($subject) : $subject;
  2663. break;
  2664. case 'body':
  2665. // Avoid recursion.
  2666. if (empty($options['privatemsg_recursion'])) {
  2667. $replacements[$original] = privatemsg_token_replace($sanitize ? check_markup($message->body, $message->format) : $message->body, $data, $options + array('privatemsg_recursion' => 1));
  2668. }
  2669. else {
  2670. $replacements[$original] = $sanitize ? check_markup($message->body, $message->format) : $message->body;
  2671. }
  2672. break;
  2673. case 'url':
  2674. $uri = entity_uri('privatemsg_message', $message);
  2675. $replacements[$original] = url($uri['path'], $url_options + $uri['options']);
  2676. break;
  2677. // Default values for the chained tokens handled below.
  2678. case 'author':
  2679. $replacements[$original] = $sanitize ? filter_xss(privatemsg_recipient_format($message->author, array('plain' => TRUE))) : privatemsg_recipient_format($message->author, array('plain' => TRUE));
  2680. break;
  2681. case 'recipient':
  2682. $replacements[$original] = $sanitize ? filter_xss(privatemsg_recipient_format($recipient, array('plain' => TRUE))) : privatemsg_recipient_format($recipient, array('plain' => TRUE));
  2683. break;
  2684. case 'timestamp':
  2685. $replacements[$original] = format_date($message->timestamp, 'medium', '', NULL, $language_code);
  2686. break;
  2687. }
  2688. }
  2689. if ($author_tokens = token_find_with_prefix($tokens, 'author')) {
  2690. $replacements += token_generate('user', $author_tokens, array('user' => $message->author), $options);
  2691. }
  2692. if ($recipient_tokens = token_find_with_prefix($tokens, 'recipient')) {
  2693. $replacements += token_generate('user', $recipient_tokens, array('user' => $recipient), $options);
  2694. }
  2695. if ($sent_tokens = token_find_with_prefix($tokens, 'timestamp')) {
  2696. $replacements += token_generate('date', $sent_tokens, array('date' => $message->timestamp), $options);
  2697. }
  2698. }
  2699. return $replacements;
  2700. }
  2701. /**
  2702. * Wrapper function for token_replace() that does not replace the tokens if the
  2703. * user viewing the message is not a recipient.
  2704. */
  2705. function privatemsg_token_replace($text, $data, array $options = array()) {
  2706. global $user;
  2707. if (empty($data['privatemsg_recipient'])) {
  2708. $recipient = $user;
  2709. }
  2710. else {
  2711. $recipient = $data['privatemsg_recipient'];
  2712. }
  2713. if (isset($options['language'])) {
  2714. $url_options['language'] = $options['language'];
  2715. $language_code = $options['language']->language;
  2716. }
  2717. else {
  2718. $language_code = NULL;
  2719. }
  2720. $message = $data['privatemsg_message'];
  2721. $show_span = !isset($options['privatemsg-show-span']) || $options['privatemsg-show-span'];
  2722. // We do not replace tokens if the user viewing the message is the author or
  2723. // not a real recipient to avoid confusion.
  2724. $sql = "SELECT 1 FROM {pm_index} WHERE recipient = :uid AND type IN ('hidden', 'user') AND mid = :mid";
  2725. $args = array(':uid' => $recipient->uid, ':mid' => $message->mid);
  2726. if ($message->author->uid == $recipient->uid || !db_query($sql, $args)->fetchField()) {
  2727. // Get all tokens of the message.
  2728. $tokens = token_scan($text);
  2729. $invalid_tokens = array();
  2730. if (function_exists('token_get_invalid_tokens_by_context')) {
  2731. $invalid_tokens = token_get_invalid_tokens_by_context($text, array('privatemsg_message'));
  2732. }
  2733. if (!empty($tokens)) {
  2734. $replacements = array();
  2735. // Loop over the found tokens.
  2736. foreach ($tokens as $tokens_type) {
  2737. // token_replace() returns tokens separated by type.
  2738. foreach ($tokens_type as $original) {
  2739. // Displaying invalid tokens only works with token.module.
  2740. if (in_array($original, $invalid_tokens)) {
  2741. $token = t('INVALID TOKEN @token', array('@token' => $original), array('langcode' => $language_code));
  2742. if (!$show_span) {
  2743. $replacements[$original] = '< ' . $token . ' >';
  2744. }
  2745. else {
  2746. $replacements[$original] = '<span class="privatemsg-token-invalid">&lt; ' . $token . ' &gt;</span>';
  2747. }
  2748. }
  2749. else {
  2750. $token = t('Token @token', array('@token' => $original), array('langcode' => $language_code));
  2751. if (!$show_span) {
  2752. $replacements[$original] = '< ' . $token . ' >';
  2753. }
  2754. else {
  2755. $replacements[$original] = '<span class="privatemsg-token-valid">&lt; ' . $token . ' &gt;</span>';
  2756. }
  2757. }
  2758. }
  2759. }
  2760. $text = str_replace(array_keys($replacements), $replacements, $text);
  2761. // If there are any tokens, add a notice that the tokens will be replaced
  2762. // for the recipient.
  2763. if (!empty($options['privatemsg-token-notice'])) {
  2764. $text .= '<p class="privatemsg-token-notice">' . t('Note: Valid tokens will be replaced when a recipient is reading this message.') . '</p>';
  2765. }
  2766. }
  2767. return $text;
  2768. }
  2769. // If the user is a recipient, use default token_replace() function.
  2770. return token_replace($text, $data, $options);
  2771. }
  2772. /**
  2773. * Implements hook_entity_property_info().
  2774. */
  2775. function privatemsg_entity_property_info() {
  2776. $info = array();
  2777. // Add meta-data about the basic node properties.
  2778. $properties = &$info['privatemsg_message']['properties'];
  2779. $properties = array(
  2780. 'mid' => array(
  2781. 'type' => 'integer',
  2782. 'label' => t('Private message ID'),
  2783. 'description' => t('Private message ID'),
  2784. ),
  2785. 'thread_id' => array(
  2786. 'type' => 'integer',
  2787. 'label' => t('Private message thread ID'),
  2788. 'description' => t('Private message thread ID'),
  2789. 'getter callback' => 'entity_property_verbatim_get',
  2790. ),
  2791. 'author' => array(
  2792. 'type' => 'user',
  2793. 'label' => t('Private message author'),
  2794. 'description' => t('Private message author'),
  2795. 'setter callback' => 'entity_property_verbatim_set',
  2796. ),
  2797. 'subject' => array(
  2798. 'type' => 'text',
  2799. 'label' => t('Private message subject'),
  2800. 'description' => t('Private message subject'),
  2801. 'setter callback' => 'entity_property_verbatim_set',
  2802. ),
  2803. 'body' => array(
  2804. 'type' => 'text',
  2805. 'label' => t('Private message body'),
  2806. 'description' => t('Private message body'),
  2807. 'setter callback' => 'entity_property_verbatim_set',
  2808. ),
  2809. 'timestamp' => array(
  2810. 'type' => 'date',
  2811. 'label' => t('Private message sent date'),
  2812. 'description' => t('Private message sent date'),
  2813. ),
  2814. );
  2815. return $info;
  2816. }
  2817. /**
  2818. * Private message controller, loads private messages.
  2819. */
  2820. class PrivatemsgMessageController extends DrupalDefaultEntityController {
  2821. protected $account = NULL;
  2822. protected function attachLoad(&$messages, $revision_id = FALSE) {
  2823. global $user;
  2824. foreach ($messages as $message) {
  2825. $message->user = $this->account ? $this->account : $user;
  2826. // Load author of message.
  2827. if (!($message->author = user_load($message->author))) {
  2828. // If user does not exist, load anonymous user.
  2829. $message->author = user_load(0);
  2830. }
  2831. }
  2832. parent::attachLoad($messages, $revision_id);
  2833. }
  2834. protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
  2835. // Remove account from conditions.
  2836. if (isset($conditions['account'])) {
  2837. $this->account = $conditions['account'];
  2838. unset($conditions['account']);
  2839. }
  2840. $query = parent::buildQuery($ids, $conditions, $revision_id);
  2841. $query->fields('pmi', array('is_new', 'thread_id'));
  2842. if ($this->account) {
  2843. $query
  2844. ->condition('pmi.recipient', $this->account->uid)
  2845. ->condition('pmi.type', array('hidden', 'user'));
  2846. }
  2847. else {
  2848. // If no account is given, at least limit the result to a single row per
  2849. // message.
  2850. $query->distinct();
  2851. }
  2852. $query->join('pm_index', 'pmi', "base.mid = pmi.mid");
  2853. return $query;
  2854. }
  2855. }
  2856. /**
  2857. * Implements hook_date_formats().
  2858. */
  2859. function privatemsg_date_formats() {
  2860. $formats = array('g:i a', 'H:i', 'M j', 'j M', 'm/d/y', 'd/m/y', 'j/n/y', 'n/j/y');
  2861. $types = array_keys(privatemsg_date_format_types());
  2862. $date_formats = array();
  2863. foreach ($types as $type) {
  2864. foreach ($formats as $format) {
  2865. $date_formats[] = array(
  2866. 'type' => $type,
  2867. 'format' => $format,
  2868. 'locales' => array(),
  2869. );
  2870. }
  2871. }
  2872. return $date_formats;
  2873. }
  2874. /**
  2875. * Implements hook_date_format_types().
  2876. */
  2877. function privatemsg_date_format_types() {
  2878. return array(
  2879. 'privatemsg_current_day' => t('Privatemsg: Current day'),
  2880. 'privatemsg_current_year' => t('Privatemsg: Current year'),
  2881. 'privatemsg_years' => t('Privatemsg: Other years'),
  2882. );
  2883. }
  2884. /**
  2885. * Formats a timestamp according to the defines rules.
  2886. *
  2887. * Examples/Rules:
  2888. *
  2889. * Current hour: 25 min ago
  2890. * Current day (but not within the hour): 10:30 am
  2891. * Current year (but not on the same day): Nov 25
  2892. * Prior years (not the current year): 11/25/08
  2893. *
  2894. * @param $timestamp
  2895. * UNIX Timestamp.
  2896. *
  2897. * @return
  2898. * The formatted date.
  2899. */
  2900. function privatemsg_format_date($timestamp) {
  2901. if ($timestamp > ((int)(REQUEST_TIME / 3600)) * 3600) {
  2902. return t('@interval ago', array('@interval' => format_interval(abs(REQUEST_TIME - $timestamp), 1)));
  2903. }
  2904. if ($timestamp > ((int)(REQUEST_TIME / 86400)) * 86400) {
  2905. return format_date($timestamp, 'privatemsg_current_day');
  2906. }
  2907. if ($timestamp > mktime(0, 0, 0, 1, 0, date('Y'))) {
  2908. return format_date($timestamp, 'privatemsg_current_year');
  2909. }
  2910. return format_date($timestamp, 'privatemsg_years');
  2911. }

Functions

Namesort descending Description
privatemsg_account_fieldset_remove_if_empty Hides the settings fieldset if there are no options to be displayed.
privatemsg_block_configure Implements hook_block_configure().
privatemsg_block_info Implements hook_block_info().
privatemsg_block_save Implements hook_block_save().
privatemsg_block_view Implements hook_block_view().
privatemsg_build_modes Implements hook_build_modes().
privatemsg_comment_view Implements hook_comment_view().
privatemsg_cron Implements hook_cron().
privatemsg_date_formats Implements hook_date_formats().
privatemsg_date_format_types Implements hook_date_format_types().
privatemsg_entity_info Implements hook_entity_info().
privatemsg_entity_property_info Implements hook_entity_property_info().
privatemsg_field_extra_fields Implements hook_field_extra_fields().
privatemsg_file_download_access Implements hook_file_download_access().
privatemsg_format_date Formats a timestamp according to the defines rules.
privatemsg_form_alter Implements hook_form_alter().
privatemsg_get_link Returns a link to send message form for a specific users.
privatemsg_is_disabled Checks the status of private messaging for provided user.
privatemsg_menu Implements hook_menu().
privatemsg_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
privatemsg_message_change_delete Delete or restore a message.
privatemsg_message_change_recipient Add or remove a recipient to an existing message.
privatemsg_message_change_status Changes the read/new status of a single message.
privatemsg_message_load Load a single message.
privatemsg_message_load_multiple Load multiple messages.
privatemsg_message_uri_callback Returns the URI for a private message.
privatemsg_new_thread Send a new message.
privatemsg_node_view Implements hook_node_view().
privatemsg_operation_execute Execute an operation on a number of threads.
privatemsg_permission Implements hook_permission().
privatemsg_privatemsg_block_message Implements hook_privatemsg_block_message().
privatemsg_privatemsg_name_lookup Implements hook_privatemsg_name_lookup().
privatemsg_privatemsg_recipient_type_info Implements hook_privatemsg_recipient_type_info().
privatemsg_privatemsg_thread_operations Implements hook_privatemsg_thread_operations().
privatemsg_privatemsg_view_template Implements hook_privatemsg_view_template().
privatemsg_recipient_access This function is used to test if the current user has write/view access for a specific recipient type.
privatemsg_recipient_format Format a single participant.
privatemsg_recipient_get_type Return a single recipient type information.
privatemsg_recipient_get_types Returns an array of defined recipient types.
privatemsg_recipient_key Return key for a recipient object used for arrays.
privatemsg_reply Send a reply message
privatemsg_sql_autocomplete Looks up autocomplete suggestions for users.
privatemsg_sql_deleted Query Builder function to load all messages that should be flushed.
privatemsg_sql_list Query definition to load a list of threads.
privatemsg_sql_messages Query definition to load messages of one or multiple threads.
privatemsg_sql_participants Load all participants of a thread.
privatemsg_sql_unread_count Count threads with unread messages.
privatemsg_theme
privatemsg_thread_change_delete Delete or restore one or multiple threads.
privatemsg_thread_change_status Marks one or multiple threads as (un)read.
privatemsg_thread_load Load a thread with all the messages and participants.
privatemsg_title_callback
privatemsg_tokens Implements hook_tokens().
privatemsg_token_info Implements hook_token_info().
privatemsg_token_replace Wrapper function for token_replace() that does not replace the tokens if the user viewing the message is not a recipient.
privatemsg_unread_count Return number of unread messages for an account.
privatemsg_user_access Privatemsg wrapper for user_access.
privatemsg_user_autocomplete Implements callback_recipient_autocomplete().
privatemsg_user_cancel Implements hook_user_cancel().
privatemsg_user_delete Implements hook_user_delete().
privatemsg_user_load_multiple Privatemsg wrapper function for user_load_multiple().
privatemsg_user_login Implements hook_user_login().
privatemsg_user_update Implements hook_user_insert().
privatemsg_user_view Implements hook_user_view().
privatemsg_views_api Implements hook_views_api().
privatemsg_view_access Check access to the view messages page.
template_preprocess_privatemsg_recipients
template_preprocess_privatemsg_view
_privatemsg_assemble_query Generates a query based on a query id.
_privatemsg_block_menu
_privatemsg_block_new
_privatemsg_delete_data Delete all message data from a user.
_privatemsg_format_participants Format an array of user objects.
_privatemsg_generate_user_array Generate array of user objects based on a string.
_privatemsg_handle_recipients Handle the non-user recipients of a new message.
_privatemsg_list_headers Returns a table header definition based on the submitted keys.
_privatemsg_list_thread Formats all rows (#options) in the privatemsg tableselect thread list.
_privatemsg_load_thread_participants Load all participants of a thread.
_privatemsg_parse_userstring Extract the valid usernames of a string and loads them.
_privatemsg_send Internal function to save a message.
_privatemsg_validate_message

Constants

Namesort descending Description
PRIVATEMSG_READ Status constant for read messages.
PRIVATEMSG_UNLIMITED Show unlimited messages in a thread.
PRIVATEMSG_UNREAD Status constant for unread messages.

Classes

Namesort descending Description
PrivatemsgMessageController Private message controller, loads private messages.