#370937 by andypost, Berdir: Privatemsg blocks now implement "cache" => BLOCK_NO_CACHE
[project/privatemsg.git] / privatemsg.module
CommitLineData
129e8ef6
OT
1<?php
2// $Id$
3
4/**
5 * @file
6 * Allows users to send private messages to other users.
7 */
544d3bb5 8
4ba95ee6
OT
9/**
10 * Status constant for read messages.
11 */
12define('PRIVATEMSG_READ', 0);
13/**
14 * Status constant for unread messages.
15 */
16define('PRIVATEMSG_UNREAD', 1);
cd266b9d 17
56306fe2 18/**
129e8ef6 19 * Implementation of hook_perm().
258788ab 20 */
3be9375b
OT
21function privatemsg_perm() {
22 return array(
23 'read privatemsg',
24 'read all private messages',
129e8ef6 25 'administer privatemsg settings',
3be9375b 26 'write privatemsg'
c1ab6ef9 27 );
c1ab6ef9
KN
28}
29
5605b147
OT
30/**
31 * Generate aray of user objects based on a string.
32 *
33 *
34 * @param $userstring
35 * A string with user id, for example 1,2,4. Returned by the list query
36 *
37 * @return
38 * Array with user objects.
39 */
68847a38 40function _privatemsg_generate_user_array($userstring, $slice = NULL) {
fbbe3e72
OT
41 static $user_cache = array();
42
68847a38
OT
43 // Convert user uid list (uid1,uid2,uid3) into an array. If $slice is not NULL
44 // pass that as argument to array_slice(). For example, -4 will only load the
45 // last four users.
46 $users = explode(',', $userstring);
47 if (!is_null($slice)) {
48 $users = array_slice($users, $slice);
49 }
fbbe3e72 50 $participants = array();
68847a38
OT
51 foreach ($users as $uid) {
52 if (!array_key_exists($uid, $user_cache)) {
53 $user_cache[$uid] = user_load($uid);
fbbe3e72 54 }
68847a38
OT
55 if (is_object($user_cache[$uid])) {
56 $participants[$uid] = $user_cache[$uid];
fbbe3e72
OT
57 }
58 }
59 return $participants;
60}
61
5605b147
OT
62/**
63 * Format an array of user objects.
64 *
65 * @param $part_array
66 * Array with user objects, for example the one returnd by
67 * _privatemsg_generate_user_array.
68 *
69 * @param $limit
70 * Limit the number of user objects which should be displayed.
71 * @param $no_text
72 * When TRUE, don't display the Participants/From text.
73 * @return
74 * String with formated user objects, like user1, user2.
75 */
4b3cbfb4
OT
76function _privatemsg_format_participants($part_array, $limit = 20, $no_text = FALSE) {
77 if (count($part_array) > 0) {
fbbe3e72 78 $to = array();
4b3cbfb4
OT
79 $limited = FALSE;
80 foreach ($part_array as $account) {
fbbe3e72 81 if (count($to) >= $limit) {
4b3cbfb4 82 $limited = TRUE;
fbbe3e72
OT
83 break;
84 }
4b3cbfb4 85 $to[] = theme('username', $account);
fbbe3e72
OT
86 }
87
88 $limit_string = '';
89 if ($limited) {
90 $limit_string = t(' and others');
91 }
92
93
4b3cbfb4 94 if ($no_text) {
fbbe3e72
OT
95 return implode(', ', $to) . $limit_string;
96 }
97
98 $last = array_pop($to);
99 if (count($to) == 0) { // Only one participant
100 return t("From !last", array('!last' => $last));
101 }
102 else { // Multipe participants..
103 $participants = implode(', ', $to);
4b3cbfb4 104 return t('Participants: !participants and !last', array('!participants' => $participants, '!last' => $last));
fbbe3e72
OT
105 }
106 }
107 return '';
108}
109
c1ab6ef9 110/**
129e8ef6 111 * Implementation of hook_menu().
c73fcb85 112 */
3be9375b 113function privatemsg_menu() {
129e8ef6
OT
114 $items['messages'] = array(
115 'title' => 'Messages',
116 'title callback' => 'privatemsg_title_callback',
4ba95ee6 117 'page callback' => 'drupal_get_form',
c277e471 118 'page arguments' => array('privatemsg_list', 'list'),
bc4689a7 119 'access callback' => 'privatemsg_user_access',
129e8ef6 120 'type' => MENU_NORMAL_ITEM,
3be9375b 121 );
c277e471
OT
122 $items['messages/list'] = array(
123 'title' => 'Messages',
4ba95ee6 124 'page callback' => 'drupal_get_form',
c277e471 125 'page arguments' => array('privatemsg_list', 'list'),
bc4689a7 126 'access callback' => 'privatemsg_user_access',
129e8ef6
OT
127 'type' => MENU_DEFAULT_LOCAL_TASK,
128 'weight' => -10,
3be9375b 129 );
fb405f31
OT
130 $items['messages/view/%privatemsg_thread'] = array(
131 'title' => 'Read message',
129e8ef6
OT
132 'page callback' => 'privatemsg_view',
133 'page arguments' => array(2),
bc4689a7 134 'access callback' => 'privatemsg_view_access',
fb405f31 135 'type' => MENU_LOCAL_TASK,
129e8ef6 136 'weight' => -10,
3be9375b 137 );
129e8ef6
OT
138 $items['messages/delete/%'] = array(
139 'title' => 'Delete message',
140 'page callback' => 'drupal_get_form',
141 'page arguments' => array('privatemsg_delete', 2),
bc4689a7 142 'access callback' => 'privatemsg_user_access',
129e8ef6
OT
143 'type' => MENU_CALLBACK,
144 'weight' => -10,
145 );
146 $items['messages/new'] = array(
147 'title' => 'Write new message',
148 'page callback' => 'drupal_get_form',
68847a38 149 'page arguments' => array('privatemsg_new', 2, 3, NULL),
bc4689a7 150 'access callback' => 'privatemsg_user_access',
129e8ef6
OT
151 'access arguments' => array('write privatemsg'),
152 'type' => MENU_LOCAL_TASK,
153 'weight' => -7,
154 );
129e8ef6
OT
155 // Auto-completes available user names & removes duplicates.
156 $items['messages/user-name-autocomplete'] = array(
157 'page callback' => 'privatemsg_user_name_autocomplete',
bc4689a7 158 'access callback' => 'privatemsg_user_access',
3be9375b 159 'access arguments' => array('write privatemsg'),
129e8ef6
OT
160 'type' => MENU_CALLBACK,
161 'weight' => -10,
162 );
163 $items['admin/settings/messages'] = array(
164 'title' => 'Private messages',
165 'description' => 'Configure private messaging settings.',
166 'page callback' => 'drupal_get_form',
167 'page arguments' => array('private_message_settings'),
168 'access arguments' => array('administer privatemsg settings'),
169 'type' => MENU_NORMAL_ITEM,
c1ab6ef9 170 );
e5fe4a8d
OT
171 $items['admin/settings/messages/default'] = array(
172 'title' => 'Private messages',
173 'description' => 'Configure private messaging settings.',
174 'page callback' => 'drupal_get_form',
175 'page arguments' => array('private_message_settings'),
176 'access arguments' => array('administer privatemsg settings'),
177 'type' => MENU_DEFAULT_LOCAL_TASK,
178 'weight' => -10,
179 );
4ba95ee6
OT
180 $items['messages/undo/action'] = array(
181 'title' => 'Private messages',
182 'description' => 'Undo last thread action',
183 'page callback' => 'privatemsg_undo_action',
184 'access arguments' => array('read privatemsg'),
185 'type' => MENU_CALLBACK,
186 );
3be9375b 187 return $items;
c1ab6ef9 188}
bc4689a7
OT
189
190/**
5605b147
OT
191 * Privatemsg wrapper for user_access.
192 *
193 * Never allows anonymous user access as that doesn't makes sense.
bc4689a7 194 *
5605b147
OT
195 * @param $permission
196 * Permission string, defaults to read privatemsg
bc4689a7 197 *
5605b147
OT
198 * @return
199 * TRUE if user has access, FALSE if not
bc4689a7 200 *
5605b147 201 * @ingroup api
bc4689a7
OT
202 */
203function privatemsg_user_access($permission = 'read privatemsg', $account = NULL) {
204 if ( $account === NULL ) {
205 global $user;
206 $account = $user;
207 }
208 if (!$account->uid) { // Disallow anonymous access, regardless of permissions
209 return FALSE;
210 }
211 if (!user_access($permission, $account)) {
212 return FALSE;
213 }
214 return TRUE;
215}
216
217
218/**
5605b147
OT
219 * Check access to the view messages page.
220 *
221 * Function to restrict the access of the view messages page to just the
222 * messages/view/% pages and not to leave tabs artifact on other lower
223 * level pages such as the messages/new/%.
224 *
225 * @ingroup api
bc4689a7
OT
226 */
227function privatemsg_view_access() {
228 if (privatemsg_user_access('read privatemsg') && arg(1) == 'view') {
229 return TRUE;
230 }
231 return FALSE;
232}
233
fb405f31 234/**
5605b147
OT
235 * Load a thread with all the messages and participants.
236 *
237 * This function is called by the menu system through the %privatemsg_thread
238 * wildcard.
fb405f31 239 *
5605b147
OT
240 * @param $thread_id
241 * Thread id, pmi.thread_id or pm.mid of the first message in that thread.
053a2f78
OT
242 * @param $account
243 * User object for which the thread should be loaded, defaults to
244 * the current user.
5605b147 245 *
053a2f78
OT
246 * @return
247 * $thread object, with keys messages, participants, title and user. messages
248 * contains an array of messages, participants an array of user, subject the
249 * subject of the thread and user the user viewing the thread.
250 *
251 * If no messages are found, or the thread_id is invalid, the function returns
252 * FALSE.
253
5605b147 254 * @ingroup api
fb405f31 255 */
053a2f78 256function privatemsg_thread_load($thread_id, $account = NULL) {
fb405f31 257 if ((int)$thread_id > 0) {
68847a38 258 $thread = array('thread_id' => $thread_id);
053a2f78
OT
259
260 if (is_null($account)) {
261 global $user;
262 $account = drupal_clone($user);
263 }
264 // load messages returned by the messages query with _privatemsg_load().
265 $query = _privatemsg_assemble_query('messages', array($thread_id), $account);
266 $conversation = db_query($query['query']);
267 while ($result = db_fetch_array($conversation)) {
268 if ($message = _privatemsg_load($result['mid'], $account)) {
269 $thread['messages'][$result['mid']] = $message;
270 }
271 }
272 // if there are no messages, don't allow access to the thread.
273 if (empty($thread['messages'])) {
274 return FALSE;
275 }
276
277 // general data, assume subject is the same for all messages of that thread.
278 $thread['user'] = $account;
279 $message = current($thread['messages']);
280 $thread['subject'] = $message['subject'];
281
282 // Load the list of participants.
283 $query = _privatemsg_assemble_query('participants', $thread_id);
284 $participants = db_query($query['query']);
285 while ($result = db_fetch_array($participants)) {
286 $thread['participants'][$result['uid']] = user_load($result['uid']);
287 }
288 return $thread;
fb405f31
OT
289 }
290 return FALSE;
291}
129e8ef6 292
3be9375b
OT
293function private_message_view_options() {
294 $options = module_invoke_all('privatemsg_view_template');
295 return $options;
fe5f5474 296}
129e8ef6 297
c1ab6ef9 298/**
129e8ef6
OT
299 * Implementation of hook_privatemsg_view_template().
300 *
301 * Allows modules to define different message view template.
302 *
303 * This hook returns information about available themes for privatemsg viewing.
3be9375b 304 *
3be9375b
OT
305 * array(
306 * 'machine_template_name' => 'Human readable template name',
307 * 'machine_template_name_2' => 'Human readable template name 2'
308 * };
309 */
310function privatemsg_privatemsg_view_template() {
311 return array(
129e8ef6 312 'privatemsg-view' => 'Default view',
c1ab6ef9 313 );
3be9375b 314}
129e8ef6 315
3be9375b
OT
316function private_message_settings() {
317 $form = array();
129e8ef6 318
3be9375b 319 $form['theming_settings'] = array(
129e8ef6
OT
320 '#type' => 'fieldset',
321 '#collapsible' => TRUE,
322 '#collapsed' => TRUE,
323 '#title' => t('Theming settings'),
3be9375b
OT
324 );
325 $form['theming_settings']['private_message_view_template'] = array(
129e8ef6
OT
326 '#type' => 'radios',
327 '#title' => t('Private message display template'),
3be9375b 328 '#default_value' => variable_get('private_message_view_template', 'privatemsg-view'),
129e8ef6
OT
329 '#options' => private_message_view_options(),
330 );
331 $form['privatemsg_per_page'] = array(
332 '#type' => 'select',
333 '#title' => t('Messages per page'),
334 '#default_value' => variable_get('privatemsg_per_page', 25),
335 '#options' => drupal_map_assoc(array(10, 25, 50, 75, 100)),
336 '#description' => t('Choose the number of conversations that should be listed per page.'),
3be9375b 337 );
20b4f5d2
OT
338 $form['privatemsg_display_loginmessage'] = array(
339 '#type' => 'checkbox',
340 '#title' => t('Inform the user about new messages on login'),
341 '#default_value' => variable_get('privatemsg_display_loginmessage', TRUE),
342 '#description' => t('This option can safely be disabled if the "New message indication" block is used instead.'),
343 );
4ba95ee6
OT
344 $form['privatemsg_listing'] = array(
345 '#type' => 'fieldset',
346 '#title' => t('Configure listings'),
347 '#collapsible' => TRUE,
348 '#collapsed' => TRUE,
349 );
350
351 $form['privatemsg_listing']['privatemsg_display_fields'] = array(
352 '#type' => 'checkboxes',
353 '#title' => t('Configure fields'),
354 '#description' => t('Select which columns/fields should be displayed in the message listings. Subject and Last updated cannot be disabled.'),
355 '#options' => array(
c277e471 356 'participants' => t('Participants'),
4ba95ee6
OT
357 'thread_started' => t('Started'),
358 'count' => t('Answers'),
359 ),
c277e471 360 '#default_value' => variable_get('privatemsg_display_fields', array('participants')),
4ba95ee6
OT
361 );
362
3be9375b
OT
363 $form['#submit'][] = 'private_message_settings_submit';
364 return system_settings_form($form);
365}
129e8ef6 366
3be9375b
OT
367function private_message_settings_submit() {
368 drupal_rebuild_theme_registry();
369}
c1ab6ef9 370
3be9375b
OT
371function privatemsg_theme() {
372 return array(
373 'privatemsg_view' => array(
129e8ef6 374 'arguments' => array('message' => NULL),
5605b147 375 'template' => variable_get('private_message_view_template', 'privatemsg-view'), // 'privatemsg',
3be9375b
OT
376 ),
377 'privatemsg_from' => array(
129e8ef6
OT
378 'arguments' => array('author' => NULL),
379 'template' => 'privatemsg-from',
3be9375b 380 ),
129e8ef6
OT
381 'privatemsg_to' => array(
382 'arguments' => array('message' => NULL),
383 'template' => 'privatemsg-recipients',
3be9375b 384 ),
129e8ef6
OT
385 'privatemsg_between' => array(
386 'arguments' => array('recipients' => NULL),
387 'template' => 'privatemsg-between',
388 ),
4ba95ee6
OT
389 'privatemsg_list' => array(
390 'file' => 'privatemsg.theme.inc',
391 'path' => drupal_get_path('module', 'privatemsg'),
392 'arguments' => array('form'),
393 ),
394 // Define pattern for header/field templates. The theme system will register all
395 // theme functions that start with the defined pattern.
396 'privatemsg_list_header' => array(
397 'file' => 'privatemsg.theme.inc',
398 'path' => drupal_get_path('module', 'privatemsg'),
399 'pattern' => 'privatemsg_list_header__',
400 'arguments' => array(),
401 ),
402 'privatemsg_list_field' => array(
403 'file' => 'privatemsg.theme.inc',
404 'path' => drupal_get_path('module', 'privatemsg'),
405 'pattern' => 'privatemsg_list_field__',
406 'arguments' => array('thread'),
3be9375b 407 ),
20b4f5d2 408 'privatemsg_new_block' => array(
4ba95ee6
OT
409 'file' => 'privatemsg.theme.inc',
410 'path' => drupal_get_path('module', 'privatemsg'),
20b4f5d2
OT
411 'arguments' => array('count'),
412 ),
c1ab6ef9 413 );
c1ab6ef9 414}
129e8ef6 415
3be9375b 416function privatemsg_preprocess_privatemsg_view(&$vars) {
129e8ef6
OT
417// drupal_set_message('<pre>'. print_r($vars,1 ) . '</pre>');
418
3be9375b 419 $message = $vars['message'];
129e8ef6 420 $vars['mid'] = isset($message['mid']) ? $message['mid']:null;
3be9375b
OT
421 $vars['author_picture'] = theme('user_picture', $message['author']);
422 $vars['author_name_link'] = theme('username', $message['author']);
129e8ef6
OT
423 /**
424 * @todo perhaps make this timestamp configurable via admin UI?
425 */
426 $vars['message_timestamp'] = format_date($message['timestamp'], 'small');
3be9375b 427 $vars['message_body'] = check_markup($message['body']);
93a1235e 428 if (isset($vars['mid'])) {
328f2c62 429 $vars['message_actions'][] = array('title' => t('Delete message'), 'href' => 'messages/delete/'. $vars['mid']);
129e8ef6 430 }
93a1235e
OT
431
432 // call hook_privatemsg_message_view_alter
433 drupal_alter('privatemsg_message_view', $vars);
328f2c62 434
efb7face 435 $vars['message_actions'] = !empty($vars['message_actions']) ? theme('links', $vars['message_actions'], array('class' => 'message-actions')) : '';
e50464be 436}
129e8ef6 437
3be9375b 438function privatemsg_preprocess_privatemsg_to(&$vars) {
5605b147 439 $vars['participants'] = ''; // assign a default empty value
fbbe3e72
OT
440 if (isset($vars['message']['participants'])) {
441 $vars['participants'] = _privatemsg_format_participants($vars['message']['participants']);
e50464be
MM
442 }
443}
fb8f4982
OT
444
445/**
129e8ef6 446 * List messages.
fb8f4982 447 *
c277e471
OT
448 * @param $form_state
449 * Form state array
450 * @param $argument
451 * An argument to pass through to the query builder.
5605b147 452 * @param $uid
c277e471
OT
453 * User id messages of another user should be displayed
454 *
347e66c9 455 * @return
c277e471 456 * Form array
fb8f4982 457 */
c277e471 458function privatemsg_list(&$form_state, $argument = 'list', $uid = NULL) {
fb8f4982 459 global $user;
129e8ef6
OT
460
461 // Setting default behavior...
fb8f4982 462 $account = $user;
4ba95ee6
OT
463 // Because uid is submitted by the menu system, it's a string not a integer.
464 if ((int)$uid > 0 && $uid != $user->uid) {
129e8ef6 465 // Trying to view someone else's messages...
bc4689a7 466 if (!privatemsg_user_access('read all private messages')) {
129e8ef6 467 drupal_set_message(t("You do not have sufficient rights to view someone else's messages"), 'warning');
fb8f4982 468 }
129e8ef6
OT
469 elseif ($account_check = user_load(array('uid' => $uid))) {
470 // Has rights and user_load return an array so user does exist
471 $account = $account_check;
fb8f4982 472 }
129e8ef6
OT
473 }
474 // By this point we have figured out for which user we are listing messages and now it is safe to use $account->uid in the listing query.
c277e471
OT
475
476 $query = _privatemsg_assemble_query('list', $account, $argument);
129e8ef6 477 $result = pager_query($query['query'], variable_get('privatemsg_per_page', 25), 0, $query['count']);
fb8f4982 478
4ba95ee6
OT
479 $threads = array();
480 $form['#data'] = array();
481 while ($row = db_fetch_array($result)) {
482 // Store the raw row data.
483 $form['#data'][$row['thread_id']] = $row;
484 // Store the themed row data.
485 $form['#rows'][$row['thread_id']] = _privatemsg_list_thread($row);
486 // store thread id for the checkboxes array
487 $threads[$row['thread_id']] = '';
129e8ef6 488 }
4ba95ee6
OT
489 if (empty($form['#data'])) {
490 // If no threads are displayed, use these default columns.
491 $keys = array('subject', 'author', 'last_updated');
129e8ef6
OT
492 }
493 else {
4ba95ee6
OT
494 // Load the keys of the first row in data, we don't know the key
495 $keys = array_keys($form['#data'][key($form['#data'])]);
496 $form['actions'] = _privatemsg_action_form();
fb8f4982 497 }
4ba95ee6
OT
498 // Load the themed list headers based on the available data
499 $form['#headers'] = _privatemsg_list_headers(!empty($form['#data']), $keys);
fb8f4982 500
4ba95ee6
OT
501 // Define checkboxes, pager and theme
502 $form['threads'] = array('#type' => 'checkboxes', '#options' => $threads);
503 $form['pager'] = array('#value' => theme('pager'), '#weight' => 20);
504 $form['#theme'] = 'privatemsg_list';
20b4f5d2 505
4ba95ee6
OT
506 // Store the account for which the threads are displayed.
507 $form['#account'] = $account;
508 return $form;
20b4f5d2
OT
509}
510
3be9375b 511/**
4ba95ee6 512 * Changes the read/new status of a single message.
3be9375b 513 *
5605b147
OT
514 * @param $pmid
515 * Message id
516 * @param $status
517 * Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD
518 * @param $account
519 * User object, defaults to the current user
3be9375b 520 */
4ba95ee6
OT
521function privatemsg_message_change_status($pmid, $status, $account = NULL) {
522 if (!$account) {
523 global $user;
524 $account = $user;
129e8ef6 525 }
4ba95ee6
OT
526 $query = "UPDATE {pm_index} SET is_new = %d WHERE mid = %d AND uid = %d";
527 db_query($query, $status, $pmid, $account->uid);
129e8ef6
OT
528}
529
530/**
129e8ef6 531 * Return number of unread messages for an account.
5605b147
OT
532 *
533 * @param $account
534 * Specifiy the user for which the unread count should be loaded.
535 *
536 * @ingroup api
129e8ef6
OT
537 */
538function privatemsg_unread_count($account = NULL) {
539 static $counts = array();
129e8ef6
OT
540 if (!$account || $account->uid == 0) {
541 global $user;
3be9375b 542 $account = $user;
129e8ef6
OT
543 }
544 if ( !isset($counts[$account->uid])) {
f2161b8c 545 $query = _privatemsg_assemble_query('unread_count', $account);
129e8ef6
OT
546 $counts[$account->uid] = db_result(db_query($query['query']));
547 }
548 return $counts[$account->uid];
549}
550
053a2f78
OT
551/**
552 * Menu callback for viewing a thread.
553 *
554 * @param $thread
555 * A array containing all information about a specific thread, generated by
556 * privatemsg_thread_load().
557 * @return
558 * The page content.
559 * @see privatemsg_thread_load().
560 */
561function privatemsg_view($thread) {
562 drupal_set_title(check_plain($thread['subject']));
35a8a46e 563
053a2f78
OT
564 // Render the participants.
565 $content['participants']['#value'] = theme('privatemsg_to', $thread);
129e8ef6
OT
566 $content['participants']['#weight'] = -5;
567
053a2f78
OT
568 // Render the messages.
569 $output = '';
570 foreach ($thread['messages'] as $pmid => $message) {
571 // Set message as read and theme it.
82b71e87
OT
572 if (!empty($message['is_new'])) {
573 privatemsg_message_change_status($pmid, PRIVATEMSG_READ, $thread['user']);
574 }
129e8ef6 575 $output .= theme('privatemsg_view', $message);
129e8ef6 576 }
053a2f78 577 $content['messages']['#value'] = $output;
129e8ef6
OT
578 $content['messages']['#weight'] = 0;
579
053a2f78
OT
580 // Display the reply form if user is allowed to use it.
581 if (privatemsg_user_access('write privatemsg')) {
68847a38 582 $content['reply']['#value'] = drupal_get_form('privatemsg_new', $thread['participants'], $thread['subject'], $thread['thread_id']);
129e8ef6
OT
583 $content['reply']['#weight'] = 5;
584 }
585
053a2f78
OT
586 //allow other modules to hook into the $content array and alter it
587 drupal_alter('privatemsg_view_messages', $content, $thread);
588 return drupal_render($content);
e50464be
MM
589}
590
129e8ef6 591
68847a38 592function privatemsg_new(&$form_state, $recipients = array(), $subject = '', $thread_id = NULL) {
e50464be 593 global $user;
129e8ef6 594
68847a38 595 $recipients_string = '';
3be9375b 596 $body = '';
129e8ef6 597
68847a38
OT
598 // convert recipients to array of user objects
599 if (!empty($recipients) && is_string($recipients) || is_int($recipients)) {
600 $recipients = _privatemsg_generate_user_array($recipients);
601 }
602 elseif (is_object($recipients)) {
603 $recipients = array($recipients);
604 }
605 elseif (empty($recipients) && is_string($recipients)) {
606 $recipients = array();
607 }
608
609 $usercount = 0;
610 $to = array();
611 foreach ($recipients as $recipient) {
612 if (in_array($recipient->name, $to)) {
613 // We already added the recipient to the list, skip him.
614 continue;
615 }
616 // Check if another module is blocking the sending of messages to the recipient by current user.
617 $user_blocked = module_invoke_all('privatemsg_block_message', $user, array($recipient));
618 if (!count($user_blocked) <> 0 && $recipient->uid) {
619 if ($recipient->uid == $user->uid) {
620 $usercount++;
621 // Skip putting author in the recipients list for now.
622 continue;
623 }
624 $to[] = $recipient->name;
625 }
626 }
627
628 if (empty($to) && $usercount >= 1) {
629 // 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.
630 $to[] = $user->name;
631 }
632
633
634 if (!empty($to)) {
635 $recipients_string = implode(', ', $to);
129e8ef6 636 }
3be9375b 637 if (isset($form_state['values'])) {
68847a38 638 $recipients_string = $form_state['values']['recipient'];
129e8ef6
OT
639 $subject = $form_state['values']['subject'];
640 $body = $form_state['values']['body'];
496acb83 641 }
68847a38
OT
642 if (!$thread_id && !empty($recipients_string)) {
643 drupal_set_title(t('Write new message to %recipient', array('%recipient' => $recipients_string)));
644 } elseif (!$thread_id) {
645 drupal_set_title(t('Write new message'));
646 }
e50464be 647
3be9375b
OT
648 $form = array();
649 if (isset($form_state['privatemsg_preview'])) {
650 $form['message_header'] = array(
651 '#type' => 'fieldset',
129e8ef6 652 '#attributes' => array('class' => 'preview'),
3be9375b
OT
653 );
654 $form['message_header']['message_preview'] = array(
655 '#value' => $form_state['privatemsg_preview'],
656 );
29001b19 657 }
129e8ef6
OT
658 $form['privatemsg'] = array(
659 '#type' => 'fieldset',
bc4689a7 660 '#access' => privatemsg_user_access('write privatemsg'),
129e8ef6
OT
661 );
662 $form['privatemsg']['author'] = array(
663 '#type' => 'value',
664 '#value' => $user,
3be9375b 665 );
129e8ef6 666 $form['privatemsg']['recipient'] = array(
3be9375b
OT
667 '#type' => 'textfield',
668 '#title' => t('To'),
669 '#description' => t('Separate multiple names with commas.'),
68847a38 670 '#default_value' => $recipients_string,
129e8ef6 671 '#required' => TRUE,
93a1235e 672 '#weight' => -10,
3be9375b 673 '#size' => 50,
129e8ef6
OT
674 '#autocomplete_path' => 'messages/user-name-autocomplete',
675 // Do not hardcode #maxlength, make it configurable by number of recipients, not their name length.
3be9375b 676 );
129e8ef6 677 $form['privatemsg']['subject'] = array(
3be9375b
OT
678 '#type' => 'textfield',
679 '#title' => t('Subject'),
680 '#size' => 50,
681 '#maxlength' => 255,
682 '#default_value' => $subject,
93a1235e 683 '#weight' => -5,
3be9375b 684 );
129e8ef6 685 $form['privatemsg']['body'] = array(
3be9375b
OT
686 '#type' => 'textarea',
687 '#title' => t('Message'),
688 '#cols' => 10,
689 '#rows' => 6,
93a1235e 690 '#weight' => 0,
3be9375b
OT
691 '#default_value' => $body,
692 );
129e8ef6 693 $form['privatemsg']['preview'] = array(
3be9375b 694 '#type' => 'submit',
129e8ef6 695 '#value' => t('Preview message'),
3be9375b 696 '#submit' => array('pm_preview'),
93a1235e 697 '#weight' => 10,
3be9375b 698 );
129e8ef6 699 $form['privatemsg']['submit'] = array(
3be9375b 700 '#type' => 'submit',
129e8ef6 701 '#value' => t('Send message'),
3be9375b 702 '#submit' => array('pm_send'),
93a1235e 703 '#weight' => 15,
3be9375b 704 );
12a9da26
OT
705 $url = 'messages';
706 if (isset($_REQUEST['destination'])) {
707 $url = $_REQUEST['destination'];
708 }
68847a38
OT
709 elseif (!is_null($thread_id)) {
710 $url = $_GET['q'];
711 }
12a9da26 712
129e8ef6 713 $form['privatemsg']['cancel'] = array(
12a9da26 714 '#value' => l(t('Cancel'), $url, array('attributes' => array('id' => 'edit-cancel'))),
93a1235e 715 '#weight' => 20,
3be9375b 716 );
3be9375b 717 $form['#validate'][] = 'pm_send_validate';
68847a38
OT
718
719 if (!is_null($thread_id)) {
720 $form['privatemsg']['thread_id'] = array(
721 '#type' => 'value',
722 '#value' => $thread_id,
723 );
724 $form['privatemsg']['subject'] = array(
725 '#type' => 'value',
726 '#default_value' => $subject,
727 );
728 $form['privatemsg']['recipient_display'] = array(
729 '#value' => '<p>'. t('<b>Reply to thread</b>:<br /> Recipients: %to', array('%to' => $recipients_string)) .'</p>',
730 '#weight' => -10,
731 );
732 $form['privatemsg']['recipient'] = array(
733 '#type' => 'value',
734 '#default_value' => $recipients_string,
735 );
736 if (empty($recipients_string)) {
737 // If there are no valid recipients, unset the message reply form.
738 $form['privatemsg']['#access'] = FALSE;
739 }
740 }
3be9375b 741 return $form;
29001b19 742}
129e8ef6 743
3be9375b 744function pm_send_validate($form, &$form_state) {
129e8ef6
OT
745 // The actual message that is being sent, we create this during validation and pass to submit to send out.
746 $message = array();
93a1235e 747
129e8ef6
OT
748 $message['body'] = $form_state['values']['body'];
749 $message['subject'] = $form_state['values']['subject'];
750 $message['author'] = $form_state['values']['author'];
3be9375b 751 $message['timestamp'] = time();
129e8ef6
OT
752 if (isset($form_state['values']['thread_id']) && $form_state['values']['thread_id']) {
753 $message['thread_id'] = $form_state['values']['thread_id'];
754 }
3be9375b 755
58f02933
OT
756 $trimed_body = trim(truncate_utf8(strip_tags($message['body']), 50, TRUE, TRUE));
757 if (empty($message['subject']) && !empty($trimed_body)) {
758 $message['subject'] = $trimed_body;
759 }
129e8ef6
OT
760
761 // Verify that recipient's name syntax is correct.
762 $fragments = explode(',', $form_state['values']['recipient']);
3be9375b
OT
763 $invalid = array();
764 $valid = array();
765 foreach ($fragments as $index => $name) {
766 $name = trim($name);
129e8ef6 767 if (!empty($name)) { // We don't care about white space names.
3be9375b 768 if (empty($name) || $error = module_invoke('user', 'validate_name', $name)) {
129e8ef6 769 // These names are invalid due to incorrect user name syntax.
3be9375b 770 $invalid[$name] = $name;
c9696a92
Z
771 }
772 else {
129e8ef6 773 $valid[$name] = $name; // These are valid only due to user name syntax. We still need to check if the user exists and accepts messages.
c9696a92
Z
774 }
775 }
c1ab6ef9 776 }
129e8ef6
OT
777
778 // Verify users exist and load their accounts.
779 foreach ($valid as $index => $name) {
780 if ($recipient = user_load(array('name' => $name))) {
93a1235e 781 $message['recipients'][$recipient->uid] = $recipient;
3be9375b
OT
782 }
783 else {
129e8ef6 784 // Here we add more invalid names due to the fact that they don't exist.
3be9375b
OT
785 $invalid[$name] = $name;
786 }
c1ab6ef9 787 }
c1ab6ef9 788
4fa130d7 789 $validated = _privatemsg_validate_message($message, TRUE);
3be9375b
OT
790 $form_state['validate_built_message'] = $message;
791 if (!empty($invalid)) {
b9fca179 792 drupal_set_message(t('The following users will not receive this private message: !invalid', array('!invalid' => implode(", ", $invalid))), 'error');
3be9375b
OT
793 }
794}
129e8ef6 795
3be9375b 796function pm_send($form, &$form_state) {
3f51bb2b
OT
797 if (_privatemsg_send($form_state['validate_built_message'])) { // Load usernames to which the message was sent to
798 $recipient_names = array();
799 foreach ($form_state['validate_built_message']['recipients'] as $recipient) {
800 $recipient_names[] = theme('username', $recipient);
801 }
802 drupal_set_message(t('A message has been sent to !recipients.', array('!recipients' => implode(', ', $recipient_names))));
6c04b224 803 }
c1ab6ef9 804}
fb8f4982 805
3be9375b 806function pm_preview($form, &$form_state) {
3e0c9a83 807
3be9375b
OT
808 drupal_validate_form($form['form_id']['#value'], $form, $form_state);
809 if (!form_get_errors()) {
129e8ef6 810 $form_state['privatemsg_preview'] = theme('privatemsg_view', $form_state['validate_built_message']);
3be9375b 811 }
129e8ef6 812
5605b147 813 $form_state['rebuild'] = TRUE; // this forces our form to be rebuilt instead of being submitted.
06203012 814}
129e8ef6 815
c277e471 816function privatemsg_sql_list(&$fragments, $account, $argument = 'list') {
3be9375b 817 $fragments['primary_table'] = '{pm_message} pm';
129e8ef6 818
4ba95ee6 819 // Load enabled columns
c277e471 820 $fields = array_filter(variable_get('privatemsg_display_fields', array('participants')));
4ba95ee6
OT
821
822 // Required columns
129e8ef6
OT
823 $fragments['select'][] = 'pmi.thread_id';
824 $fragments['select'][] = 'MIN(pm.subject) as subject';
fbbe3e72 825 $fragments['select'][] = 'MAX(pm.timestamp) as last_updated';
129e8ef6 826 $fragments['select'][] = 'MAX(pmi.is_new) as is_new';
b9fca179 827
4ba95ee6
OT
828 if (in_array('count', $fields)) {
829 $fragments['select'][] = 'COUNT(pmi.thread_id) as count';
830 }
c277e471 831 if (in_array('participants', $fields)) {
4ba95ee6
OT
832 // Query for a string with uid's, for example "1,6,7". This needs a subquery on PostgreSQL.
833 if ($GLOBALS['db_type'] == 'pgsql') {
c277e471
OT
834 $fragments['select'][] = "array_to_string(array(SELECT DISTINCT textin(int4out(pmia.uid))
835 FROM {pm_index} pmia
836 WHERE pmia.thread_id = pmi.thread_id), ',') AS participants";
4ba95ee6
OT
837 }
838 else {
c277e471
OT
839 $fragments['select'][] = '(SELECT GROUP_CONCAT(DISTINCT pmia.uid SEPARATOR ",")
840 FROM {pm_index} pmia
841 WHERE pmia.thread_id = pmi.thread_id) AS participants';
4ba95ee6
OT
842 }
843 }
844 if (in_array('thread_started', $fields)) {
845 $fragments['select'][] = 'MIN(pm.timestamp) as thread_started';
846 }
129e8ef6 847 // pm_index needs to be the first join.
f2161b8c 848 $fragments['inner_join'][] = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid';
129e8ef6 849 $fragments['where'][] = 'pmi.uid = %d';
19ee9333 850 $fragments['query_args']['where'][] = $account->uid;
129e8ef6
OT
851 $fragments['where'][] = 'pmi.deleted = 0';
852 $fragments['group_by'][] = 'pmi.thread_id';
129e8ef6 853
4ba95ee6
OT
854 // We can use tablesort_sql now, but we don't need the ORDER BY part of the query.
855 // Because of that, we need to cut off the first 9 characters of the generated string
856 $order_by = drupal_substr(tablesort_sql(_privatemsg_list_headers( FALSE, array('subject', 'last_updated') + $fields), 'is_new DESC,'), 9);
857 $fragments['order_by'][] = $order_by;
c1ab6ef9
KN
858}
859
5605b147
OT
860/**
861 * @addtogroup sql
862 * @{
863 */
864
865/**
866 * Query function for load.
867 */
93a1235e 868function privatemsg_sql_load(&$fragments, $pmid, $account) {
3be9375b 869// drupal_set_message('<pre>'. print_r(func_get_args(), 1) . '</pre>');
129e8ef6
OT
870 $fragments['primary_table'] = '{pm_message} pm'; // Our primary table
871
872 $fragments['select'][] = "pm.mid";
873 $fragments['select'][] = "pm.author";
874 $fragments['select'][] = "pm.subject";
875 $fragments['select'][] = "pm.body";
876 $fragments['select'][] = "pm.timestamp";
877 $fragments['select'][] = "pmi.is_new";
878
879 $fragments['inner_join'][] = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid';
880 $fragments['where'][] = 'pmi.mid = %d';
19ee9333 881 $fragments['query_args']['where'][] = $pmid;
129e8ef6 882 $fragments['where'][] = 'pmi.uid = %d';
19ee9333 883 $fragments['query_args']['where'][] = $account->uid;
129e8ef6 884}
4ba95ee6
OT
885/**
886 * Query definition to load messages of one or multiple threads.
887 *
888 * @param $fragments
889 * Query fragments array.
890 * @param $threads
891 * Array with one or multiple thread id's.
892 * @param $account
ebf57264 893 * User object for which the messages are being loaded.
4ba95ee6 894 * @param $load_all
ebf57264 895 * Deleted messages are only loaded if this is set to TRUE.
4ba95ee6
OT
896 */
897function privatemsg_sql_messages(&$fragments, $threads, $account, $load_all = FALSE) {
129e8ef6
OT
898 $fragments['primary_table'] = '{pm_index} pmi';
899
900 $fragments['select'][] = 'DISTINCT(pmi.mid) as mid';
4ba95ee6 901 $fragments['where'][] = 'pmi.thread_id IN ('. db_placeholders($threads) .')';
19ee9333 902 $fragments['query_args']['where'] += $threads;
129e8ef6 903 $fragments['where'][] = 'pmi.uid = %d';
19ee9333 904 $fragments['query_args']['where'][] = $account->uid;
4ba95ee6
OT
905 if (!$load_all) {
906 $fragments['where'][] = 'pmi.deleted = 0';
907 }
129e8ef6 908 $fragments['order_by'][] = 'pmi.mid ASC';
c1ab6ef9 909}
04b84abf 910
f2161b8c 911function privatemsg_sql_participants(&$fragments, $thread_id) {
3be9375b 912 $fragments['primary_table'] = '{pm_index} pmi';
129e8ef6
OT
913
914 $fragments['select'][] = 'DISTINCT(pmi.uid) as uid';
915 $fragments['where'][] = 'pmi.thread_id = %d';
19ee9333 916 $fragments['query_args']['where'][] = $thread_id;
fb8f4982
OT
917}
918
919
f2161b8c 920function privatemsg_sql_unread_count(&$fragments, $account) {
129e8ef6
OT
921 $fragments['primary_table'] = '{pm_index} pmi';
922
923 $fragments['select'][] = 'COUNT(DISTINCT thread_id) as unread_count';
924 $fragments['where'][] = 'pmi.deleted = 0';
925 $fragments['where'][] = 'pmi.is_new = 1';
926 $fragments['where'][] = 'pmi.uid = %d';
19ee9333 927 $fragments['query_args']['where'][] = $account->uid;
129e8ef6
OT
928}
929
afbd3810
OT
930function privatemsg_sql_autocomplete(&$fragments, $search, $names) {
931 $fragments['primary_table'] = '{users} u';
932 $fragments['select'][] = 'u.name';
933 $fragments['where'][] = "u.name LIKE '%s'";
19ee9333 934 $fragments['query_args']['where'][] = $search .'%%';
afbd3810
OT
935 if (!empty($names)) {
936 $fragments['where'][] = "u.name NOT IN (". db_placeholders($names, 'text') .")";
19ee9333 937 $fragments['query_args']['where'] += $names;
afbd3810
OT
938 }
939 $fragments['where'][] = 'u.status <> 0';
940 $fragments['order_by'][] = 'u.name ASC';
941}
942
fb8f4982 943/**
5605b147
OT
944 * @}
945 */
946
947/**
fb8f4982 948 * Return autocomplete results for usernames.
5605b147 949 *
129e8ef6 950 * Prevents usernames from being used and/or suggested twice.
fb8f4982
OT
951 */
952function privatemsg_user_name_autocomplete($string) {
953 $names = array();
129e8ef6 954 // 1: Parse $string and build list of valid user names.
fb8f4982
OT
955 $fragments = explode(',', $string);
956 foreach ($fragments as $index => $name) {
957 $name = trim($name);
958 if ($error = module_invoke('user', 'validate_name', $name)) {
129e8ef6 959 // Do nothing if this name does not validate.
fb8f4982
OT
960 }
961 else {
962 $names[$name] = $name;
963 }
964 }
129e8ef6
OT
965
966 // By using user_validate_user we can ensure that names included in $names are at least logisticaly possible.
967 // 2: Find the next user name suggestion.
fb8f4982 968 $fragment = array_pop($names);
56c677a0 969 $matches = array();
fb8f4982 970 if (!empty($fragment)) {
afbd3810
OT
971 $query = _privatemsg_assemble_query('autocomplete', $fragment, $names);
972 $result = db_query_range($query['query'], $fragment, 0, 10);
129e8ef6
OT
973 $prefix = count($names) ? implode(", ", $names) .", " : '';
974 // 3: Build proper suggestions and print.
fb8f4982 975 while ($user = db_fetch_object($result)) {
129e8ef6 976 $matches[$prefix . $user->name .", "] = $user->name;
fb8f4982 977 }
93a1235e 978 }
56c677a0
OT
979 // convert to object to prevent drupal bug, see http://drupal.org/node/175361
980 drupal_json((object)$matches);
fb8f4982
OT
981}
982
129e8ef6
OT
983function privatemsg_user($op, &$edit, &$account, $category = NULL) {
984 global $user;
985
986
987 switch ($op) {
988 case 'view':
68847a38 989 if ($url = privatemsg_get_link(array($account))) {
129e8ef6
OT
990 $account->content['privatemsg_send_new_message'] = array(
991 '#type' => 'markup',
93a1235e 992 '#value' => l(t('Send this user a message'), $url, array('query' => drupal_get_destination())),
129e8ef6
OT
993 '#weight' => 10,
994 );
995 }
996 break;
997 case 'login':
bc4689a7 998 if (variable_get('privatemsg_display_loginmessage', TRUE) && privatemsg_user_access()) {
20b4f5d2
OT
999 $count = privatemsg_unread_count();
1000 if ($count) {
47cbdd37 1001 drupal_set_message(t('You have <a href="@messages">%unread</a>.', array('@messages' => url('messages'), '%unread' => format_plural($count, '1 unread message', '@count unread messages'))));
20b4f5d2 1002 }
129e8ef6
OT
1003 }
1004 break;
1005 }
1006}
1007
1008function privatemsg_block($op = 'list', $delta = 0, $edit = array()) {
1009 if ('list' == $op) {
1010 $blocks = array();
1011 $blocks['privatemsg-menu'] = array(
1012 'info' => t('Privatemsg links'),
7deb0c62 1013 'cache' => BLOCK_NO_CACHE,
129e8ef6 1014 );
20b4f5d2
OT
1015 $blocks['privatemsg-new'] = array(
1016 'info' => t('New message indication'),
7deb0c62 1017 'cache' => BLOCK_NO_CACHE,
20b4f5d2 1018 );
129e8ef6
OT
1019
1020 return $blocks;
1021 }
5605b147 1022 elseif ('view' == $op) {
129e8ef6
OT
1023 $block = array();
1024 switch ($delta) {
1025 case 'privatemsg-menu':
1026 $block = _privatemsg_block_menu();
1027 break;
20b4f5d2
OT
1028 case 'privatemsg-new':
1029 $block = _privatemsg_block_new();
1030 break;
129e8ef6
OT
1031 }
1032 return $block;
1033 }
1034}
1035
8960261c 1036function privatemsg_title_callback($title = NULL) {
129e8ef6 1037 $count = privatemsg_unread_count();
129e8ef6 1038
b1d8b30e 1039 if ($count > 0) {
924b247b 1040 return format_plural($count, 'Messages (1 new)', 'Messages (@count new)');
b1d8b30e
OT
1041 }
1042 return t('Messages');
129e8ef6
OT
1043}
1044
1045
20b4f5d2
OT
1046function _privatemsg_block_new() {
1047 $block = array();
1048
bc4689a7 1049 if (!privatemsg_user_access()) {
20b4f5d2
OT
1050 return $block;
1051 }
1052
1053 $count = privatemsg_unread_count();
1054 if ($count) {
1055 $block = array(
1056 'subject' => format_plural($count, 'New message', 'New messages'),
1057 'content' => theme('privatemsg_new_block', $count),
1058 );
1059 return $block;
1060 }
1061 return array();
1062}
1063
129e8ef6
OT
1064function _privatemsg_block_menu() {
1065 $block = array();
1066
129e8ef6 1067 $links = array();
bc4689a7 1068 if (privatemsg_user_access('write privatemsg')) {
129e8ef6
OT
1069 $links[] = l(t('Write new message'), 'messages/new');
1070 }
bc4689a7 1071 if (privatemsg_user_access('read privatemsg') || privatemsg_user_access('read all private messages') ) {
8960261c 1072 $links[] = l(privatemsg_title_callback(), 'messages');
129e8ef6
OT
1073 }
1074 if ( count( $links ) ) {
1075 $block = array(
8c6d3b74 1076 'subject' => t('Private messages'),
129e8ef6
OT
1077 'content' => theme('item_list', $links),
1078 );
1079 }
1080 return $block;
1081}
1082
129e8ef6
OT
1083function privatemsg_delete($form_state, $pmid) {
1084 global $user;
1085
1086 $form['pmid'] = array(
1087 '#type' => 'value',
1088 '#value' => $pmid,
1089 );
1090 return confirm_form($form,
1091 t('Are you sure you want to delete'),
1092 isset($_GET['destination']) ? $_GET['destination'] : 'messages/view/'. $pmid,
1093 t('This action cannot be undone.'),
1094 t('Delete'),
1095 t('Cancel')
1096 );
1097}
1098
5605b147
OT
1099/**
1100 * Delete or restore a message.
1101 *
1102 * @param $pmid
1103 * Message id, pm.mid field.
1104 * @param $delete
1105 * Either deletes or restores the thread (1 => delete, 0 => restore)
1106 * @param $account
1107 * User acccount for which the message should be deleted.
1108 *
1109 * @ingroup api
1110 */
4ba95ee6 1111function privatemsg_message_change_delete($pmid, $delete, $account = NULL) {
93a1235e
OT
1112 if (is_null($account)) {
1113 global $user;
1114 $account = drupal_clone($user);
1115 }
1116 $message = _privatemsg_load($pmid, $account);
1117
4ba95ee6 1118 db_query('UPDATE {pm_index} SET deleted = %d WHERE mid = %d AND uid = %d', $delete, $pmid, $account->uid);
93a1235e
OT
1119
1120 $result = db_query("SELECT MIN(deleted) AS deleted_by_all FROM {pm_index} WHERE mid = %d", $pmid);
1121 $deleted = db_fetch_array($result);
1122
1123 $deleted_by_all = FALSE;
1124 if ($deleted['deleted_by_all'] == 0) {
1125 $deleted_by_all = TRUE;
1126 }
1127
1128 module_invoke_all('privatemsg_message_delete', $message, $deleted_by_all);
1129}
1130
129e8ef6 1131function privatemsg_delete_submit($form, &$form_state) {
129e8ef6 1132 if ($form_state['values']['confirm']) {
4ba95ee6 1133 privatemsg_message_change_delete($form_state['values']['pmid'], 1);
0f7e1e50 1134 drupal_set_message(t('Message has been deleted'));
129e8ef6
OT
1135 }
1136 $form_state['redirect'] = 'messages';
1137}
93a1235e 1138/**
5605b147 1139 * Send a new message.
93a1235e
OT
1140 *
1141 * This functions does send a message in a new thread.
5605b147
OT
1142 * Example:
1143 * @code
cb86482d 1144 * privatemsg_new_thread(array(user_load(5)), 'The subject', 'The body text');
5605b147
OT
1145 * @endcode
1146 *
4fa130d7
OT
1147 * @param $recipients
1148 * Array of recipients (user objects)
5605b147
OT
1149 * @param $subject
1150 * The subject of the new message
1151 * @param $body
1152 * The body text of the new message
4fa130d7
OT
1153 * @param $options
1154 * Additional options, possible keys:
1155 * author => User object of the author
1156 * timestamp => Time when the message was sent
93a1235e 1157 *
5605b147
OT
1158 * @return
1159 * Either true or an array with validation errors
1160 *
1161 * @ingroup api
93a1235e 1162 */
4fa130d7
OT
1163function privatemsg_new_thread($recipients, $subject, $body = NULL, $options = array()) {
1164 global $user;
1165 $author = drupal_clone($user);
129e8ef6 1166
93a1235e
OT
1167 $message = array();
1168 $message['subject'] = $subject;
93a1235e 1169 $message['body'] = $body;
93a1235e
OT
1170 $message['recipients'] = $recipients;
1171
cb86482d 1172 // Set custom options, if any.
4fa130d7
OT
1173 if (!empty($options)) {
1174 $message += $options;
93a1235e 1175 }
cb86482d 1176 // Apply defaults - this will not overwrite existing keys.
4fa130d7
OT
1177 $message += array(
1178 'author' => $author,
1179 'timestamp' => time(),
1180 );
1181
1182 $validated = _privatemsg_validate_message($message);
1183 if ($validated['success']) {
1184 $validated['success'] = _privatemsg_send($message);
93a1235e 1185 }
4fa130d7
OT
1186
1187 return $validated;
93a1235e
OT
1188}
1189/**
1190 * Send a reply message
1191 *
1192 * This functions replies on an existing thread.
1193 *
5605b147
OT
1194 * @param $thread_id
1195 * Thread id
1196 * @param $body
1197 * The body text of the new message
4fa130d7
OT
1198 * @param $options
1199 * Additional options, possible keys:
1200 * author => User object of the author
1201 * timestamp => Time when the message was sent
5605b147
OT
1202 *
1203 * @return
1204 * Either true or an array with validation errors
93a1235e 1205 *
5605b147 1206 * @ingroup api
93a1235e 1207 */
4fa130d7
OT
1208function privatemsg_reply($thread_id, $body = NULL, $options = array()) {
1209 global $user;
1210 $author = drupal_clone($user);
93a1235e
OT
1211
1212 $message = array();
93a1235e 1213 $message['body'] = $body;
4fa130d7
OT
1214
1215 // set custom options, if any
1216 if (!empty($options)) {
1217 $message += $options;
1218 }
1219 // apply defaults
1220 $message += array(
1221 'author' => $author,
1222 'timestamp' => time(),
1223 );
93a1235e
OT
1224
1225 // We don't know the subject and the recipients, so we need to load them..
1226 // thread_id == mid on the first message of the thread
4fa130d7 1227 $first_message = _privatemsg_load($thread_id, $message['author']);
93a1235e
OT
1228 if (!$first_message) {
1229 return array(t('Thread %thread_id not found, unable to answer', array('%thread_id' => $thread_id)));
1230 }
1231
89f2c1af 1232 $message['thread_id'] = $thread_id;
93a1235e
OT
1233
1234 $query = _privatemsg_assemble_query('participants', $thread_id);
1235 $participants = db_query($query['query']);
1236 while ($result = db_fetch_object($participants)) {
1237 $recipient = user_load($result->uid);
1238 $message['recipients'][$recipient->uid] = $recipient;
1239 }
1240
1241 $message['subject'] = $first_message['subject'];
1242
4fa130d7
OT
1243 $validated = _privatemsg_validate_message($message);
1244 if ($validated['success']) {
1245 $validated['success'] = _privatemsg_send($message);
93a1235e 1246 }
4fa130d7 1247 return $validated;
93a1235e
OT
1248}
1249
4fa130d7
OT
1250function _privatemsg_validate_message(&$message, $form = FALSE) {
1251 $messages = array('error' => array(), 'warning' => array());
1252 if (!privatemsg_user_access('write privatemsg', $message['author'])) {
93a1235e 1253 // no need to do further checks in this case...
4fa130d7
OT
1254 if ($form) {
1255 form_set_error('author', t('User @user is not allowed to write messages', array('@user' => $message['author']->name)));
1256 return array(
1257 'success' => FALSE,
1258 'messages' => $messages,
1259 );
1260 }
1261 else {
1262 $messages['error'][] = t('User @user is not allowed to write messages', array('@user' => $message['author']->name));
1263 return array(
1264 'success' => FALSE,
1265 'messages' => $messages,
1266 );
1267 }
129e8ef6
OT
1268 }
1269
93a1235e 1270 if (empty($message['subject'])) {
4fa130d7
OT
1271 if ($form) {
1272 form_set_error('subject', t('Disallowed to send a message without subject'));
1273 }
1274 else {
1275 $messages['error'][] = t('Disallowed to send a message without subject');
1276 }
93a1235e
OT
1277 }
1278
93a1235e 1279 if (empty($message['recipients']) || !is_array($message['recipients'])) {
4fa130d7
OT
1280 if ($form) {
1281 form_set_error('to', t('Disallowed to send a message without atleast one valid recipient'));
1282 }
1283 else {
1284 $messages['error'][] = t('Disallowed to send a message without atleast one valid recipient');
1285 }
93a1235e
OT
1286 }
1287
1288 if (!empty($message['recipients']) && is_array($message['recipients'])) {
4fa130d7 1289 foreach(module_invoke_all('privatemsg_block_message', $message['author'], $message['recipients']) as $blocked) {
b9fca179 1290 unset($message['recipients'][$blocked['uid']]);
6f9a6e13
OT
1291 if ($form) {
1292 drupal_set_message($blocked['message'], 'warning');
1293 } else {
1294 $messages['warning'][] = $blocked['message'];
1295 }
93a1235e
OT
1296 }
1297 }
1298
1299 // Check again, give another error message if all recipients are blocked
1300 if (empty($message['recipients'])) {
4fa130d7
OT
1301 if ($form) {
1302 form_set_error('to', t('Disallowed to send message because all recipients are blocked'));
1303 }
1304 else {
1305 $messages['error'][] = t('Disallowed to send message because all recipients are blocked');
1306 }
93a1235e
OT
1307 }
1308
4fa130d7
OT
1309 $messages += module_invoke_all('privatemsg_message_validate', $message, $form);
1310 $success = empty($messages['error']);
1311 return array(
1312 'success' => $success,
1313 'messages' => $messages,
1314 );
129e8ef6
OT
1315}
1316
93a1235e
OT
1317function _privatemsg_send($message) {
1318
1319 drupal_alter('privatemsg_message_presave', $message);
1320
1321 // 1) Save the message body first.
1322 $args = array();
1323 $args[] = $message['subject'];
1324 $args[] = $message['author']->uid;
1325 $args[] = $message['body'];
1326 $args[] = $message['timestamp'];
1327 $query = "INSERT INTO {pm_message} (subject, author, body, timestamp) VALUES ('%s', %d, '%s', %d)";
1328 $resuld = db_query($query, $args);
1329 $mid = db_last_insert_id('pm_message', 'mid');
1330 $message['mid'] = $mid;
1331
1332 // Thread ID is the same as the mid if it's the first message in the thread.
1333 if (!isset($message['thread_id'])) {
1334 $message['thread_id'] = $mid;
1335 }
1336
1337 // 2) Save message to recipients.
1338 // Each recipient gets a record in the pm_index table.
1339
1340 $query = "INSERT INTO {pm_index} (mid, thread_id, uid, is_new, deleted) VALUES (%d, %d, %d, %d, 0)";
1341 foreach ($message['recipients'] as $recipient) {
1342
1343 db_query($query, $mid, $message['thread_id'], $recipient->uid, 1);
1344 }
1345 // Also add a record for tha author to the pm_index table - set column "new" to 0.
1346 db_query($query, $mid, $message['thread_id'], $message['author']->uid, 0);
1347
1348 module_invoke_all('privatemsg_message_insert', $message);
1349
1350 return TRUE;
129e8ef6
OT
1351}
1352
93a1235e
OT
1353/**
1354 * Returns a link to send message form for a specific users.
1355 *
1356 * Contains permission checks of author/recipient, blocking and
1357 * if a anonymous user is involved.
1358 *
5605b147
OT
1359 * @param $recipient
1360 * Recipient of the message
1361 * @param $account
1362 * Sender of the message, defaults to the current user
1363 *
1364 * @return
1365 * Either FALSE or a URL string
93a1235e 1366 *
5605b147 1367 * @ingroup api
93a1235e 1368 */
68847a38 1369function privatemsg_get_link($recipients, $account = array(), $subject = NULL) {
93a1235e
OT
1370 if ($account == NULL) {
1371 global $user;
1372 $account = $user;
1373 }
76d28f2a 1374
68847a38
OT
1375 if (!is_array($recipients)) {
1376 $recipients = array($recipients);
1377 }
93a1235e 1378
68847a38 1379 if (!privatemsg_user_access('write privatemsg', $account) || $account->uid == 0) {
93a1235e
OT
1380 return FALSE;
1381 }
1382
68847a38
OT
1383 $validated = array();
1384 foreach ($recipients as $recipient) {
1385 if (!privatemsg_user_access('read privatemsg', $recipient)) {
1386 continue;
1387 }
1388 if (count(module_invoke_all('privatemsg_block_message', $account, array($recipient))) > 0) {
1389 continue;
1390 }
1391 $validated[] = $recipient->uid;
1392 }
1393 if (empty($validated)) {
93a1235e
OT
1394 return FALSE;
1395 }
68847a38
OT
1396 $url = 'messages/new/'. implode(',', $validated);
1397 if (!is_null($subject)) {
1398 $url .= '/'. $subject;
1399 }
1400 return $url;
93a1235e 1401}
efb7face
OT
1402
1403/**
5605b147 1404 * Load a single message.
efb7face 1405 *
5605b147
OT
1406 * @param $pmid
1407 * Message id, pm.mid field
1408 * @param $account
1409 * For which account the message should be loaded.
1410 * Defaults to the current user.
1411 *
1412 * @ingroup api
efb7face
OT
1413 */
1414function _privatemsg_load($pmid, $account = NULL) {
1415 if (empty($account)) {
1416 global $user;
1417 $account = drupal_clone($user);
1418 }
1419
1420 $query = _privatemsg_assemble_query('load', $pmid, $account);
1421
1422 $result = db_query($query['query']);
1423 $message = db_fetch_array($result);
1424 $message['user'] = $account;
053a2f78
OT
1425 // Load author of message.
1426 $message['author'] = user_load($message['author']);
efb7face
OT
1427 $returned = module_invoke_all('privatemsg_message_load', $message);
1428 if (!empty($returned)) {
1429 $message = array_merge_recursive($returned, $message);
1430 }
1431 return $message;
1432}
1433
5605b147
OT
1434/**
1435 * Generates a query based on a query id.
1436 *
1437 * @param $query
1438 * Either be a string ('some_id') or an array('group_name', 'query_id'),
1439 * if a string is supplied, group_name defaults to 'privatemsg'.
1440 *
1441 * @return
1442 * Array with the keys query and count. count can be used to count the
1443 * elements which would be returned by query. count can be used together
1444 * with pager_query().
1445 *
1446 * @ingroup sql
1447 */
efb7face
OT
1448function _privatemsg_assemble_query($query) {
1449
1450 // Modules will be allowed to choose the prefix for the querybuilder, but if there is not one supplied, 'privatemsg' will be taken by default.
1451 if (is_array($query)) {
1452 $query_id = $query[0];
1453 $query_group = $query[1];
1454 }
1455 else {
1456 $query_id = $query;
1457 $query_group = 'privatemsg';
1458 }
1459
1460 $SELECT = array();
1461 $INNER_JOIN = array();
1462 $WHERE = array();
1463 $GROUP_BY = array();
c277e471 1464 $HAVING = array();
efb7face 1465 $ORDER_BY = array();
19ee9333 1466 $QUERY_ARGS = array('where' => array(), 'join' => array(), 'having' => array());
efb7face
OT
1467 $primary_table = '';
1468
1469 $fragments = array(
1470 'select' => $SELECT,
1471 'inner_join' => $INNER_JOIN,
1472 'where' => $WHERE,
1473 'group_by' => $GROUP_BY,
c277e471 1474 'having' => $HAVING,
efb7face
OT
1475 'order_by' => $ORDER_BY,
1476 'query_args' => $QUERY_ARGS,
1477 'primary_table' => $primary_table,
1478 );
1479
1480 /**
1481 * Begin: dynamic arguments
1482 */
1483 $args = func_get_args();
1484 unset($args[0]);
5605b147
OT
1485 // we do the merge because we call call_user_func_array and not drupal_alter
1486 // this is necessary because otherwise we would not be able to use $args correctly (otherwise it doesnt unfold)
efb7face
OT
1487 $alterargs = array(&$fragments);
1488 $query_function = $query_group .'_sql_'. $query_id;
1489 if (!empty($args)) {
1490 $alterargs = array_merge($alterargs, $args);
1491 }
1492 /**
1493 * END: Dynamic arguments
1494 */
1495 if (!function_exists($query_function)) {
1496 drupal_set_message(t('Query function %function does not exist', array('%function' => $query_function)), 'error');
1497 return FALSE;
1498 }
1499 call_user_func_array($query_function, $alterargs);
1500
1501 array_unshift($alterargs, $query_function);
1502 call_user_func_array('drupal_alter', $alterargs);
1503
1504 $SELECT = $fragments['select'];
1505 $INNER_JOIN = $fragments['inner_join'];
1506 $WHERE = $fragments['where'];
1507 $GROUP_BY = $fragments['group_by'];
c277e471 1508 $HAVING = $fragments['having'];
efb7face
OT
1509 $ORDER_BY = $fragments['order_by'];
1510 $QUERY_ARGS = $fragments['query_args'];
1511 $primary_table = $fragments['primary_table'];
1512
d3e3d31e
OT
1513 // pgsql has a case sensitive LIKE - replace it with ILIKE. see http://drupal.org/node/462982
1514 if ($GLOBALS['db_type'] == 'pgsql') {
1515 $WHERE = str_replace('LIKE', 'ILIKE', $WHERE);
1516 }
1517
efb7face
OT
1518 if (empty($primary_table)) {
1519 $primary_table = '{privatemsg} pm';
1520 }
1521
1522 // Perform the whole query assembly only if we have something to select.
1523 if (!empty($SELECT)) {
1524 $str_select = implode(", ", $SELECT);
1525 $query = "SELECT {$str_select} FROM ". $primary_table;
1526
1527 // Also build a count query which can be passed to pager_query to get a "page count" as that does not play well with queries including "GROUP BY".
1528 // In most cases, "COUNT(*)" is enough to get the count query, but in queries involving a GROUP BY, we want a count of the number of groups we have, not the count of elements inside each group.
1529 // So we test if there is GROUP BY and if there is, count the number of distinct groups. If not, we go the normal wal and do a plain COUNT(*).
1530 if (!empty($GROUP_BY)) {
1531 // PostgreSQL does not support COUNT(sometextfield, someintfield), so I'm only using the first one
1532 // Works fine for thread_id/list but may generate an error when a more complex GROUP BY is used.
1533 $str_group_by_count = current($GROUP_BY);
1534 $count = "SELECT COUNT(DISTINCT {$str_group_by_count}) FROM ". $primary_table;
1535 }
1536 else {
1537 $count = "SELECT COUNT(*) FROM ". $primary_table;
1538 }
1539
1540 if (!empty($INNER_JOIN)) {
1541 $str_inner_join = implode(' ', $INNER_JOIN);
1542 $query .= " {$str_inner_join}";
1543 $count .= " {$str_inner_join}";
1544 }
1545 if (!empty($WHERE)) {
1546 $str_where = '('. implode(') AND (', $WHERE) .')';
1547 $query .= " WHERE {$str_where}";
1548 $count .= " WHERE {$str_where}";
1549 }
1550 if (!empty($GROUP_BY)) {
1551 $str_group_by = ' GROUP BY '. implode(", ", $GROUP_BY) ;
1552 $query .= " {$str_group_by}";
1553 }
c277e471
OT
1554 if (!empty($HAVING)) {
1555 $str_having = '('. implode(') AND (', $HAVING) .')';
1556 $query .= " HAVING {$str_having}";
1557 // queries containing a HAVING break the count query on pgsql.
1558 // In this case, use the subquery method as outlined in http://drupal.org/node/303087#comment-1370752 .
1559 // The subquery method will work for all COUNT queries, but it is thought to be much slower, so we are only using it where other cross database approaches fail.
1560 $count = 'SELECT COUNT(*) FROM ('. $query .') as count';
1561 }
efb7face
OT
1562 if (!empty($ORDER_BY)) {
1563 $str_order_by = ' ORDER BY '. implode(", ", $ORDER_BY) ;
1564 $query .= " {$str_order_by}";
1565 }
19ee9333 1566 $QUERY_ARGS = array_merge($QUERY_ARGS['join'], $QUERY_ARGS['where'], $QUERY_ARGS['having']);
efb7face
OT
1567 if (!empty($QUERY_ARGS)) {
1568 _db_query_callback($QUERY_ARGS, TRUE);
1569 $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
1570 _db_query_callback($QUERY_ARGS, TRUE);
1571 $count = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $count);
1572 }
1573 return array('query' => $query, 'count' => $count);
1574 }
1575 return FALSE;
1576}
4ba95ee6 1577
a926640f
OT
1578/**
1579 * Returns a form which handles and displays thread actions.
1580 *
1581 * Additional actions can be added with the privatemsg_thread_operations hook.
1582 * It is also possible to extend this form with additional buttons or other
1583 * elements, in that case, the definitions in the above hook need no label tag,
1584 * instead, the submit button key needs to match with the key of the operation.
1585 *
1586 * @see hook_privatemsg_thread_operations()
1587 *
1588 * @return
1589 * The FAPI definitions for the thread action form.
1590 */
4ba95ee6
OT
1591function _privatemsg_action_form() {
1592 $form = array(
1593 '#type' => 'fieldset',
1594 '#title' => t('Actions'),
1595 '#prefix' => '<div class="container-inline">',
1596 '#suffix' => '</div>',
1597 '#collapsible' => TRUE,
1598 '#collapsed' => FALSE,
1599 '#weight' => 15,
1600 );
1601 $form['delete'] = array(
1602 '#type' => 'submit',
1603 '#value' => t('Delete'),
1604 );
1605 // Display all operations which have a label.
68940e1a 1606 $options = array(0 => t('More actions...'));
4ba95ee6
OT
1607 foreach (module_invoke_all('privatemsg_thread_operations') as $operation => $array) {
1608 if (isset($array['label'])) {
1609 $options[$operation] = $array['label'];
1610 }
1611 }
1612 $form['operation'] = array(
1613 '#type' => 'select',
1614 '#options' => $options,
1615 '#default_value' => 0,
1616 // Execute the submit button if a operation has been selected.
1617 '#attributes' => array('onchange' => "$('#edit-submit').click()"),
1618 );
1619 $form['submit'] = array(
1620 '#type' => 'submit',
1621 '#value' => t('Execute'),
1622 '#submit' => array('privatemsg_list_submit'),
1623 '#attributes' => array('class' => 'privatemsg-action-button'),
1624 );
1625 // JS for hiding the execute button(s)
1626 drupal_add_js(drupal_get_path('module', 'privatemsg') .'/privatemsg-list.js');
1627 return $form;
1628}
1629
1630/**
7608a25a 1631 * Marks one or multiple threads as (un)read.
4ba95ee6
OT
1632 *
1633 * @param $threads
7608a25a 1634 * Array with thread id's or a single thread id.
4ba95ee6 1635 * @param $status
7608a25a 1636 * Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD, sets the new status.
4ba95ee6 1637 * @param $account
7608a25a 1638 * User object for which the threads should be deleted, defaults to the current user.
4ba95ee6
OT
1639 */
1640function privatemsg_thread_change_status($threads, $status, $account = NULL) {
1641 if (!is_array($threads)) {
1642 $threads = array($threads);
1643 }
1644 if (empty($account)) {
1645 global $user;
1646 $account = drupal_clone($user);
1647 }
1648 // Merge status and uid with the threads list. array_merge() will not overwrite/ignore thread_id 1.
1649 $params = array_merge(array($status, $account->uid), $threads);
1650 db_query('UPDATE {pm_index} SET is_new = %d WHERE uid = %d AND thread_id IN ('. db_placeholders($threads) .')', $params);
1651
1652 if ($status == PRIVATEMSG_UNREAD) {
1653 drupal_set_message(t('Marked %count threads as unread.', array('%count' => count($threads))));
1654 }
1655 else {
1656 drupal_set_message(t('Marked %count threads as read.', array('%count' => count($threads))));
1657 }
1658}
1659/**
1660 * Returns a table header definition based on the submitted keys.
1661 *
7bb3f078
OT
1662 * Uses @link theming theme patterns @endlink to theme single headers.
1663 *
4ba95ee6 1664 * @param $has_posts
7608a25a
OT
1665 * TRUE when there is at least one row. Decides if the select all checkbox should be displayed.
1666 * @param $keys
4ba95ee6
OT
1667 * Array with the keys which are present in the query/should be displayed.
1668 * @return
1669 * Array with header defintions for tablesort_sql and theme('table').
1670 */
1671function _privatemsg_list_headers($has_posts, $keys) {
1672 $select_header = $has_posts ? theme('table_select_header_cell') : '';
1673 $select_header['#weight'] = -50;
1674
1675 // theme() doesn't include the theme file for patterns, we need to do it manually.
1676 include_once drupal_get_path('module', 'privatemsg') .'/privatemsg.theme.inc';
1677
1678 $header = array($select_header);
1679 foreach ($keys as $key) {
1680 // First, try to load a specific theme for that header, if not present, use the default.
1681 if ($return = theme(array('privatemsg_list_header__'. $key, 'privatemsg_list_header'))) {
1682 // The default theme returns nothing, only store the value if we have something.
1683 $header[$key] = $return;
1684 }
1685 }
1686 return $header;
1687}
1688
1689/**
1690 * Formats a row in the message list.
1691 *
7bb3f078 1692 * Uses @link theming theme patterns @endlink to theme single fields.
b9fca179 1693 *
4ba95ee6
OT
1694 * @param $thread
1695 * Array with the row data returned by the database.
1696 * @return
1697 * Row definition for use with theme('table')
1698 */
1699function _privatemsg_list_thread($thread) {
1700 $row = array('data' => array());
1701
1702 if (!empty($thread['is_new'])) {
1703 // Set the css class in the tr tag.
1704 $row['class'] = 'privatemsg-unread';
1705 }
1706 foreach ($thread as $key => $data) {
1707 // First, try to load a specific theme for that field, if not present, use the default.
1708 if ($return = theme(array('privatemsg_list_field__'. $key, 'privatemsg_list_field'), $thread)) {
1709 // The default theme returns nothing, only store the value if we have something.
1710 $row['data'][$key] = $return;
1711 }
1712 }
1713 return $row;
1714}
1715
1716/**
1717 * Menu callback for messages/undo/action.
1718 *
ad541119 1719 * This function will test if an undo callback is stored in SESSION and execute it.
4ba95ee6
OT
1720 */
1721function privatemsg_undo_action() {
1722 // Check if a undo callback for that user exists.
1723 if (isset($_SESSION['privatemsg']['undo callback']) && is_array($_SESSION['privatemsg']['undo callback'])) {
1724 $undo = $_SESSION['privatemsg']['undo callback'];
1725 // If the defined undo callback exists, execute it
1726 if (isset($undo['function']) && isset($undo['args'])) {
1727 call_user_func_array($undo['function'], $undo['args']);
1728 }
1729 // Return back to the site defined by the destination GET param.
1730 drupal_goto();
1731 }
1732}
1733
1734/**
1735 * Process privatemsg_list form submissions.
1736 *
1737 * Execute the chosen action on the selected messages. This function is
1738 * based on node_admin_nodes_submit().
1739 */
1740function privatemsg_list_submit($form, &$form_state) {
1741 // Load all available operation definitions.
1742 $operations = module_invoke_all('privatemsg_thread_operations');
1743
1744 // Default "default" operation, which won't do anything.
1745 $operation = array('callback' => 0);
1746
1747 // Check if a valid operation has been submitted.
1748 if (isset($form_state['values']['operation']) && isset($operations[$form_state['values']['operation']])) {
1749 $operation = $operations[$form_state['values']['operation']];
1750 }
1751
1752 // Load all keys where the value is the current op.
1753 $keys = array_keys($form_state['values'], $form_state['values']['op']);
1754
1755 // The first one is op itself, we need to use the second.
1756 if (isset($keys[1]) && isset($operations[$keys[1]])) {
1757 $operation = $operations[$keys[1]];
1758 }
1759
ad541119 1760 // Filter out unchecked threads, this gives us an array of "checked" threads.
4ba95ee6
OT
1761 $threads = array_filter($form_state['values']['threads']);
1762
1763 // Only execute something if we have a valid callback and atleast one checked thread.
1764 if (!empty($operation['callback']) && !empty($threads)) {
1765 // Add in callback arguments if present.
1766 if (isset($operation['callback arguments'])) {
1767 $args = array_merge(array($threads), $operation['callback arguments']);
1768 }
1769 else {
1770 $args = array($threads);
1771 }
1772 // Execute the chosen action and pass the defined arguments.
1773 call_user_func_array($operation['callback'], $args);
1774
1775 // Check if that operation has defined a undo callback
1776 if (isset($operation['undo callback']) && $undo_function = $operation['undo callback']) {
1777 // Add in callback arguments if present.
1778 if (isset($operation['undo callback arguments'])) {
1779 $undo_args = array_merge(array($threads), $operation['undo callback arguments']);
1780 }
1781 else {
1782 $undo_args = array($threads);
1783 }
1784 // Store the undo callback in the session and display a "Undo" link.
1785 // @todo: Provide a more flexible solution for such an undo action, operation defined string for example.
1786 $_SESSION['privatemsg']['undo callback'] = array('function' => $undo_function, 'args' => $undo_args);
1787 $undo = l(t('undone'), 'messages/undo/action', array('query' => drupal_get_destination()));
1788
1789 drupal_set_message(t('The previous action can be !undo.', array('!undo' => $undo)));
1790 }
1791 }
1792}
1793
1794/**
22107330 1795 * Delete or restore one or multiple threads.
4ba95ee6
OT
1796 *
1797 * @param $threads
22107330 1798 * Array with thread id's or a single thread id.
4ba95ee6 1799 * @param $delete
22107330 1800 * Indicates if the threads should be deleted or restored. 1 => delete, 0 => restore.
4ba95ee6
OT
1801 * @param $account
1802 * User object for which the threads should be deleted, defaults to the current user.
1803 */
1804function privatemsg_thread_change_delete($threads, $delete, $account = NULL) {
1805 if (!is_array($threads)) {
1806 $threads = array($threads);
1807 }
1808 if (empty($account)) {
1809 global $user;
1810 $account = drupal_clone($user);
1811 }
1812
1813 // Merge status and uid with the threads list. array_merge() will not overwrite/ignore thread_id 1.
1814 $params = array_merge(array($delete, $account->uid), $threads);
1815
1816 // Load all messages of those threads including the deleted.
1817 $query = _privatemsg_assemble_query('messages', $threads, $account, TRUE);
1818 $result = db_query($query['query']);
1819
1820 // Delete each message. We need to do that to trigger the delete hook.
1821 while ($row = db_fetch_array($result)) {
1822 privatemsg_message_change_delete($row['mid'], $delete, $account);
1823 }
1824
1825 if ($delete) {
1826 drupal_set_message(t('Deleted %count threads.', array('%count' => count($threads))));
1827 }
1828 else {
1829 drupal_set_message(t('Restored %count threads.', array('%count' => count($threads))));
1830 }
1831}
1832
1833/**
1834 * Implementation of hook_privatemsg_thread_operations().
1835 */
1836function privatemsg_privatemsg_thread_operations() {
1837 $operations = array(
1838 'mark as read' => array(
1839 'label' => t('Mark as read'),
1840 'callback' => 'privatemsg_thread_change_status',
1841 'callback arguments' => array('status' => PRIVATEMSG_READ),
1842 'undo callback' => 'privatemsg_thread_change_status',
1843 'undo callback arguments' => array('status' => PRIVATEMSG_UNREAD),
1844 ),
1845 'mark as unread' => array(
1846 'label' => t('Mark as unread'),
1847 'callback' => 'privatemsg_thread_change_status',
1848 'callback arguments' => array('status' => PRIVATEMSG_UNREAD),
1849 'undo callback' => 'privatemsg_thread_change_status',
1850 'undo callback arguments' => array('status' => PRIVATEMSG_READ),
1851 ),
1852 'delete' => array(
1853 'callback' => 'privatemsg_thread_change_delete',
1854 'callback arguments' => array('delete' => 1),
1855 'undo callback' => 'privatemsg_thread_change_delete',
1856 'undo callback arguments' => array('delete' => 0),
1857 ),
1858 );
1859 return $operations;
1860}