privatemsg.pages.inc

  1. 7-1 privatemsg.pages.inc
  2. 7-2 privatemsg.pages.inc
  3. 6-2 privatemsg.pages.inc

User menu callbacks for Privatemsg.

File

privatemsg.pages.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * User menu callbacks for Privatemsg.
  5. */
  6. /**
  7. * Returns a form which handles and displays thread actions.
  8. *
  9. * Additional actions can be added with the privatemsg_thread_operations hook.
  10. * It is also possible to extend this form with additional buttons or other
  11. * elements, in that case, the definitions in the above hook need no label tag,
  12. * instead, the submit button key needs to match with the key of the operation.
  13. *
  14. * @see hook_privatemsg_thread_operations()
  15. *
  16. * @return
  17. * The FAPI definitions for the thread action form.
  18. */
  19. function _privatemsg_action_form($type) {
  20. $form = array(
  21. '#prefix' => '<div class="container-inline">',
  22. '#suffix' => '</div>',
  23. '#weight' => -5,
  24. );
  25. // Display all operations which have a label.
  26. $operations = module_invoke_all('privatemsg_thread_operations', $type);
  27. drupal_alter('privatemsg_thread_operations', $operations, $type);
  28. foreach ($operations as $operation => $array) {
  29. if (!empty($array['button'])) {
  30. $form[$operation] = array(
  31. '#type' => 'submit',
  32. '#value' => $array['label'],
  33. '#ajax' => array(
  34. 'callback' => 'privatemsg_list_js',
  35. 'wrapper' => 'privatemsg-list-form',
  36. 'effect' => 'fade',
  37. ),
  38. );
  39. }
  40. elseif (isset($array['label'])) {
  41. $options[$operation] = $array['label'];
  42. }
  43. }
  44. if (!empty($options)) {
  45. array_unshift($options, t('Actions...'));
  46. $form['operation'] = array(
  47. '#type' => 'select',
  48. '#options' => $options,
  49. '#ajax' => array(
  50. 'callback' => 'privatemsg_list_js',
  51. 'wrapper' => 'privatemsg-list-form',
  52. 'effect' => 'fade',
  53. ),
  54. '#executes_submit_callback' => TRUE,
  55. );
  56. $form['submit'] = array(
  57. '#type' => 'submit',
  58. '#value' => t('Execute'),
  59. '#attributes' => array('class' => array('form-item')),
  60. '#states' => array(
  61. 'visible' => array(
  62. // This is never true, button is always hidden when JS is enabled.
  63. ':input[name=operation]' => array('value' => 'fake'),
  64. ),
  65. ),
  66. );
  67. }
  68. return $form;
  69. }
  70. /**
  71. * AJAX callback to return the form again.
  72. */
  73. function privatemsg_list_js($form, $form_state) {
  74. return $form['updated'];
  75. }
  76. function privatemsg_delete($form, $form_state, $thread, $message) {
  77. $form['pmid'] = array(
  78. '#type' => 'value',
  79. '#value' => $message->mid,
  80. );
  81. $form['delete_destination'] = array(
  82. '#type' => 'value',
  83. '#value' => count($thread['messages']) > 1 ? 'messages/view/' . $message->thread_id : 'messages',
  84. );
  85. if (privatemsg_user_access('read all private messages')) {
  86. $form['delete_options'] = array(
  87. '#type' => 'checkbox',
  88. '#title' => t('Delete this message for all users?'),
  89. '#description' => t('Tick the box to delete the message for all users.'),
  90. '#default_value' => FALSE,
  91. );
  92. }
  93. return confirm_form($form,
  94. t('Are you sure you want to delete this message?'),
  95. isset($_GET['destination']) ? $_GET['destination'] : 'messages/view/' . $message->thread_id,
  96. t('This action cannot be undone.'),
  97. t('Delete'),
  98. t('Cancel')
  99. );
  100. }
  101. function privatemsg_delete_submit($form, &$form_state) {
  102. global $user;
  103. $account = clone $user;
  104. if ($form_state['values']['confirm']) {
  105. if (isset($form_state['values']['delete_options']) && $form_state['values']['delete_options']) {
  106. privatemsg_message_change_delete($form_state['values']['pmid'], 1);
  107. drupal_set_message(t('Message has been deleted for all users.'));
  108. }
  109. else {
  110. privatemsg_message_change_delete($form_state['values']['pmid'], 1, $account);
  111. drupal_set_message(t('Message has been deleted.'));
  112. }
  113. }
  114. $form_state['redirect'] = $form_state['values']['delete_destination'];
  115. }
  116. /**
  117. * List messages.
  118. *
  119. * @param $argument
  120. * An argument to pass through to the query builder.
  121. * @param $uid
  122. * User id messages of another user should be displayed
  123. *
  124. * @return
  125. * Form array
  126. */
  127. function privatemsg_list_page($argument = 'list', $uid = NULL) {
  128. global $user;
  129. // Setting default behavior...
  130. $account = $user;
  131. // Because uid is submitted by the menu system, it's a string not a integer.
  132. if ((int)$uid > 0 && $uid != $user->uid) {
  133. // Trying to view someone else's messages...
  134. if (!$account_check = user_load($uid)) {
  135. return MENU_NOT_FOUND;
  136. }
  137. if (!privatemsg_user_access('read all private messages')) {
  138. return MENU_ACCESS_DENIED;
  139. }
  140. // Has rights and user_load return an array so user does exist
  141. $account = $account_check;
  142. }
  143. return drupal_get_form('privatemsg_list', $argument, $account);
  144. }
  145. function privatemsg_list($form, &$form_state, $argument, $account) {
  146. // If this is an AJAX request, update $_GET['q'] so that table sorting and
  147. // similar links are using the correct base path.
  148. if ($_GET['q'] == 'system/ajax') {
  149. $q = 'messages';
  150. if (!empty($argument)) {
  151. $q .= '/' . $argument;
  152. }
  153. $_GET['q'] = $q;
  154. }
  155. // Load the themed list headers based on the available data.
  156. $headers = privatemsg_get_headers(TRUE);
  157. $form = array(
  158. '#list_argument' => $argument,
  159. '#submit' => array('privatemsg_list_submit'),
  160. 'updated' => array(
  161. '#prefix' => '<div id="privatemsg-list-form">',
  162. '#suffix' => '</div>',
  163. ),
  164. '#attached' => array(
  165. 'css' => array(
  166. drupal_get_path('module', 'privatemsg') . '/styles/privatemsg-list.css',
  167. ),
  168. ),
  169. );
  170. $form['updated']['list'] = array(
  171. '#type' => 'tableselect',
  172. '#header' => $headers,
  173. '#options' => array(),
  174. '#attributes' => array('class' => array('privatemsg-list')),
  175. '#empty' => t('No messages available.'),
  176. '#weight' => 5,
  177. '#pre_render' => array('_privatemsg_list_thread'),
  178. );
  179. $query = _privatemsg_assemble_query('list', $account, $argument);
  180. $i = 0;
  181. foreach ($query->execute() as $row) {
  182. // Store the raw row data.
  183. $form['updated']['list']['#options'][$row->thread_id] = (array)$row;
  184. // Tableselect sorts the options, set a weight so that the order doesn't get
  185. // changed.
  186. $form['updated']['list']['#options'][$row->thread_id]['#weight'] = $i++;
  187. }
  188. if (!empty($form['updated']['list']['#options'])) {
  189. // Load the last reply that is not from the current user.
  190. $result = db_query('SELECT pmi.thread_id, MAX(pm.mid) AS last_message FROM {pm_message} pm INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid WHERE pmi.thread_id IN (:thread_ids) AND pm.author <> :current_uid GROUP BY pmi.thread_id', array(':current_uid' => $account->uid, ':thread_ids' => array_keys($form['updated']['list']['#options'])));
  191. foreach ($result as $row) {
  192. // Set replied flag if there is no newer message from another user than
  193. // the last replied.
  194. if ($row->last_message <= $form['updated']['list']['#options'][$row->thread_id]['last_reply_to_mid']) {
  195. $form['updated']['list']['#options'][$row->thread_id]['is_replied'] = TRUE;
  196. }
  197. }
  198. $form['updated']['actions'] = _privatemsg_action_form($argument);
  199. }
  200. // Save the currently active account, used for actions.
  201. $form['account'] = array('#type' => 'value', '#value' => $account);
  202. // Define checkboxes, pager and theme
  203. $form['updated']['pager'] = array('#markup' => theme('pager'), '#weight' => 20);
  204. return $form;
  205. }
  206. /**
  207. * Process privatemsg_list form submissions.
  208. *
  209. * Execute the chosen action on the selected messages. This function is
  210. * based on node_admin_nodes_submit().
  211. */
  212. function privatemsg_list_submit($form, &$form_state) {
  213. // Load all available operation definitions.
  214. $operations = module_invoke_all('privatemsg_thread_operations', $form['#list_argument']);
  215. drupal_alter('privatemsg_thread_operations', $operations, $form['#list_argument']);
  216. // Default "default" operation, which won't do anything.
  217. $operation = array('callback' => 0);
  218. // Check if a valid operation has been submitted.
  219. if (isset($form_state['values']['operation']) && isset($operations[$form_state['values']['operation']])) {
  220. $operation = $operations[$form_state['values']['operation']];
  221. }
  222. if (!empty($form_state['values']['op'])) {
  223. // Load all keys where the value is the current op.
  224. $keys = array_keys($form_state['values'], $form_state['values']['op']);
  225. // Loop over them and detect if a matching button was pressed.
  226. foreach ($keys as $key) {
  227. if ($key != 'op' && isset($operations[$key])) {
  228. $operation = $operations[$key];
  229. }
  230. }
  231. }
  232. // Only execute something if we have a valid callback and at least one checked thread.
  233. if (!empty($operation['callback'])) {
  234. // Hack to fix destination during ajax requests.
  235. if (isset($form_state['input']['ajax_page_state'])) {
  236. $destination = 'messages';
  237. if (!empty($form['#list_argument'])) {
  238. $destination .= '/' . $form['#list_argument'];
  239. }
  240. $_GET['destination'] = $destination;
  241. }
  242. privatemsg_operation_execute($operation, $form_state['values']['list'], $form_state['values']['account']);
  243. }
  244. $form_state['rebuild'] = TRUE;
  245. $form_state['input'] = array();
  246. }
  247. /**
  248. * Form builder function; Write a new private message.
  249. */
  250. function privatemsg_new($form, &$form_state, $recipients = '', $subject = '') {
  251. // Convert recipients to array of user objects.
  252. $unique = FALSE;
  253. if (!empty($recipients) && is_string($recipients) || is_int($recipients)) {
  254. $unique = TRUE;
  255. $recipients = _privatemsg_generate_user_array($recipients);
  256. }
  257. else {
  258. $recipients = array();
  259. }
  260. // Subject has / encoded twice if clean urls are enabled to get it through
  261. // mod_rewrite and the menu system. Decode it once more.
  262. $subject = str_replace('%2F', '/', $subject);
  263. if (isset($form_state['values'])) {
  264. if (isset($form_state['values']['recipient'])) {
  265. $recipients_plain = $form_state['values']['recipient'];
  266. }
  267. $subject = $form_state['values']['subject'];
  268. }
  269. else {
  270. $to = _privatemsg_get_allowed_recipients($recipients);
  271. $recipients_plain = '';
  272. if (!empty($to)) {
  273. $to_plain = array();
  274. $to_title = array();
  275. foreach ($to as $recipient) {
  276. // Load user(s) with that name
  277. $to_user = _privatemsg_parse_userstring($recipient->name);
  278. // If the count of duplicated is more than 0 that means the recipient name is used by the other recipient type,
  279. // so we should use unique.
  280. $to_plain[] = privatemsg_recipient_format($recipient, array('plain' => TRUE, 'unique' => (count($to_user[2]) > 0)));
  281. $to_title[] = privatemsg_recipient_format($recipient, array('plain' => TRUE));
  282. }
  283. $recipients_plain = implode(', ', $to_plain);
  284. $recipients_title = implode(', ', $to_title);
  285. }
  286. }
  287. if (!empty($recipients_title)) {
  288. drupal_set_title(t('Write new message to %recipient', array('%recipient' => $recipients_title)), PASS_THROUGH);
  289. }
  290. else {
  291. drupal_set_title(t('Write new message'));
  292. }
  293. $form = array(
  294. '#access' => privatemsg_user_access('write privatemsg'),
  295. );
  296. $form += _privatemsg_form_base_fields($form, $form_state);
  297. $description_array = array();
  298. foreach (privatemsg_recipient_get_types() as $name => $type) {
  299. if (privatemsg_recipient_access($name, 'write')) {
  300. $description_array[] = $type['description'];
  301. }
  302. }
  303. $description = t('Enter the recipient, separate recipients with commas.');
  304. $description .= theme('item_list', array('items' => $description_array));
  305. $form['recipient'] = array(
  306. '#type' => 'textfield',
  307. '#title' => t('To'),
  308. '#description' => $description,
  309. '#default_value' => $recipients_plain,
  310. '#required' => TRUE,
  311. '#weight' => -10,
  312. '#size' => 50,
  313. '#autocomplete_path' => 'messages/autocomplete',
  314. // Disable #maxlength, make it configurable by number of recipients, not
  315. // their name length.
  316. '#after_build' => array('privatemsg_disable_maxlength'),
  317. );
  318. $form['subject'] = array(
  319. '#type' => 'textfield',
  320. '#title' => t('Subject'),
  321. '#size' => 50,
  322. '#maxlength' => 255,
  323. '#default_value' => $subject,
  324. '#weight' => -5,
  325. );
  326. $url = 'messages';
  327. if (isset($_REQUEST['destination'])) {
  328. $url = $_REQUEST['destination'];
  329. }
  330. $form['actions']['cancel'] = array(
  331. '#value' => l(t('Cancel'), $url, array('attributes' => array('id' => 'edit-cancel'))),
  332. '#weight' => 20,
  333. );
  334. return $form;
  335. }
  336. /**
  337. * Remove the maxlength attribute from a field.
  338. */
  339. function privatemsg_disable_maxlength($element) {
  340. unset($element['#maxlength']);
  341. return $element;
  342. }
  343. /**
  344. * Form builder function; Write a reply to a thread.
  345. */
  346. function privatemsg_form_reply($form, &$form_state, $thread) {
  347. $form = array(
  348. '#access' => privatemsg_user_access('write privatemsg') || privatemsg_user_access('reply only privatemsg'),
  349. );
  350. $to = _privatemsg_get_allowed_recipients($thread['participants'], $thread['thread_id']);
  351. if (empty($to)) {
  352. // Display a message if some users are blocked.
  353. // @todo: Move this check out of the form, don't use the form in that case.
  354. $blocked_messages = &drupal_static('privatemsg_blocked_messages', array());
  355. if (count($blocked_messages)) {
  356. $blocked = t('You can not reply to this conversation because all recipients are blocked.');
  357. $blocked .= theme('item_list', array('items' => $blocked_messages));
  358. $form['blocked']['#markup'] = $blocked;
  359. }
  360. else {
  361. $form['#access'] = FALSE;
  362. }
  363. return $form;
  364. }
  365. $form += _privatemsg_form_base_fields($form, $form_state);
  366. $form['actions']['cancel'] = array(
  367. '#value' => l(t('Clear'), $_GET['q'], array('attributes' => array('id' => 'edit-cancel'))),
  368. '#weight' => 20,
  369. );
  370. // Include the mid of the message we're responding to so we can mark it as
  371. // replied when the form is submitted.
  372. $reply_to_mid = end($thread['messages'])->mid;
  373. $form['reply_to_mid'] = array(
  374. '#type' => 'value',
  375. '#value' => $reply_to_mid,
  376. );
  377. $form['thread_id'] = array(
  378. '#type' => 'value',
  379. '#value' => $thread['thread_id'],
  380. );
  381. $form['subject'] = array(
  382. '#type' => 'value',
  383. '#default_value' => $thread['subject'],
  384. );
  385. $form['reply'] = array(
  386. '#markup' => '<h2 class="privatemsg-reply">' . t('Reply') . '</h2>',
  387. '#weight' => -10,
  388. );
  389. $form['read_all'] = array(
  390. '#type' => 'value',
  391. '#value' => $thread['read_all'],
  392. );
  393. return $form;
  394. }
  395. /**
  396. * Returns the common fields of the reply and new form.
  397. */
  398. function _privatemsg_form_base_fields($form, &$form_state) {
  399. global $user;
  400. if (isset($form_state['privatemsg_preview'])) {
  401. $preview_subject = '';
  402. // Only display subject on preview for new messages.
  403. if (empty($form_state['validate_built_message']->thread_id)) {
  404. $preview_subject = check_plain($form_state['validate_built_message']->subject);
  405. // If message has tokens, replace them.
  406. if ($form_state['validate_built_message']->has_tokens) {
  407. $preview_subject = privatemsg_token_replace($preview_subject, array('privatemsg_message' => $form_state['validate_built_message']), array('sanitize' => TRUE, 'privatemsg-show-span' => FALSE));
  408. }
  409. }
  410. $form['message_header'] = array(
  411. '#type' => 'fieldset',
  412. '#title' => !empty($preview_subject) ? $preview_subject : t('Preview'),
  413. '#attributes' => array('class' => array('preview')),
  414. '#weight' => -20,
  415. );
  416. $form['message_header']['message_preview'] = $form_state['privatemsg_preview'];
  417. }
  418. $form['author'] = array(
  419. '#type' => 'value',
  420. '#value' => $user,
  421. );
  422. // The input filter widget looses the format during preview, specify it
  423. // explicitly.
  424. if (isset($form_state['values']) && array_key_exists('format', $form_state['values'])) {
  425. $format = $form_state['values']['format'];
  426. }
  427. $form['body'] = array(
  428. '#type' => 'text_format',
  429. '#title' => t('Message'),
  430. '#rows' => 6,
  431. '#weight' => -3,
  432. '#resizable' => TRUE,
  433. '#format' => isset($format) ? $format : NULL,
  434. '#after_build' => array('privatemsg_check_format_access'),
  435. );
  436. if (privatemsg_user_access('use tokens in privatemsg') && module_exists('token')) {
  437. $form['token'] = array(
  438. '#type' => 'fieldset',
  439. '#title' => t('Token browser'),
  440. '#weight' => -1,
  441. '#collapsible' => TRUE,
  442. '#collapsed' => TRUE,
  443. );
  444. $form['token']['browser'] = array(
  445. '#theme' => 'token_tree',
  446. '#token_types' => array('privatemsg_message'),
  447. );
  448. }
  449. $form['actions'] = array('#type' => 'actions');
  450. if (variable_get('privatemsg_display_preview_button', FALSE)) {
  451. $form['actions']['preview'] = array(
  452. '#type' => 'submit',
  453. '#value' => t('Preview message'),
  454. '#validate' => array('privatemsg_new_validate'),
  455. '#submit' => array('privatemsg_new_preview'),
  456. '#weight' => 10,
  457. );
  458. }
  459. $form['actions']['submit'] = array(
  460. '#type' => 'submit',
  461. '#value' => t('Send message'),
  462. '#weight' => 15,
  463. '#validate' => array('privatemsg_new_validate'),
  464. '#submit' => array('privatemsg_new_submit'),
  465. );
  466. // Attach field widgets.
  467. $message = (object) array();
  468. if (isset($form_state['validate_built_message'])) {
  469. $message = $form_state['validate_built_message'];
  470. }
  471. // If a module (e.g. OG) adds a validate or submit callback to the form in
  472. // field_attach_form, the form system will not add ours automatically
  473. // anymore. Therefore, explicitly adding them here.
  474. $form['#submit'] = array('privatemsg_new_submit');
  475. $form['#validate'] = array('privatemsg_new_validate');
  476. field_attach_form('privatemsg_message', $message, $form, $form_state);
  477. return $form;
  478. }
  479. /**
  480. * Check if the current user is allowed to write these recipients.
  481. *
  482. * @param $recipients
  483. * Array of recipient objects.
  484. *
  485. * @return
  486. * Array of allowed recipient objects.
  487. */
  488. function _privatemsg_get_allowed_recipients($recipients, $thread_id = NULL) {
  489. global $user;
  490. $usercount = 0;
  491. $valid = array();
  492. $blocked_messages = &drupal_static('privatemsg_blocked_messages', array());
  493. foreach ($recipients as $recipient) {
  494. // Allow to pass in normal user objects.
  495. if (empty($recipient->type)) {
  496. $recipient->type = 'user';
  497. $recipient->recipient = $recipient->uid;
  498. }
  499. if ($recipient->type == 'hidden') {
  500. continue;
  501. }
  502. if (isset($valid[privatemsg_recipient_key($recipient)])) {
  503. // We already added the recipient to the list, skip him.
  504. continue;
  505. }
  506. if (!privatemsg_recipient_access($recipient->type, 'write', $recipient)) {
  507. // User does not have access to write to this recipient, continue.
  508. continue;
  509. }
  510. if ($recipient->type == 'user' && $recipient->recipient == $user->uid) {
  511. // Skip putting author in the recipients list for now.
  512. // Will be added if he is the only recipient.
  513. $usercount++;
  514. continue;
  515. }
  516. $valid[privatemsg_recipient_key($recipient)] = $recipient;
  517. }
  518. foreach (module_invoke_all('privatemsg_block_message', $user, $valid, array('thread_id' => $thread_id)) as $blocked) {
  519. // Unset the recipient.
  520. unset($valid[$blocked['recipient']]);
  521. // Store blocked messages. These are only displayed if all recipients
  522. // are blocked.
  523. $blocked_messages[] = $blocked['message'];
  524. }
  525. if (empty($valid) && $usercount >= 1 && empty($blocked_messages)) {
  526. // Assume the user sent message to own account as if the usercount is one or
  527. // less, then the user sent a message but not to self.
  528. $valid['user_' . $user->uid] = $user;
  529. }
  530. return $valid;
  531. }
  532. /**
  533. * After build callback; Hide format widget if user doesn't have permission.
  534. */
  535. function privatemsg_check_format_access($element) {
  536. if (isset($element['format'])) {
  537. $element['format']['#access'] = privatemsg_user_access('select text format for privatemsg');
  538. }
  539. return $element;
  540. }
  541. function privatemsg_new_validate($form, &$form_state) {
  542. // The actual message that is being sent, we create this during validation and
  543. // pass to submit to send out.
  544. $message = (object)$form_state['values'];
  545. $message->mid = 0;
  546. $message->format = $message->body['format'];
  547. $message->body = $message->body['value'];
  548. $message->timestamp = REQUEST_TIME;
  549. // Avoid subjects which only consist of a space as these can not be clicked.
  550. $message->subject = trim($message->subject);
  551. $trimmed_body = trim(truncate_utf8(strip_tags($message->body), 50, TRUE, TRUE));
  552. if (empty($message->thread_id) && empty($message->subject) && !empty($trimmed_body)) {
  553. $message->subject = $trimmed_body;
  554. }
  555. // Only parse the user string for a new thread.
  556. if (!isset($message->thread_id)) {
  557. list($message->recipients, $invalid, $duplicates, $denieds) = _privatemsg_parse_userstring($message->recipient);
  558. }
  559. else {
  560. // Load participants. Limit recipients to visible unless read_all is TRUE.
  561. $message->recipients = _privatemsg_load_thread_participants($message->thread_id, $message->read_all ? FALSE : $message->author);
  562. }
  563. if (!empty($invalid)) {
  564. // Display information about invalid recipients.
  565. drupal_set_message(t('The following recipients will not receive this private message: @invalid.', array('@invalid' => implode(", ", $invalid))), 'error');
  566. }
  567. if (!empty($denieds)) {
  568. // Display information about denied recipients.
  569. drupal_set_message(t('You do not have access to write these recipients: @denieds.', array('@denieds' => implode(", ", $denieds))), 'error');
  570. }
  571. if (!empty($duplicates)) {
  572. // Add JS and CSS to allow choosing the recipient.
  573. drupal_add_js(drupal_get_path('module', 'privatemsg') . '/privatemsg-alternatives.js');
  574. // Display information about recipients that couldn't be identified
  575. // uniquely.
  576. $js_duplicates = array();
  577. foreach ($duplicates as $string => $duplicate) {
  578. $alternatives = array();
  579. foreach ($duplicate as $match) {
  580. $formatted_match = privatemsg_recipient_format($match, array('plain' => TRUE, 'unique' => TRUE));
  581. $js_duplicates[$formatted_match] = $string;
  582. $alternatives[] = '<span class="privatemsg-recipient-alternative">' . $formatted_match . '</span>';
  583. }
  584. // Build a formatted list of possible recipients.
  585. $alternatives = theme('item_list', array('items' => $alternatives, 'attributes' => array('class' => array('action-links'))));
  586. form_set_error('recipient', '<span class="privatemsg-alternative-description">' . t('The site has multiple recipients named %string. Please choose your intended recipient: !list', array('%string' => $string, '!list' => $alternatives)) . '</span>');
  587. }
  588. // Also make that information available to the javascript replacement code.
  589. drupal_add_js(array('privatemsg_duplicates' => $js_duplicates), 'setting');
  590. }
  591. $validated = _privatemsg_validate_message($message, TRUE);
  592. foreach ($validated['messages'] as $type => $texts) {
  593. foreach ($texts as $text) {
  594. drupal_set_message($text, $type);
  595. }
  596. }
  597. $form_state['validate_built_message'] = $message;
  598. }
  599. function privatemsg_new_preview($form, &$form_state) {
  600. $message = $form_state['validate_built_message'];
  601. // Execute submit hook, removes empty fields.
  602. field_attach_submit('privatemsg_message', $message, $form, $form_state);
  603. // Load information attached to the message. Use an internal function
  604. // to avoid the internal field cache.
  605. _field_invoke_multiple('load', 'privatemsg_message', array($message->mid => $message));
  606. $form_state['privatemsg_preview'] = array(
  607. '#markup' => theme('privatemsg_view', array('message' => $message)),
  608. '#attached' => array(
  609. drupal_get_path('module', 'privatemsg') . '/styles/privatemsg-view.base.css',
  610. drupal_get_path('module', 'privatemsg') . '/styles/privatemsg-view.theme.css',
  611. ),
  612. );
  613. // This forces the form to be rebuilt instead of being submitted.
  614. $form_state['rebuild'] = TRUE;
  615. }
  616. /**
  617. * Submit callback for the privatemsg_new form.
  618. */
  619. function privatemsg_new_submit($form, &$form_state) {
  620. $message = $form_state['validate_built_message'];
  621. field_attach_submit('privatemsg_message', $message, $form, $form_state);
  622. // Format each recipient.
  623. $recipient_names = array();
  624. foreach ($message->recipients as $recipient) {
  625. $recipient_names[] = privatemsg_recipient_format($recipient);
  626. }
  627. try {
  628. $message = _privatemsg_send($message);
  629. _privatemsg_handle_recipients($message->mid, $message->recipients);
  630. drupal_set_message(t('A message has been sent to !recipients.', array('!recipients' => implode(', ', $recipient_names))));
  631. // Only redirect on new threads.
  632. if ($message->mid == $message->thread_id || variable_get('privatemsg_default_redirect_reply', FALSE)) {
  633. $redirect = variable_get('privatemsg_default_redirect', '<new-message>');
  634. if ($redirect == '<new-message>' || (!empty($_REQUEST['destination']) && $_REQUEST['destination'] == '[new-message]')) {
  635. if (!empty($_REQUEST['destination']) && $_REQUEST['destination'] == '[new-message]') {
  636. // Remove GET param so that drupal_goto() uses the redirect from
  637. // $form_state.
  638. unset($_GET['destination']);
  639. }
  640. // Forward to the new message in the thread.
  641. $form_state['redirect'] = array('messages/view/' . $message->thread_id, array('fragment' => 'privatemsg-mid-' . $message->mid));
  642. }
  643. elseif (!empty($redirect)) {
  644. $form_state['redirect'] = $redirect;
  645. }
  646. }
  647. }
  648. catch (Exception $e) {
  649. if (error_displayable()) {
  650. require_once DRUPAL_ROOT . '/includes/errors.inc';
  651. $variables = _drupal_decode_exception($e);
  652. drupal_set_message(t('Failed to send a message to !recipients. %type: !message in %function (line %line of %file).', array('!recipients' => implode(', ', $recipient_names)) + $variables), 'error');
  653. }
  654. else {
  655. drupal_set_message(t('Failed to send a message to !recipients. Contact your site administrator.', array('!recipients' => implode(', ', $recipient_names))), 'error');
  656. }
  657. }
  658. }
  659. /**
  660. * Menu callback for messages/undo/action.
  661. *
  662. * This function will test if an undo callback is stored in SESSION and execute it.
  663. */
  664. function privatemsg_undo_action() {
  665. // Check if a undo callback for that user exists.
  666. if (isset($_SESSION['privatemsg']['undo callback']) && is_array($_SESSION['privatemsg']['undo callback'])) {
  667. $undo = $_SESSION['privatemsg']['undo callback'];
  668. // If the defined undo callback exists, execute it
  669. if (isset($undo['function']) && isset($undo['args'])) {
  670. // Load the user object.
  671. if (isset($undo['args']['account']) && $undo['args']['account'] > 0) {
  672. $undo['args']['account'] = user_load((int)$undo['args']['account']);
  673. }
  674. call_user_func_array($undo['function'], $undo['args']);
  675. }
  676. }
  677. // Return back to the site defined by the destination GET param.
  678. drupal_goto();
  679. }
  680. /**
  681. * Return autocomplete results for usernames.
  682. *
  683. * Prevents usernames from being used and/or suggested twice.
  684. */
  685. function privatemsg_autocomplete($string) {
  686. $names = array();
  687. // 1: Parse $string and build list of valid user names.
  688. $fragments = explode(',', $string);
  689. foreach ($fragments as $name) {
  690. if ($name = trim($name)) {
  691. $names[$name] = $name;
  692. }
  693. }
  694. // 2: Find the next user name suggestion.
  695. $fragment = array_pop($names);
  696. $matches = array();
  697. if (!empty($fragment)) {
  698. $remaining = 10;
  699. $types = privatemsg_recipient_get_types();
  700. foreach ($types as $name => $type) {
  701. if (isset($type['autocomplete']) && is_callable($type['autocomplete']) && privatemsg_recipient_access($name, 'write')) {
  702. $function = $type['autocomplete'];
  703. $return = $function($fragment, $names, $remaining, $name);
  704. if (is_array($return) && !empty($return)) {
  705. $matches = array_merge($matches, $return);
  706. }
  707. $remaining = 10 - count($matches);
  708. if ($remaining <= 0) {
  709. break;
  710. }
  711. }
  712. }
  713. }
  714. // Allow modules to alter the autocomplete list.
  715. drupal_alter('privatemsg_autocomplete', $matches, $names, $fragment);
  716. // Format the suggestions.
  717. $themed_matches = array();
  718. foreach ($matches as $key => $match) {
  719. $themed_matches[$key] = privatemsg_recipient_format($match, array('plain' => TRUE));
  720. }
  721. // Check if there are any duplicates.
  722. if (count(array_unique($themed_matches)) != count($themed_matches)) {
  723. // Loop over matches, look for duplicates of each one.
  724. foreach ($themed_matches as $themed_match) {
  725. $duplicate_keys = array_keys($themed_matches, $themed_match);
  726. if (count($duplicate_keys) > 1) {
  727. // There are duplicates, make them unique.
  728. foreach ($duplicate_keys as $duplicate_key) {
  729. // Reformat them with unique argument.
  730. $themed_matches[$duplicate_key] = privatemsg_recipient_format($matches[$duplicate_key], array('plain' => TRUE, 'unique' => TRUE));
  731. }
  732. }
  733. }
  734. }
  735. // Prefix the matches and convert them to the correct form for the
  736. // autocomplete.
  737. $prefix = count($names) ? implode(", ", $names) . ", " : '';
  738. $suggestions = array();
  739. foreach ($themed_matches as $match) {
  740. $suggestions[$prefix . $match . ', '] = $match;
  741. }
  742. // convert to object to prevent drupal bug, see http://drupal.org/node/175361
  743. drupal_json_output((object)$suggestions);
  744. }
  745. /**
  746. * Menu callback for viewing a thread.
  747. *
  748. * @param $thread
  749. * A array containing all information about a specific thread, generated by
  750. * privatemsg_thread_load().
  751. * @return
  752. * The page content.
  753. *
  754. * @see privatemsg_thread_load()
  755. */
  756. function privatemsg_view($thread) {
  757. drupal_set_title($thread['subject-tokenized']);
  758. $content = array(
  759. '#thread' => $thread,
  760. );
  761. if ($thread['to'] != $thread['message_count'] || !empty($thread['start'])) {
  762. // Generate paging links.
  763. $older = '';
  764. if (isset($thread['older_start'])) {
  765. $options = array(
  766. 'query' => array('start' => $thread['older_start']),
  767. 'attributes' => array(
  768. 'title' => t('Display older messages'),
  769. ),
  770. );
  771. $older = l(t('<<'), 'messages/view/' . $thread['thread_id'], $options);
  772. }
  773. $newer = '';
  774. if (isset($thread['newer_start'])) {
  775. $options = array(
  776. 'query' => array('start' => $thread['newer_start']),
  777. 'attributes' => array(
  778. 'title' => t('Display newer messages'),
  779. ),
  780. );
  781. $newer = l(t('>>'), 'messages/view/' . $thread['thread_id'], $options);
  782. }
  783. $substitutions = array('@from' => $thread['from'], '@to' => $thread['to'], '@total' => $thread['message_count'], '!previous_link' => $older, '!newer_link' => $newer);
  784. $title = t('!previous_link Displaying messages @from - @to of @total !newer_link', $substitutions);
  785. $content['pager'] = array(
  786. '#markup' => trim($title),
  787. '#prefix' => '<div class="privatemsg-view-pager">',
  788. '#suffix' => '</div>',
  789. '#weight' => 3,
  790. );
  791. }
  792. // Render the participants.
  793. $content['participants'] = array(
  794. '#markup' => theme('privatemsg_recipients', array('thread' => $thread)),
  795. '#weight' => -5,
  796. '#attached' => array(
  797. 'css' => array(
  798. drupal_get_path('module', 'privatemsg') . '/styles/privatemsg-recipients.css',
  799. ),
  800. ),
  801. );
  802. // Render the messages.
  803. $content['messages']['#weight'] = 0;
  804. $i = 1;
  805. $count = count($thread['messages']);
  806. foreach ($thread['messages'] as $pmid => $message) {
  807. // Set message as read and theme it.
  808. // Add CSS classes.
  809. $message->classes = array('privatemsg-message', 'privatemsg-message-' . $i, $i % 2 == 1 ? 'privatemsg-message-even' : 'privatemsg-message-odd');
  810. if (!empty($message->is_new)) {
  811. // Mark message as read.
  812. privatemsg_message_change_status($pmid, PRIVATEMSG_READ, $thread['user']);
  813. $message->classes[] = 'privatemsg-message-new';
  814. }
  815. if ($i == 1) {
  816. $message->classes[] = 'privatemsg-message-first';
  817. }
  818. if ($i == $count) {
  819. $message->classes[] = 'privatemsg-message-last';
  820. }
  821. $i++;
  822. $content['messages'][$pmid] = array(
  823. '#markup' => theme('privatemsg_view', array('message' => $message)),
  824. '#attached' => array(
  825. 'css' => array(
  826. drupal_get_path('module', 'privatemsg') . '/styles/privatemsg-view.base.css',
  827. drupal_get_path('module', 'privatemsg') . '/styles/privatemsg-view.theme.css',
  828. ),
  829. ),
  830. );
  831. }
  832. // Display the reply form if user is allowed to use it.
  833. if (privatemsg_user_access('write privatemsg') || privatemsg_user_access('reply only privatemsg')) {
  834. $content['reply'] = drupal_get_form('privatemsg_form_reply', $thread);
  835. $content['reply']['#weight'] = 5;
  836. }
  837. // Check after calling the privatemsg_new form so that this message is only
  838. // displayed when we are not sending a message.
  839. if ($thread['read_all']) {
  840. // User has permission to read all messages AND is not a participant of the current thread.
  841. drupal_set_message(t('This conversation is being viewed with escalated privileges and may not be the same as shown to normal users.'), 'warning');
  842. }
  843. drupal_alter('privatemsg_view', $content);
  844. return $content;
  845. }
  846. /**
  847. * Batch processing function for rebuilding the index.
  848. */
  849. function privatemsg_load_recipients($mid, $recipient, &$context) {
  850. // Get type information.
  851. $type = privatemsg_recipient_get_type($recipient->type);
  852. // First run, initialize sandbox.
  853. if (!isset($context['sandbox']['current_offset'])) {
  854. $context['sandbox']['current_offset'] = 0;
  855. $count_function = $type['count'];
  856. $context['sandbox']['count'] = $count_function($recipient);
  857. }
  858. // Fetch the 10 next recipients.
  859. $load_function = $type['generate recipients'];
  860. $uids = $load_function($recipient, 10, $context['sandbox']['current_offset']);
  861. if (!empty($uids)) {
  862. foreach ($uids as $uid) {
  863. privatemsg_message_change_recipient($mid, $uid, 'hidden');
  864. }
  865. $context['sandbox']['current_offset'] += 10;
  866. // Set finished based on sandbox.
  867. $context['finished'] = empty($context['sandbox']['count']) ? 1 : ($context['sandbox']['current_offset'] / $context['sandbox']['count']);
  868. }
  869. else {
  870. // If no recipients were returned, mark as finished too.
  871. $context['sandbox']['finished'] = 1;
  872. }
  873. // If we are finished, mark the recipient as read.
  874. if ($context['finished'] >= 1) {
  875. db_update('pm_index')
  876. ->fields(array('is_new' => PRIVATEMSG_READ))
  877. ->condition('mid', $mid)
  878. ->condition('recipient', $recipient->recipient)
  879. ->condition('type', $recipient->type)
  880. ->execute();
  881. }
  882. }

Functions

Namesort descending Description
privatemsg_autocomplete Return autocomplete results for usernames.
privatemsg_check_format_access After build callback; Hide format widget if user doesn't have permission.
privatemsg_delete
privatemsg_delete_submit
privatemsg_disable_maxlength Remove the maxlength attribute from a field.
privatemsg_form_reply Form builder function; Write a reply to a thread.
privatemsg_list
privatemsg_list_js AJAX callback to return the form again.
privatemsg_list_page List messages.
privatemsg_list_submit Process privatemsg_list form submissions.
privatemsg_load_recipients Batch processing function for rebuilding the index.
privatemsg_new Form builder function; Write a new private message.
privatemsg_new_preview
privatemsg_new_submit Submit callback for the privatemsg_new form.
privatemsg_new_validate
privatemsg_undo_action Menu callback for messages/undo/action.
privatemsg_view Menu callback for viewing a thread.
_privatemsg_action_form Returns a form which handles and displays thread actions.
_privatemsg_form_base_fields Returns the common fields of the reply and new form.
_privatemsg_get_allowed_recipients Check if the current user is allowed to write these recipients.