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