privatemsg.module

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

Allows users to send private messages to other users.

File

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

Functions

Namesort descending Description
privatemsg_account_fieldset_remove_if_empty Hides the settings fieldset if there are no options to be displayed.
privatemsg_allow_disable Checks if the user is allowed to disable/enable private message.
privatemsg_block_configure Implements hook_block_configure().
privatemsg_block_info Implements hook_block_info().
privatemsg_block_save Implements hook_block_save().
privatemsg_block_view Implements hook_block_view().
privatemsg_build_modes Implements hook_build_modes().
privatemsg_comment_view Implements hook_comment_view().
privatemsg_cron Implements hook_cron().
privatemsg_date_formats Implements hook_date_formats().
privatemsg_date_format_types Implements hook_date_format_types().
privatemsg_del_setting
privatemsg_entity_info Implements hook_entity_info().
privatemsg_entity_property_info Implements hook_entity_property_info().
privatemsg_field_extra_fields Implements hook_field_extra_fields().
privatemsg_file_download_access Implements hook_file_download_access().
privatemsg_format_date Formats a timestamp according to the defines rules.
privatemsg_form_alter Implements hook_form_alter().
privatemsg_get_default_setting_ids Extract the default ids of a user account.
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_menu Implements hook_menu().
privatemsg_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
privatemsg_message_change_delete Delete or restore a message.
privatemsg_message_change_recipient Add or remove a recipient to an existing message.
privatemsg_message_change_status Changes the read/new status of a single message.
privatemsg_message_load Load a single message.
privatemsg_message_load_multiple Load multiple messages.
privatemsg_message_uri_callback Returns the URI for a private message.
privatemsg_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_node_view Implements hook_node_view().
privatemsg_operation_execute Execute an operation on a number of threads.
privatemsg_permission Implements hook_permission().
privatemsg_privatemsg_block_message Implements hook_privatemsg_block_message().
privatemsg_privatemsg_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 Looks up autocomplete suggestions for users.
privatemsg_sql_deleted Query Builder function to load all messages that should be flushed.
privatemsg_sql_list Query definition to load a list of threads.
privatemsg_sql_messages Query definition to load messages of one or multiple threads.
privatemsg_sql_participants Load all participants of a thread.
privatemsg_sql_settings Query builder function to load user settings.
privatemsg_sql_unread_count Count threads with unread messages.
privatemsg_theme
privatemsg_thread_change_delete Delete or restore one or multiple threads.
privatemsg_thread_change_status Marks one or multiple threads as (un)read.
privatemsg_thread_load Load a thread with all the messages and participants.
privatemsg_title_callback
privatemsg_tokens Implements hook_tokens().
privatemsg_token_info Implements hook_token_info().
privatemsg_token_replace Wrapper function for token_replace() that does not replace the tokens if the user viewing the message is not a recipient.
privatemsg_unread_count Return number of unread messages for an account.
privatemsg_user_access Privatemsg wrapper for user_access.
privatemsg_user_autocomplete Implements callback_recipient_autocomplete().
privatemsg_user_cancel Implements hook_user_cancel().
privatemsg_user_delete Implements hook_user_delete().
privatemsg_user_load_multiple Privatemsg wrapper function for user_load_multiple().
privatemsg_user_login Implements hook_user_login().
privatemsg_user_update Implements hook_user_update().
privatemsg_user_view Implements hook_user_view().
privatemsg_views_api Implements hook_views_api().
privatemsg_view_access Check access to the view messages page.
template_preprocess_privatemsg_recipients
template_preprocess_privatemsg_view Implements hook_preprocess_THEME().
_privatemsg_assemble_query Generates a query based on a query id.
_privatemsg_block_menu
_privatemsg_block_new
_privatemsg_delete_data Delete all message data from a user.
_privatemsg_format_participants Format an array of user objects.
_privatemsg_generate_user_array Generate array of user objects based on a string.
_privatemsg_handle_recipients Handle the non-user recipients of a new message.
_privatemsg_list_thread Formats all rows (#options) in the privatemsg tableselect thread list.
_privatemsg_load_thread_participants Load all participants of a thread.
_privatemsg_parse_userstring Extract the valid usernames of a string and loads them.
_privatemsg_send Internal function to save a message.
_privatemsg_validate_message

Constants

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

Classes

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