Projet

Général

Profil

Paste
Télécharger (31,6 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / privatemsg / privatemsg.pages.inc @ 87dbc3bf

1
<?php
2

    
3
/**
4
 * @file
5
 * User menu callbacks for Privatemsg.
6
 */
7

    
8

    
9
/**
10
 * Returns a form which handles and displays thread actions.
11
 *
12
 * Additional actions can be added with the privatemsg_thread_operations hook.
13
 * It is also possible to extend this form with additional buttons or other
14
 * elements, in that case, the definitions in the above hook need no label tag,
15
 * instead, the submit button key needs to match with the key of the operation.
16
 *
17
 * @see hook_privatemsg_thread_operations()
18
 *
19
 * @return
20
 *   The FAPI definitions for the thread action form.
21
 */
22
function _privatemsg_action_form($type) {
23
  $form = array(
24
      '#prefix'      => '<div class="container-inline">',
25
      '#suffix'      => '</div>',
26
      '#weight'      => -5,
27
  );
28
  // Display all operations which have a label.
29
  $operations = module_invoke_all('privatemsg_thread_operations', $type);
30
  drupal_alter('privatemsg_thread_operations', $operations, $type);
31
  foreach ($operations as $operation => $array) {
32
    if (!empty($array['button'])) {
33
      $form[$operation] = array(
34
        '#type'   => 'submit',
35
        '#value'  => $array['label'],
36
        '#ajax' => array(
37
          'callback' => 'privatemsg_list_js',
38
          'wrapper' => 'privatemsg-list-form',
39
          'effect' => 'fade',
40
        ),
41
      );
42
    }
43
    elseif (isset($array['label'])) {
44
      $options[$operation] = $array['label'];
45
    }
46
  }
47
  if (!empty($options)) {
48
    array_unshift($options, t('Actions...'));
49
    $form['operation'] = array(
50
      '#type'          => 'select',
51
      '#options'       => $options,
52
      '#ajax' => array(
53
        'callback' => 'privatemsg_list_js',
54
        'wrapper' => 'privatemsg-list-form',
55
        'effect' => 'fade',
56
      ),
57
      '#executes_submit_callback' => TRUE,
58
    );
59
    $form['submit'] = array(
60
      '#type'       => 'submit',
61
      '#value'      => t('Execute'),
62
      '#attributes' => array('class' => array('form-item')),
63
      '#states' => array(
64
        'visible' => array(
65
          // This is never true, button is always hidden when JS is enabled.
66
          ':input[name=operation]' => array('value' => 'fake'),
67
        ),
68
      ),
69
    );
70
  }
71
  return $form;
72
}
73

    
74
/**
75
 * AJAX callback to return the form again.
76
 */
77
function privatemsg_list_js($form, $form_state) {
78
  return $form['updated'];
79
}
80

    
81
function privatemsg_delete($form, $form_state, $thread, $message) {
82
  $form['pmid'] = array(
83
    '#type' => 'value',
84
    '#value' => $message->mid,
85
  );
86
  $form['delete_destination'] = array(
87
    '#type' => 'value',
88
    '#value' => count($thread['messages']) > 1 ? 'messages/view/' . $message->thread_id : 'messages',
89
  );
90

    
91
  if (privatemsg_user_access('read all private messages')) {
92
    $form['delete_options'] = array(
93
      '#type' => 'checkbox',
94
      '#title' => t('Delete this message for all users?'),
95
      '#description' => t('Tick the box to delete the message for all users.'),
96
      '#default_value' => FALSE,
97
    );
98
  }
99
  return confirm_form($form,
100
    t('Are you sure you want to delete this message?'),
101
    isset($_GET['destination']) ? $_GET['destination'] : 'messages/view/' . $message->thread_id,
102
    t('This action cannot be undone.'),
103
    t('Delete'),
104
    t('Cancel')
105
  );
106
}
107

    
108
function privatemsg_delete_submit($form, &$form_state) {
109
  global $user;
110
  $account = clone $user;
111

    
112
  if ($form_state['values']['confirm']) {
113
    if (isset($form_state['values']['delete_options']) && $form_state['values']['delete_options']) {
114
      privatemsg_message_change_delete($form_state['values']['pmid'], 1);
115
      drupal_set_message(t('Message has been deleted for all users.'));
116
    }
117
    else {
118
      privatemsg_message_change_delete($form_state['values']['pmid'], 1, $account);
119
      drupal_set_message(t('Message has been deleted.'));
120
    }
121
  }
122
  $form_state['redirect'] = $form_state['values']['delete_destination'];
123
}
124

    
125
/**
126
 * List messages.
127
 *
128
 * @param $argument
129
 *   An argument to pass through to the query builder.
130
 * @param $uid
131
 *   User id messages of another user should be displayed
132
 *
133
 * @return
134
 *   Form array
135
 */
136
function privatemsg_list_page($argument = 'list', $uid = NULL) {
137
  global $user;
138

    
139
  // Setting default behavior...
140
  $account = $user;
141
  // Because uid is submitted by the menu system, it's a string not a integer.
142
  if ((int)$uid > 0 && $uid != $user->uid) {
143
    // Trying to view someone else's messages...
144
    if (!$account_check = user_load($uid)) {
145
      return MENU_NOT_FOUND;
146
    }
147
    if (!privatemsg_user_access('read all private messages')) {
148
      return MENU_ACCESS_DENIED;
149
    }
150
    // Has rights and user_load return an array so user does exist
151
    $account = $account_check;
152
  }
153

    
154
  return drupal_get_form('privatemsg_list', $argument, $account);
155
}
156

    
157
function privatemsg_list($form, &$form_state, $argument, $account) {
158
  drupal_add_css(drupal_get_path('module', 'privatemsg') . '/styles/privatemsg-list.css');
159

    
160
  // If this is an AJAX request, update $_GET['q'] so that table sorting and
161
  // similar links are using the correct base path.
162
  if ($_GET['q'] == 'system/ajax') {
163
    $q = 'messages';
164
    if (!empty($argument)) {
165
      $q .= '/' . $argument;
166
    }
167
    $_GET['q'] = $q;
168
  }
169

    
170
  // Load the table columns.
171
  $columns = array_merge(array('subject', 'last_updated'), array_filter(variable_get('privatemsg_display_fields', array('participants'))));
172

    
173
  // Load the themed list headers based on the available data.
174
  $headers = _privatemsg_list_headers($columns);
175

    
176
  $form = array(
177
    '#list_argument' => $argument,
178
    '#submit' => array('privatemsg_list_submit'),
179
    'updated' => array(
180
      '#prefix' => '<div id="privatemsg-list-form">',
181
      '#suffix' => '</div>',
182
    ),
183
  );
184
  $form['updated']['list'] = array(
185
    '#type' => 'tableselect',
186
    '#header' => $headers,
187
    '#options' => array(),
188
    '#attributes' => array('class' => array('privatemsg-list')),
189
    '#empty' => t('No messages available.'),
190
    '#weight' => 5,
191
    '#pre_render' => array('_privatemsg_list_thread'),
192
  );
193

    
194
  $query = _privatemsg_assemble_query('list', $account, $argument);
195
  $i = 0;
196
  foreach ($query->execute() as $row) {
197
    // Store the raw row data.
198
    $form['updated']['list']['#options'][$row->thread_id] = (array)$row;
199
    // Tableselect sorts the options, set a weight so that the order doesn't get
200
    // changed.
201
    $form['updated']['list']['#options'][$row->thread_id]['#weight'] = $i++;
202
  }
203

    
204
  if (!empty($form['updated']['list']['#options'])) {
205
    $form['updated']['actions'] = _privatemsg_action_form($argument);
206
  }
207

    
208
  // Save the currently active account, used for actions.
209
  $form['account'] = array('#type' => 'value', '#value' => $account);
210

    
211
  // Define checkboxes, pager and theme
212
  $form['updated']['pager'] = array('#markup' => theme('pager'), '#weight' => 20);
213
  return $form;
214
}
215

    
216
/**
217
 * Process privatemsg_list form submissions.
218
 *
219
 * Execute the chosen action on the selected messages. This function is
220
 * based on node_admin_nodes_submit().
221
 */
222
function privatemsg_list_submit($form, &$form_state) {
223
  // Load all available operation definitions.
224
  $operations = module_invoke_all('privatemsg_thread_operations', $form['#list_argument']);
225
  drupal_alter('privatemsg_thread_operations', $operations, $form['#list_argument']);
226

    
227
  // Default "default" operation, which won't do anything.
228
  $operation = array('callback' => 0);
229

    
230
  // Check if a valid operation has been submitted.
231
  if (isset($form_state['values']['operation']) && isset($operations[$form_state['values']['operation']])) {
232
    $operation = $operations[$form_state['values']['operation']];
233
  }
234

    
235
  if (!empty($form_state['values']['op'])) {
236
    // Load all keys where the value is the current op.
237
    $keys = array_keys($form_state['values'], $form_state['values']['op']);
238

    
239
    // Loop over them and detect if a matching button was pressed.
240
    foreach ($keys as $key) {
241
      if ($key != 'op' && isset($operations[$key])) {
242
        $operation = $operations[$key];
243
      }
244
    }
245
  }
246

    
247
  // Only execute something if we have a valid callback and at least one checked thread.
248
  if (!empty($operation['callback'])) {
249
    // Hack to fix destination during ajax requests.
250
    if (isset($form_state['input']['ajax_page_state'])) {
251
      $destination = 'messages';
252
      if (!empty($form['#list_argument'])) {
253
        $destination .= '/' . $form['#list_argument'];
254
      }
255
      $_GET['destination'] = $destination;
256
    }
257
    privatemsg_operation_execute($operation, $form_state['values']['list'], $form_state['values']['account']);
258
  }
259

    
260
  $form_state['rebuild'] = TRUE;
261
  $form_state['input'] = array();
262
}
263

    
264

    
265
function privatemsg_new($form, &$form_state, $recipients = array(), $subject = '', $thread_id = NULL, $read_all = FALSE) {
266
  global $user;
267

    
268
  $recipients_plain = '';
269
  $body      = '';
270

    
271
  // convert recipients to array of user objects
272
  $unique = FALSE;
273
  if (!empty($recipients) && is_string($recipients) || is_int($recipients)) {
274
    $unique = TRUE;
275
    $recipients = _privatemsg_generate_user_array($recipients);
276
  }
277
  elseif (is_object($recipients)) {
278
    $recipients = array($recipients);
279
  }
280
  elseif (empty($recipients) && is_string($recipients)) {
281
    $recipients = array();
282
  }
283

    
284
  $usercount = 0;
285
  $to = array();
286
  $to_plain = array();
287
  $blocked_messages = array();
288
  foreach ($recipients as $recipient) {
289
    // Allow to pass in normal user objects.
290
    if (empty($recipient->type)) {
291
      $recipient->type = 'user';
292
      $recipient->recipient = $recipient->uid;
293
    }
294
    if ($recipient->type == 'hidden') {
295
      continue;
296
    }
297
    if (isset($to[privatemsg_recipient_key($recipient)])) {
298
      // We already added the recipient to the list, skip him.
299
      continue;
300
    }
301
    if (!privatemsg_recipient_access($recipient->type, 'write', $recipient)) {
302
      // User does not have access to write to this recipient, continue.
303
      continue;
304
    }
305

    
306
    // Check if another module is blocking the sending of messages to the recipient by current user.
307
    $user_blocked = module_invoke_all('privatemsg_block_message', $user, array(privatemsg_recipient_key($recipient) => $recipient), array('thread_id' => $thread_id));
308
    if (!count($user_blocked) <> 0 && $recipient->recipient) {
309
      if ($recipient->type == 'user' && $recipient->recipient == $user->uid) {
310
        $usercount++;
311
        // Skip putting author in the recipients list for now.
312
        continue;
313
      }
314
      $to[privatemsg_recipient_key($recipient)] = privatemsg_recipient_format($recipient);
315
      $to_plain[privatemsg_recipient_key($recipient)] = privatemsg_recipient_format($recipient, array('plain' => TRUE, 'unique' => $unique));
316
    }
317
    else {
318
      // Store blocked messages. These are only displayed if all recipients
319
      // are blocked.
320
      $first_reason = reset($user_blocked);
321
      $blocked_messages[] = $first_reason['message'];
322
    }
323
  }
324

    
325
  if (empty($to) && $usercount >= 1 && empty($blocked_messages)) {
326
    // 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.
327
    $to['user_' . $user->uid] = privatemsg_recipient_format($user);
328
    $to_plain['user_' . $user->uid] = privatemsg_recipient_format($user, array('plain' => TRUE));
329
  }
330

    
331
  // Subject has / encoded twice if clean urls are enabled to get it through
332
  // mod_rewrite and the menu system. Decode it once more.
333
  $subject = str_replace('%2F', '/', $subject);
334

    
335
  if (!empty($to)) {
336
    $recipients_plain = implode(', ', $to_plain);
337
  }
338
  if (isset($form_state['values'])) {
339
    if (isset($form_state['values']['recipient'])) {
340
      $recipients_plain = $form_state['values']['recipient'];
341
    }
342
    if (isset($form_state['values']['subject'])) {
343
      $subject   = $form_state['values']['subject'];
344
    }
345
    if (isset($form_state['values']['body'])) {
346
      $body      = $form_state['values']['body'];
347
    }
348
  }
349
  if (!$thread_id && !empty($recipients_plain)) {
350
    drupal_set_title(t('Write new message to @recipient', array('@recipient' => $recipients_plain)));
351
  }
352
  elseif (!$thread_id) {
353
    drupal_set_title(t('Write new message'));
354
  }
355

    
356
  $form = array(
357
    '#access'   => (privatemsg_user_access('write privatemsg') || privatemsg_user_access('reply only privatemsg')),
358
  );
359
  if (isset($form_state['privatemsg_preview'])) {
360

    
361
    $preview_subject = '';
362
    // Only display subject on preview for new messages.
363
    if (empty($form_state['validate_built_message']->thread_id)) {
364
      $preview_subject = check_plain($form_state['validate_built_message']->subject);
365
      // If message has tokens, replace them.
366
      if ($form_state['validate_built_message']->has_tokens) {
367
        $preview_subject = privatemsg_token_replace($preview_subject, array('privatemsg_message' => $form_state['validate_built_message']), array('sanitize' => TRUE, 'privatemsg-show-span' => FALSE));
368
      }
369
    }
370

    
371
    $form['message_header'] = array(
372
      '#type' => 'fieldset',
373
      '#title' => !empty($preview_subject) ? $preview_subject : t('Preview'),
374
      '#attributes' => array('class' => array('preview')),
375
      '#weight' => -20,
376
    );
377
    $form['message_header']['message_preview'] = $form_state['privatemsg_preview'];
378
  }
379
  $form['author'] = array(
380
    '#type' => 'value',
381
    '#value' => $user,
382
  );
383
  if (is_null($thread_id)) {
384
    $description_array = array();
385
    foreach (privatemsg_recipient_get_types() as $name => $type) {
386
      if (privatemsg_recipient_access($name, 'write')) {
387
        $description_array[] = $type['description'];
388
      }
389
    }
390
    $description = t('Enter the recipient, separate recipients with commas.');
391
    $description .= theme('item_list', array('items' => $description_array));
392

    
393
    $form['recipient'] = array(
394
      '#type'               => 'textfield',
395
      '#title'              => t('To'),
396
      '#description'        => $description,
397
      '#default_value'      => $recipients_plain,
398
      '#required'           => TRUE,
399
      '#weight'             => -10,
400
      '#size'               => 50,
401
      '#autocomplete_path'  => 'messages/autocomplete',
402
      // Do not hardcode #maxlength, make it configurable by number of recipients, not their name length.
403
    );
404
  }
405
  $form['subject'] = array(
406
    '#type'               => 'textfield',
407
    '#title'              => t('Subject'),
408
    '#size'               => 50,
409
    '#maxlength'          => 255,
410
    '#default_value'      => $subject,
411
    '#weight'             => -5,
412
  );
413

    
414
  // The input filter widget looses the format during preview, specify it
415
  // explicitly.
416
  if (isset($form_state['values']) && array_key_exists('format', $form_state['values'])) {
417
    $format = $form_state['values']['format'];
418
  }
419
  $form['body'] = array(
420
    '#type'               => 'text_format',
421
    '#title'              => t('Message'),
422
    '#rows'               => 6,
423
    '#weight'             => -3,
424
    '#default_value'      => $body,
425
    '#resizable'          => TRUE,
426
    '#format'             => isset($format) ? $format : NULL,
427
    '#after_build'        => array('privatemsg_check_format_access'),
428
  );
429
  if (privatemsg_user_access('use tokens in privatemsg') && module_exists('token')) {
430
    $form['token'] = array(
431
      '#type' => 'fieldset',
432
      '#title' => t('Token browser'),
433
      '#weight' => -1,
434
      '#collapsible' => TRUE,
435
      '#collapsed' => TRUE,
436
    );
437
    $form['token']['browser'] = array(
438
      '#theme' => 'token_tree',
439
      '#token_types' => array('privatemsg_message'),
440
    );
441
  }
442
  $form['actions'] = array('#type' => 'actions');
443
  if (variable_get('privatemsg_display_preview_button', FALSE)) {
444
    $form['actions']['preview'] = array(
445
      '#type'               => 'submit',
446
      '#value'              => t('Preview message'),
447
      '#submit'             => array('privatemsg_new_preview'),
448
      '#weight'             => 48,
449
    );
450
  }
451
  $form['actions']['submit'] = array(
452
    '#type'               => 'submit',
453
    '#value'              => t('Send message'),
454
    '#weight'             => 49,
455
  );
456
  $url = 'messages';
457
  $title = t('Cancel');
458
  if (isset($_REQUEST['destination'])) {
459
    $url = $_REQUEST['destination'];
460
  }
461
  elseif (!is_null($thread_id)) {
462
    $url = $_GET['q'];
463
    $title = t('Clear');
464
  }
465

    
466
  $form['actions']['cancel'] = array(
467
    '#markup'              => l($title, $url, array('attributes' => array('id' => 'edit-cancel'))),
468
    '#weight'             => 50,
469
  );
470

    
471
  if (!is_null($thread_id)) {
472
    $form['thread_id'] = array(
473
      '#type' => 'value',
474
      '#value' => $thread_id,
475
    );
476
    $form['subject'] = array(
477
      '#type' => 'value',
478
      '#default_value' => $subject,
479
    );
480
    $form['reply'] = array(
481
      '#markup' => '<h2 class="privatemsg-reply">' . t('Reply') . '</h2>',
482
      '#weight' => -10,
483
    );
484
    if (empty($to)) {
485
      // If there are no valid recipients, hide all visible parts of the form.
486
      foreach (element_children($form) as $element) {
487
        $form[$element]['#access'] = FALSE;
488
      }
489

    
490
      // Display a message if some users are blocked.
491
      if (!empty($blocked_messages)) {
492
        $form['blocked'] = array(
493
          '#theme' => 'item_list',
494
          '#items' => $blocked_messages,
495
          '#title' => t('You can not reply to this conversation because all recipients are blocked.'),
496
        );
497
      }
498
    }
499
  }
500
  // Only set read all if it is a boolean TRUE. It might also be an integer set
501
  // through the URL.
502
  $form['read_all'] = array(
503
    '#type'  => 'value',
504
    '#value' => $read_all === TRUE,
505
  );
506
  // Attach field widgets.
507
  $message = (object) array();
508
  if (isset($form_state['validate_built_message'])) {
509
    $message = $form_state['validate_built_message'];
510
  }
511

    
512
  // If a module (e.g. OG) adds a validate or submit callback to the form in
513
  // field_attach_form, the form system will not add ours automatically
514
  // anymore. Therefore, explicitly adding them here.
515
  $form['#submit'] = array('privatemsg_new_submit');
516
  $form['#validate'] = array('privatemsg_new_validate');
517

    
518
  field_attach_form('privatemsg_message', $message, $form, $form_state);
519
  return $form;
520
}
521

    
522
/**
523
 * After build callback; Hide format widget if user doesn't have permission.
524
 */
525
function privatemsg_check_format_access($element) {
526
  if (isset($element['format'])) {
527
    $element['format']['#access'] = privatemsg_user_access('select text format for privatemsg');
528
  }
529
  return $element;
530
}
531

    
532
function privatemsg_new_validate($form, &$form_state) {
533
  // The actual message that is being sent, we create this during validation and
534
  // pass to submit to send out.
535
  $message = (object)$form_state['values'];
536
  $message->mid       = 0;
537
  $message->format    = $message->body['format'];
538
  $message->body      = $message->body['value'];
539
  $message->timestamp = REQUEST_TIME;
540
  // Avoid subjects which only consist of a space as these can not be clicked.
541
  $message->subject = trim($message->subject);
542

    
543
  $trimmed_body = trim(truncate_utf8(strip_tags($message->body), 50, TRUE, TRUE));
544
  if (empty($message->subject) && !empty($trimmed_body)) {
545
    $message->subject = $trimmed_body;
546
  }
547
  // Only parse the user string for a new thread.
548
  if (!isset($message->thread_id)) {
549
    list($message->recipients, $invalid, $duplicates, $denieds) = _privatemsg_parse_userstring($message->recipient);
550
  }
551
  else {
552
    // Load participants. Limit recipients to visible unless read_all is TRUE.
553
    $message->recipients = _privatemsg_load_thread_participants($message->thread_id, $message->read_all ? FALSE : $message->author);
554
  }
555

    
556
  if (!empty($invalid)) {
557
    // Display information about invalid recipients.
558
    drupal_set_message(t('The following users will not receive this private message: @invalid.', array('@invalid' => implode(", ", $invalid))), 'error');
559
  }
560
  if (!empty($denieds)) {
561
    // Display information about denied recipients.
562
    drupal_set_message(t('You do not have access to write these recipients: @denieds.', array('@denieds' => implode(", ", $denieds))), 'error');
563
  }
564

    
565
  if (!empty($duplicates)) {
566
    // Add JS and CSS to allow choosing the recipient.
567
    drupal_add_js(drupal_get_path('module', 'privatemsg') . '/privatemsg-alternatives.js');
568

    
569
    // Display information about recipients that couldn't be identified
570
    // uniquely.
571
    $js_duplicates = array();
572
    foreach ($duplicates as $string => $duplicate) {
573
      $alternatives = array();
574
      foreach ($duplicate as $match) {
575
        $formatted_match = privatemsg_recipient_format($match, array('plain' => TRUE, 'unique' => TRUE));
576
        $js_duplicates[$formatted_match] = $string;
577
        $alternatives[] = '<span class="privatemsg-recipient-alternative">' . $formatted_match . '</span>';
578
      }
579
      // Build a formatted list of possible recipients.
580
      $alternatives = theme('item_list', array('items' => $alternatives, 'attributes' => array('class' => array('action-links'))));
581
      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>');
582
    }
583

    
584
    // Also make that information available to the javascript replacement code.
585
    drupal_add_js(array('privatemsg_duplicates' => $js_duplicates), 'setting');
586
  }
587

    
588
  $validated = _privatemsg_validate_message($message, TRUE);
589
  foreach ($validated['messages'] as $type => $texts) {
590
    foreach ($texts as $text) {
591
      drupal_set_message($text, $type);
592
    }
593
  }
594
  $form_state['validate_built_message'] = $message;
595
}
596

    
597
function privatemsg_new_preview($form, &$form_state) {
598
  $message = $form_state['validate_built_message'];
599
  // Execute submit hook, removes empty fields.
600
  field_attach_submit('privatemsg_message', $message, $form, $form_state);
601
  // Load information attached to the message. Use an internal function
602
  // to avoid the internal field cache.
603
  _field_invoke_multiple('load', 'privatemsg_message', array($message->mid => $message));
604
  $form_state['privatemsg_preview'] = array(
605
    '#markup' => theme('privatemsg_view', array('message' => $message)),
606
  );
607
  // This forces the form to be rebuilt instead of being submitted.
608
  $form_state['rebuild'] = TRUE;
609
}
610

    
611

    
612
/**
613
 * Submit callback for the privatemsg_new form.
614
 */
615
function privatemsg_new_submit($form, &$form_state) {
616
  $message = $form_state['validate_built_message'];
617
  field_attach_submit('privatemsg_message', $message, $form, $form_state);
618

    
619
  // Format each recipient.
620
  $recipient_names = array();
621
  foreach ($message->recipients as $recipient) {
622
    $recipient_names[] = privatemsg_recipient_format($recipient);
623
  }
624
  try {
625
    $message = _privatemsg_send($message);
626
    _privatemsg_handle_recipients($message->mid, $message->recipients);
627
    drupal_set_message(t('A message has been sent to !recipients.', array('!recipients' => implode(', ', $recipient_names))));
628
    // Only redirect on new threads.
629
    if ($message->mid == $message->thread_id || variable_get('privatemsg_default_redirect_reply', FALSE)) {
630
      $redirect = variable_get('privatemsg_default_redirect', '<new-message>');
631
      if ($redirect == '<new-message>' || (!empty($_REQUEST['destination']) && $_REQUEST['destination'] == '[new-message]')) {
632
        if (!empty($_REQUEST['destination']) && $_REQUEST['destination'] == '[new-message]') {
633
          // Remove GET param so that drupal_goto() uses the redirect from
634
          // $form_state.
635
          unset($_GET['destination']);
636
        }
637
        // Forward to the new message in the thread.
638
        $form_state['redirect'] = array('messages/view/' . $message->thread_id, array('fragment' => 'privatemsg-mid-' . $message->mid));
639
      }
640
      elseif (!empty($redirect)) {
641
        $form_state['redirect'] = $redirect;
642
      }
643
    }
644
  }
645
  catch (Exception $e) {
646
    if (error_displayable()) {
647
      require_once DRUPAL_ROOT . '/includes/errors.inc';
648
      $variables = _drupal_decode_exception($e);
649
      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');
650
    }
651
    else {
652
      drupal_set_message(t('Failed to send a message to !recipients. Contact your site administrator.', array('!recipients' => implode(', ', $recipient_names))), 'error');
653
    }
654
  }
655
}
656

    
657

    
658
/**
659
 * Menu callback for messages/undo/action.
660
 *
661
 * This function will test if an undo callback is stored in SESSION and execute it.
662
 */
663
function privatemsg_undo_action() {
664
  // Check if a undo callback for that user exists.
665
  if (isset($_SESSION['privatemsg']['undo callback']) && is_array($_SESSION['privatemsg']['undo callback'])) {
666
    $undo = $_SESSION['privatemsg']['undo callback'];
667
    // If the defined undo callback exists, execute it
668
    if (isset($undo['function']) && isset($undo['args'])) {
669
      // Load the user object.
670
      if (isset($undo['args']['account']) && $undo['args']['account'] > 0) {
671
        $undo['args']['account'] = user_load((int)$undo['args']['account']);
672
      }
673
      call_user_func_array($undo['function'], $undo['args']);
674
    }
675
  }
676
  // Return back to the site defined by the destination GET param.
677
  drupal_goto();
678
}
679

    
680

    
681
/**
682
 * Return autocomplete results for usernames.
683
 *
684
 * Prevents usernames from being used and/or suggested twice.
685
 */
686
function privatemsg_autocomplete($string) {
687
  $names = array();
688
  // 1: Parse $string and build list of valid user names.
689
  $fragments = explode(',', $string);
690
  foreach ($fragments as $name) {
691
    if ($name = trim($name)) {
692
      $names[$name] = $name;
693
    }
694
  }
695
  // 2: Find the next user name suggestion.
696
  $fragment = array_pop($names);
697
  $matches = array();
698
  if (!empty($fragment)) {
699
    $remaining = 10;
700
    $types = privatemsg_recipient_get_types();
701
    foreach ($types as $name => $type) {
702
      if (isset($type['autocomplete']) && is_callable($type['autocomplete']) && privatemsg_recipient_access($name, 'write')) {
703
        $function = $type['autocomplete'];
704
        $return = $function($fragment, $names, $remaining);
705
        if (is_array($return) && !empty($return)) {
706
          $matches = array_merge($matches, $return);
707
        }
708
        $remaining = 10 - count($matches);
709
        if ($remaining <= 0) {
710
          break;
711
        }
712
      }
713
    }
714
  }
715
  // Allow modules to alter the autocomplete list.
716
  drupal_alter('privatemsg_autocomplete', $matches, $names, $fragment);
717

    
718
  // Format the suggestions.
719
  $themed_matches = array();
720
  foreach ($matches as $key => $match) {
721
    $themed_matches[$key] = privatemsg_recipient_format($match, array('plain' => TRUE));
722
  }
723

    
724
  // Check if there are any duplicates.
725
  if (count(array_unique($themed_matches)) != count($themed_matches)) {
726
    // Loop over matches, look for duplicates of each one.
727
    foreach ($themed_matches as $themed_match) {
728
      $duplicate_keys = array_keys($themed_matches, $themed_match);
729
      if (count($duplicate_keys) > 1) {
730
        // There are duplicates, make them unique.
731
        foreach ($duplicate_keys as $duplicate_key) {
732
          // Reformat them with unique argument.
733
          $themed_matches[$duplicate_key] = privatemsg_recipient_format($matches[$duplicate_key], array('plain' => TRUE, 'unique' => TRUE));
734
        }
735
      }
736
    }
737
  }
738

    
739
  // Prefix the matches and convert them to the correct form for the
740
  // autocomplete.
741
  $prefix = count($names) ? implode(", ", $names) . ", " : '';
742
  $suggestions = array();
743
  foreach ($themed_matches as $match) {
744
    $suggestions[$prefix . $match . ', '] = $match;
745
  }
746

    
747
  // convert to object to prevent drupal bug, see http://drupal.org/node/175361
748
  drupal_json_output((object)$suggestions);
749
}
750

    
751

    
752
/**
753
 * Menu callback for viewing a thread.
754
 *
755
 * @param $thread
756
 *   A array containing all information about a specific thread, generated by
757
 *   privatemsg_thread_load().
758
 * @return
759
 *   The page content.
760
 *
761
 * @see privatemsg_thread_load()
762
 */
763
function  privatemsg_view($thread) {
764
  drupal_set_title($thread['subject']);
765

    
766
  $content = array(
767
    '#thread' => $thread,
768
  );
769

    
770
  if ($thread['to'] != $thread['message_count'] || !empty($thread['start'])) {
771
    // Generate paging links.
772
    $older = '';
773
    if (isset($thread['older_start'])) {
774
      $options = array(
775
        'query' => array('start' => $thread['older_start']),
776
        'title' => t('Display older messages'),
777
      );
778
      $older = l(t('<<'), 'messages/view/' . $thread['thread_id'], $options);
779
    }
780
    $newer = '';
781
    if (isset($thread['newer_start'])) {
782
      $options = array(
783
        'query' => array('start' => $thread['newer_start']),
784
        'title' => t('Display newer messages'),
785
      );
786
      $newer = l(t('>>'), 'messages/view/' . $thread['thread_id'], $options);
787
    }
788
    $substitutions = array('@from' => $thread['from'], '@to' => $thread['to'], '@total' => $thread['message_count'], '!previous_link' => $older, '!newer_link' => $newer);
789
    $title = t('!previous_link Displaying messages @from - @to of @total !newer_link', $substitutions);
790
    $content['pager'] = array(
791
      '#markup'  => trim($title),
792
      '#prefix' => '<div class="privatemsg-view-pager">',
793
      '#suffix' => '</div>',
794
      '#weight' => 3,
795
    );
796
  }
797

    
798
  // Render the participants.
799
  $content['participants'] = array(
800
    '#markup' => theme('privatemsg_recipients', array('thread' => $thread)),
801
    '#weight' => -5
802
  );
803

    
804
  // Render the messages.
805
  $content['messages']['#weight'] = 0;
806
  $i = 1;
807
  $count = count($thread['messages']);
808
  foreach ($thread['messages'] as $pmid => $message) {
809
    // Set message as read and theme it.
810
    // Add CSS classes.
811
    $message->classes = array('privatemsg-message', 'privatemsg-message-' . $i, $i % 2 == 1 ? 'privatemsg-message-even' : 'privatemsg-message-odd');
812
    if (!empty($message->is_new)) {
813
      // Mark message as read.
814
      privatemsg_message_change_status($pmid, PRIVATEMSG_READ, $thread['user']);
815
      $message->classes[] = 'privatemsg-message-new';
816
    }
817

    
818
    if ($i == 1) {
819
      $message->classes[] = 'privatemsg-message-first';
820
    }
821
    if ($i == $count) {
822
      $message->classes[] = 'privatemsg-message-last';
823
    }
824
    $i++;
825
    $content['messages'][$pmid] = array(
826
      '#markup' => theme('privatemsg_view', array('message' => $message)),
827
    );
828
  }
829

    
830
  // Display the reply form if user is allowed to use it.
831
  if (privatemsg_user_access('write privatemsg') || privatemsg_user_access('reply only privatemsg')) {
832
    $content['reply'] = drupal_get_form('privatemsg_new', $thread['participants'], $thread['subject-original'], $thread['thread_id'], $thread['read_all']);
833
    $content['reply']['#weight'] = 5;
834
  }
835
  // Check after calling the privatemsg_new form so that this message is only
836
  // displayed when we are not sending a message.
837
  if ($thread['read_all']) {
838
    // User has permission to read all messages AND is not a participant of the current thread.
839
    drupal_set_message(t('This conversation is being viewed with escalated privileges and may not be the same as shown to normal users.'), 'warning');
840
  }
841

    
842
  drupal_alter('privatemsg_view', $content);
843

    
844
  return $content;
845
}
846

    
847
/**
848
 * Batch processing function for rebuilding the index.
849
 */
850
function privatemsg_load_recipients($mid, $recipient, &$context) {
851
  // Get type information.
852
  $type = privatemsg_recipient_get_type($recipient->type);
853

    
854
  // First run, initialize sandbox.
855
  if (!isset($context['sandbox']['current_offset'])) {
856
    $context['sandbox']['current_offset'] = 0;
857
    $count_function = $type['count'];
858
    $context['sandbox']['count'] = $count_function($recipient);
859
  }
860

    
861
  // Fetch the 10 next recipients.
862
  $load_function = $type['generate recipients'];
863
  $uids = $load_function($recipient, 10, $context['sandbox']['current_offset']);
864

    
865
  if (!empty($uids)) {
866
    foreach ($uids as $uid) {
867
      privatemsg_message_change_recipient($mid, $uid, 'hidden');
868
    }
869

    
870
    $context['sandbox']['current_offset'] += 10;
871
    // Set finished based on sandbox.
872
    $context['finished'] = empty($context['sandbox']['count']) ? 1 : ($context['sandbox']['current_offset'] / $context['sandbox']['count']);
873
  }
874
  else {
875
    // If no recipients were returned, mark as finished too.
876
    $context['sandbox']['finished'] = 1;
877
  }
878

    
879
  // If we are finished, mark the recipient as read.
880
  if ($context['finished'] >= 1) {
881
    db_update('pm_index')
882
      ->fields(array('is_new' => PRIVATEMSG_READ))
883
      ->condition('mid', $mid)
884
      ->condition('recipient', $recipient->recipient)
885
      ->condition('type', $recipient->type)
886
      ->execute();
887
  }
888
}