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

Functions

Namesort descending Description
privatemsg_account_fieldset_remove_if_empty Hides the settings fieldset if there are no options to be displayed.
privatemsg_block
privatemsg_cron Implements hook_cron().
privatemsg_del_setting
privatemsg_get_default_setting_ids Extract the default ids of a user account.
privatemsg_get_dynamic_url_prefix Returns the current dynamic url prefix.
privatemsg_get_enabled_headers Returns an array of enabled header keys.
privatemsg_get_headers Returns an array of defined column headers for message listings.
privatemsg_get_link Returns a link to send message form for a specific users.
privatemsg_get_setting Retrieve a user setting.
privatemsg_is_disabled Checks the status of private messaging for provided user.
privatemsg_link Implements hook_link().
privatemsg_menu Implements hook_menu().
privatemsg_menu_access Checks access to a menu entry.
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_mollom_form_info Implements hook_mollom_form_info().
privatemsg_mollom_form_list Implements hook_mollom_form_list().
privatemsg_new_thread Send a new message.
privatemsg_operation_execute Execute an operation on a number of threads.
privatemsg_perm Implements hook_perm().
privatemsg_popups Implements hook_popups().
privatemsg_privatemsg_block_message Implements hook_privatemsg_block_message().
privatemsg_privatemsg_header_info Implements hook_privatemsg_header_info().
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_set_setting
privatemsg_sql_autocomplete Query definition to search for username autocomplete suggestions.
privatemsg_sql_deleted Query Builder function to load all messages that should be flushed.
privatemsg_sql_list
privatemsg_sql_load Query function for loading a single or multiple messages.
privatemsg_sql_messages Query definition to load messages of one or multiple threads.
privatemsg_sql_participants Load all participants of a thread.
privatemsg_sql_privatemsg_query_settings
privatemsg_sql_unread_count Query definition to count 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_unread_count Return number of unread messages for an account.
privatemsg_user
privatemsg_user_access Privatemsg wrapper for user_access.
privatemsg_user_autocomplete Implements callback_recipient_autocomplete().
privatemsg_user_load Privatemsg wrapper function for user_load() with a static cache.
privatemsg_views_api Implements hook_views_api().
privatemsg_view_access Check access to the view messages page.
private_message_view_options
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_format_participants Format an array of user objects.
_privatemsg_generate_user_array Generate aray of user objects based on a string.
_privatemsg_handle_recipients Handle the non-user recipients of a new message.
_privatemsg_list_thread Formats a row in the message list.
_privatemsg_list_thread_fallback Table row definition for themes that don't support theme patterns.
_privatemsg_load_thread_participants Load all participants of a thread, optionally without author.
_privatemsg_parse_userstring Extract the valid usernames of a string and loads them.
_privatemsg_send Internal function to save a message.
_privatemsg_setting_static_cache Holds the static cache for privatemsg user settings.
_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.