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