Completed
Pull Request — master (#3325)
by Emanuele
11:19
created

PersonalMessage.subs.php ➔ sendpm()   F

Complexity

Conditions 87
Paths > 20000

Size

Total Lines 410

Duplication

Lines 13
Ratio 3.17 %

Code Coverage

Tests 0
CRAP Score 7656

Importance

Changes 0
Metric Value
cc 87
nc 429496.7295
nop 6
dl 13
loc 410
rs 0
c 0
b 0
f 0
ccs 0
cts 316
cp 0
crap 7656

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file handles tasks related to personal messages. It performs all
5
 * the necessary (database updates, statistics updates) to add, delete, mark
6
 * etc personal messages.
7
 *
8
 * The functions in this file do NOT check permissions.
9
 *
10
 * @name      ElkArte Forum
11
 * @copyright ElkArte Forum contributors
12
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
13
 *
14
 * This file contains code covered by:
15
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
16
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
17
 *
18
 * @version 1.1.4
19
 *
20
 */
21
22
/**
23
 * Loads information about the users personal message limit.
24
 *
25
 * @package PersonalMessage
26
 */
27
function loadMessageLimit()
28
{
29
	global $user_info;
30
31
	$db = database();
32
33
	$message_limit = 0;
34
	if ($user_info['is_admin'])
35
		$message_limit = 0;
36
	elseif (!Cache::instance()->getVar($message_limit, 'msgLimit:' . $user_info['id'], 360))
37
	{
38
		$request = $db->query('', '
39
			SELECT
40
				MAX(max_messages) AS top_limit, MIN(max_messages) AS bottom_limit
41
			FROM {db_prefix}membergroups
42
			WHERE id_group IN ({array_int:users_groups})',
43
			array(
44
				'users_groups' => $user_info['groups'],
45
			)
46
		);
47
		list ($maxMessage, $minMessage) = $db->fetch_row($request);
48
		$db->free_result($request);
49
50
		$message_limit = $minMessage == 0 ? 0 : $maxMessage;
51
52
		// Save us doing it again!
53
		Cache::instance()->put('msgLimit:' . $user_info['id'], $message_limit, 360);
54
	}
55
56
	return $message_limit;
57
}
58
59
/**
60
 * Loads the count of messages on a per label basis.
61
 *
62
 * @param $labels mixed[] array of labels that we are calculating the message count
63
 * @package PersonalMessage
64
 */
65
function loadPMLabels($labels)
66
{
67
	global $user_info;
68
69
	$db = database();
70
71
	// Looks like we need to reseek!
72
	$result = $db->query('', '
73
		SELECT
74
			labels, is_read, COUNT(*) AS num
75
		FROM {db_prefix}pm_recipients
76
		WHERE id_member = {int:current_member}
77
			AND deleted = {int:not_deleted}
78
		GROUP BY labels, is_read',
79
		array(
80
			'current_member' => $user_info['id'],
81
			'not_deleted' => 0,
82
		)
83
	);
84
	while ($row = $db->fetch_assoc($result))
85
	{
86
		$this_labels = explode(',', $row['labels']);
87
		foreach ($this_labels as $this_label)
88
		{
89
			$labels[(int) $this_label]['messages'] += $row['num'];
90
91
			if (!($row['is_read'] & 1))
92
			{
93
				$labels[(int) $this_label]['unread_messages'] += $row['num'];
94
			}
95
		}
96
	}
97
	$db->free_result($result);
98
99
	// Store it please!
100
	Cache::instance()->put('labelCounts:' . $user_info['id'], $labels, 720);
101
102
	return $labels;
103
}
104
105
/**
106
 * Get the number of PMs.
107
 *
108
 * @package PersonalMessage
109
 * @param bool $descending
110
 * @param int|null $pmID
111
 * @param string $labelQuery
112
 * @return int
113
 */
114
function getPMCount($descending = false, $pmID = null, $labelQuery = '')
115
{
116
	global $user_info, $context;
117
118
	$db = database();
119
120
	// Figure out how many messages there are.
121
	if ($context['folder'] == 'sent')
122
	{
123
		$request = $db->query('', '
124
			SELECT
125
				COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT id_pm_head' : '*') . ')
126
			FROM {db_prefix}personal_messages
127
			WHERE id_member_from = {int:current_member}
128
				AND deleted_by_sender = {int:not_deleted}' . ($pmID !== null ? '
129
				AND id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}' : ''),
130
			array(
131
				'current_member' => $user_info['id'],
132
				'not_deleted' => 0,
133
				'id_pm' => $pmID,
134
			)
135
		);
136
	}
137
	else
138
	{
139
		$request = $db->query('', '
140
			SELECT
141
				COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
142
			FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
143
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . '
144
			WHERE pmr.id_member = {int:current_member}
145
				AND pmr.deleted = {int:not_deleted}' . $labelQuery . ($pmID !== null ? '
146
				AND pmr.id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}' : ''),
147
			array(
148
				'current_member' => $user_info['id'],
149
				'not_deleted' => 0,
150
				'id_pm' => $pmID,
151
			)
152
		);
153
	}
154
155
	list ($count) = $db->fetch_row($request);
156
	$db->free_result($request);
157
158
	return $count;
159
}
160
161
/**
162
 * Delete the specified personal messages.
163
 *
164
 * @package PersonalMessage
165
 * @param int[]|null $personal_messages array of pm ids
166
 * @param string|null $folder = null
167
 * @param int|int[]|null $owner = null
168
 */
169
function deleteMessages($personal_messages, $folder = null, $owner = null)
170
{
171
	global $user_info;
172
173
	$db = database();
174
175
	if ($owner === null)
176
		$owner = array($user_info['id']);
177
	elseif (empty($owner))
178
		return;
179
	elseif (!is_array($owner))
180
		$owner = array($owner);
181
182
	if ($personal_messages !== null)
183
	{
184
		if (empty($personal_messages) || !is_array($personal_messages))
185
			return;
186
187
		foreach ($personal_messages as $index => $delete_id)
188
			$personal_messages[$index] = (int) $delete_id;
189
190
		$where = '
191
				AND id_pm IN ({array_int:pm_list})';
192
	}
193
	else
194
		$where = '';
195
196
	if ($folder == 'sent' || $folder === null)
197
	{
198
		$db->query('', '
199
			UPDATE {db_prefix}personal_messages
200
			SET deleted_by_sender = {int:is_deleted}
201
			WHERE id_member_from IN ({array_int:member_list})
202
				AND deleted_by_sender = {int:not_deleted}' . $where,
203
			array(
204
				'member_list' => $owner,
205
				'is_deleted' => 1,
206
				'not_deleted' => 0,
207
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
208
			)
209
		);
210
	}
211
	if ($folder != 'sent' || $folder === null)
212
	{
213
		// Calculate the number of messages each member's gonna lose...
214
		$request = $db->query('', '
215
			SELECT
216
				id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read
217
			FROM {db_prefix}pm_recipients
218
			WHERE id_member IN ({array_int:member_list})
219
				AND deleted = {int:not_deleted}' . $where . '
220
			GROUP BY id_member, is_read',
221
			array(
222
				'member_list' => $owner,
223
				'not_deleted' => 0,
224
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
225
			)
226
		);
227
		require_once(SUBSDIR . '/Members.subs.php');
228
		// ...And update the statistics accordingly - now including unread messages!.
229
		while ($row = $db->fetch_assoc($request))
230
		{
231
			if ($row['is_read'])
232
				updateMemberData($row['id_member'], array('personal_messages' => $where == '' ? 0 : 'personal_messages - ' . $row['num_deleted_messages']));
233
			else
234
				updateMemberData($row['id_member'], array('personal_messages' => $where == '' ? 0 : 'personal_messages - ' . $row['num_deleted_messages'], 'unread_messages' => $where == '' ? 0 : 'unread_messages - ' . $row['num_deleted_messages']));
235
236
			// If this is the current member we need to make their message count correct.
237
			if ($user_info['id'] == $row['id_member'])
238
			{
239
				$user_info['messages'] -= $row['num_deleted_messages'];
240
				if (!($row['is_read']))
241
					$user_info['unread_messages'] -= $row['num_deleted_messages'];
242
			}
243
		}
244
		$db->free_result($request);
245
246
		// Do the actual deletion.
247
		$db->query('', '
248
			UPDATE {db_prefix}pm_recipients
249
			SET deleted = {int:is_deleted}
250
			WHERE id_member IN ({array_int:member_list})
251
				AND deleted = {int:not_deleted}' . $where,
252
			array(
253
				'member_list' => $owner,
254
				'is_deleted' => 1,
255
				'not_deleted' => 0,
256
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
257
			)
258
		);
259
	}
260
261
	// If sender and recipients all have deleted their message, it can be removed.
262
	$request = $db->query('', '
263
		SELECT
264
			pm.id_pm AS sender, pmr.id_pm
265
		FROM {db_prefix}personal_messages AS pm
266
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted})
267
		WHERE pm.deleted_by_sender = {int:is_deleted}
268
			' . str_replace('id_pm', 'pm.id_pm', $where) . '
269
		GROUP BY sender, pmr.id_pm
270
		HAVING pmr.id_pm IS null',
271
		array(
272
			'not_deleted' => 0,
273
			'is_deleted' => 1,
274
			'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
275
		)
276
	);
277
	$remove_pms = array();
278
	while ($row = $db->fetch_assoc($request))
279
		$remove_pms[] = $row['sender'];
280
	$db->free_result($request);
281
282
	if (!empty($remove_pms))
283
	{
284
		$db->query('', '
285
			DELETE FROM {db_prefix}personal_messages
286
			WHERE id_pm IN ({array_int:pm_list})',
287
			array(
288
				'pm_list' => $remove_pms,
289
			)
290
		);
291
292
		$db->query('', '
293
			DELETE FROM {db_prefix}pm_recipients
294
			WHERE id_pm IN ({array_int:pm_list})',
295
			array(
296
				'pm_list' => $remove_pms,
297
			)
298
		);
299
	}
300
301
	// Any cached numbers may be wrong now.
302
	Cache::instance()->put('labelCounts:' . $user_info['id'], null, 720);
303
}
304
305
/**
306
 * Mark the specified personal messages read.
307
 *
308
 * @package PersonalMessage
309
 * @param int[]|int|null $personal_messages null or array of pm ids
310
 * @param string|null $label = null, if label is set, only marks messages with that label
311
 * @param int|null $owner = null, if owner is set, marks messages owned by that member id
312
 */
313
function markMessages($personal_messages = null, $label = null, $owner = null)
314
{
315
	global $user_info;
316
317
	$db = database();
318
319
	if ($owner === null)
320
		$owner = $user_info['id'];
321
322
	if (!is_null($personal_messages) && !is_array($personal_messages))
323
		$personal_messages = array($personal_messages);
324
325
	$db->query('', '
326
		UPDATE {db_prefix}pm_recipients
327
		SET is_read = is_read | 1
328
		WHERE id_member = {int:id_member}
329
			AND NOT (is_read & 1 >= 1)' . ($label === null ? '' : '
330
			AND FIND_IN_SET({string:label}, labels) != 0') . ($personal_messages !== null ? '
331
			AND id_pm IN ({array_int:personal_messages})' : ''),
332
		array(
333
			'personal_messages' => $personal_messages,
334
			'id_member' => $owner,
335
			'label' => $label,
336
		)
337
	);
338
339
	// If something wasn't marked as read, get the number of unread messages remaining.
340
	if ($db->affected_rows() > 0)
341
		updatePMMenuCounts($owner);
342
}
343
344
/**
345
 * Mark the specified personal messages as unread.
346
 *
347
 * @package PersonalMessage
348
 * @param int|int[] $personal_messages
349
 */
350
function markMessagesUnread($personal_messages)
351
{
352
	global $user_info;
353
354
	$db = database();
355
356
	if (empty($personal_messages))
357
		return;
358
359
	if (!is_array($personal_messages))
360
		$personal_messages = array($personal_messages);
361
362
	$owner = $user_info['id'];
363
364
	// Flip the "read" bit on this
365
	$db->query('', '
366
		UPDATE {db_prefix}pm_recipients
367
		SET is_read = is_read & 2
368
		WHERE id_member = {int:id_member}
369
			AND (is_read & 1 >= 1)
370
			AND id_pm IN ({array_int:personal_messages})',
371
		array(
372
			'personal_messages' => $personal_messages,
373
			'id_member' => $owner,
374
		)
375
	);
376
377
	// If something was marked unread, update the number of unread messages remaining.
378
	if ($db->affected_rows() > 0)
379
		updatePMMenuCounts($owner);
380
}
381
382
/**
383
 * Updates the number of unread messages for a user
384
 *
385
 * - Updates the per label totals as well as the overall total
386
 *
387
 * @package PersonalMessage
388
 * @param int $owner
389
 */
390
function updatePMMenuCounts($owner)
391
{
392
	global $user_info, $context;
393
394
	$db = database();
395
396
	if ($owner == $user_info['id'])
397
	{
398
		foreach ($context['labels'] as $label)
399
			$context['labels'][(int) $label['id']]['unread_messages'] = 0;
400
	}
401
402
	$result = $db->query('', '
403
		SELECT
404
			labels, COUNT(*) AS num
405
		FROM {db_prefix}pm_recipients
406
		WHERE id_member = {int:id_member}
407
			AND NOT (is_read & 1 >= 1)
408
			AND deleted = {int:is_not_deleted}
409
		GROUP BY labels',
410
		array(
411
			'id_member' => $owner,
412
			'is_not_deleted' => 0,
413
		)
414
	);
415
	$total_unread = 0;
416
	while ($row = $db->fetch_assoc($result))
417
	{
418
		$total_unread += $row['num'];
419
420
		if ($owner != $user_info['id'])
421
			continue;
422
423
		$this_labels = explode(',', $row['labels']);
424
		foreach ($this_labels as $this_label)
425
			$context['labels'][(int) $this_label]['unread_messages'] += $row['num'];
426
	}
427
	$db->free_result($result);
428
429
	// Need to store all this.
430
	Cache::instance()->put('labelCounts:' . $owner, $context['labels'], 720);
431
	require_once(SUBSDIR . '/Members.subs.php');
432
	updateMemberData($owner, array('unread_messages' => $total_unread));
433
434
	// If it was for the current member, reflect this in the $user_info array too.
435
	if ($owner == $user_info['id'])
436
		$user_info['unread_messages'] = $total_unread;
437
}
438
439
/**
440
 * Check if the PM is available to the current user.
441
 *
442
 * @package PersonalMessage
443
 * @param int $pmID
444
 * @param string $validFor
445
 * @return boolean|null
446
 */
447
function isAccessiblePM($pmID, $validFor = 'in_or_outbox')
448
{
449
	global $user_info;
450
451
	$db = database();
452
453
	$request = $db->query('', '
454
		SELECT
455
			pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted} AS valid_for_outbox,
456
			pmr.id_pm IS NOT NULL AS valid_for_inbox
457
		FROM {db_prefix}personal_messages AS pm
458
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.id_member = {int:id_current_member} AND pmr.deleted = {int:not_deleted})
459
		WHERE pm.id_pm = {int:id_pm}
460
			AND ((pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted}) OR pmr.id_pm IS NOT NULL)',
461
		array(
462
			'id_pm' => $pmID,
463
			'id_current_member' => $user_info['id'],
464
			'not_deleted' => 0,
465
		)
466
	);
467
	if ($db->num_rows($request) === 0)
468
	{
469
		$db->free_result($request);
470
		return false;
471
	}
472
	$validationResult = $db->fetch_assoc($request);
473
	$db->free_result($request);
474
475
	switch ($validFor)
476
	{
477
		case 'inbox':
478
			return !empty($validationResult['valid_for_inbox']);
479
480
		case 'outbox':
481
			return !empty($validationResult['valid_for_outbox']);
482
483
		case 'in_or_outbox':
484
			return !empty($validationResult['valid_for_inbox']) || !empty($validationResult['valid_for_outbox']);
485
486
		default:
487
			trigger_error('Undefined validation type given', E_USER_ERROR);
488
	}
489
}
490
491
/**
492
 * Sends a personal message from the specified person to the specified people
493
 * ($from defaults to the user)
494
 *
495
 * @package PersonalMessage
496
 * @param mixed[] $recipients - an array containing the arrays 'to' and 'bcc', both containing id_member's.
497
 * @param string $subject - should have no slashes and no html entities
498
 * @param string $message - should have no slashes and no html entities
499
 * @param bool $store_outbox
500
 * @param mixed[]|null $from - an array with the id, name, and username of the member.
501
 * @param int $pm_head - the ID of the chain being replied to - if any.
502
 * @return mixed[] an array with log entries telling how many recipients were successful and which recipients it failed to send to.
503
 * @throws Elk_Exception
504
 */
505
function sendpm($recipients, $subject, $message, $store_outbox = true, $from = null, $pm_head = 0)
506
{
507
	global $scripturl, $txt, $user_info, $language, $modSettings, $webmaster_email;
508
509
	$db = database();
510
511
	// Make sure the PM language file is loaded, we might need something out of it.
512
	loadLanguage('PersonalMessage');
513
514
	// Needed for our email and post functions
515
	require_once(SUBSDIR . '/Mail.subs.php');
516
	require_once(SUBSDIR . '/Post.subs.php');
517
518
	// Initialize log array.
519
	$log = array(
520
		'failed' => array(),
521
		'sent' => array()
522
	);
523
524
	if ($from === null)
525
		$from = array(
526
			'id' => $user_info['id'],
527
			'name' => $user_info['name'],
528
			'username' => $user_info['username']
529
		);
530
	// Probably not needed.  /me something should be of the typer.
531
	else
532
		$user_info['name'] = $from['name'];
533
534
	// Integrated PMs
535
	call_integration_hook('integrate_personal_message', array(&$recipients, &$from, &$subject, &$message));
536
537
	// This is the one that will go in their inbox.
538
	$htmlmessage = Util::htmlspecialchars($message, ENT_QUOTES, 'UTF-8', true);
539
	preparsecode($htmlmessage);
540
	$htmlsubject = strtr(Util::htmlspecialchars($subject), array("\r" => '', "\n" => '', "\t" => ''));
541
	if (Util::strlen($htmlsubject) > 100)
542
		$htmlsubject = Util::substr($htmlsubject, 0, 100);
543
544
	// Make sure is an array
545
	if (!is_array($recipients))
0 ignored issues
show
introduced by
The condition is_array($recipients) is always true.
Loading history...
546
		$recipients = array($recipients);
547
548
	// Get a list of usernames and convert them to IDs.
549
	$usernames = array();
550
	foreach ($recipients as $rec_type => $rec)
551
	{
552
		foreach ($rec as $id => $member)
553
		{
554
			if (!is_numeric($recipients[$rec_type][$id]))
555
			{
556
				$recipients[$rec_type][$id] = Util::strtolower(trim(preg_replace('/[<>&"\'=\\\]/', '', $recipients[$rec_type][$id])));
557
				$usernames[$recipients[$rec_type][$id]] = 0;
558
			}
559
		}
560
	}
561
562
	if (!empty($usernames))
563
	{
564
		$request = $db->query('pm_find_username', '
565
			SELECT
566
				id_member, member_name
567
			FROM {db_prefix}members
568
			WHERE ' . (defined('DB_CASE_SENSITIVE') ? 'LOWER(member_name)' : 'member_name') . ' IN ({array_string:usernames})',
569
			array(
570
				'usernames' => array_keys($usernames),
571
			)
572
		);
573
		while ($row = $db->fetch_assoc($request))
574
			if (isset($usernames[Util::strtolower($row['member_name'])]))
575
				$usernames[Util::strtolower($row['member_name'])] = $row['id_member'];
576
		$db->free_result($request);
577
578
		// Replace the usernames with IDs. Drop usernames that couldn't be found.
579
		foreach ($recipients as $rec_type => $rec)
580
		{
581
			foreach ($rec as $id => $member)
582
			{
583
				if (is_numeric($recipients[$rec_type][$id]))
584
					continue;
585
586
				if (!empty($usernames[$member]))
587
					$recipients[$rec_type][$id] = $usernames[$member];
588
				else
589
				{
590
					$log['failed'][$id] = sprintf($txt['pm_error_user_not_found'], $recipients[$rec_type][$id]);
591
					unset($recipients[$rec_type][$id]);
592
				}
593
			}
594
		}
595
	}
596
597
	// Make sure there are no duplicate 'to' members.
598
	$recipients['to'] = array_unique($recipients['to']);
599
600
	// Only 'bcc' members that aren't already in 'to'.
601
	$recipients['bcc'] = array_diff(array_unique($recipients['bcc']), $recipients['to']);
602
603
	// Combine 'to' and 'bcc' recipients.
604
	$all_to = array_merge($recipients['to'], $recipients['bcc']);
605
606
	// Check no-one will want it deleted right away!
607
	$request = $db->query('', '
608
		SELECT
609
			id_member, criteria, is_or
610
		FROM {db_prefix}pm_rules
611
		WHERE id_member IN ({array_int:to_members})
612
			AND delete_pm = {int:delete_pm}',
613
		array(
614
			'to_members' => $all_to,
615
			'delete_pm' => 1,
616
		)
617
	);
618
	$deletes = array();
619
	// Check whether we have to apply anything...
620
	while ($row = $db->fetch_assoc($request))
621
	{
622
		$criteria = Util::unserialize($row['criteria']);
623
624
		// Note we don't check the buddy status, cause deletion from buddy = madness!
625
		$delete = false;
626
		foreach ($criteria as $criterium)
627
		{
628
			if (($criterium['t'] == 'mid' && $criterium['v'] == $from['id']) || ($criterium['t'] == 'gid' && in_array($criterium['v'], $user_info['groups'])) || ($criterium['t'] == 'sub' && strpos($subject, $criterium['v']) !== false) || ($criterium['t'] == 'msg' && strpos($message, $criterium['v']) !== false))
629
				$delete = true;
630
			// If we're adding and one criteria don't match then we stop!
631
			elseif (!$row['is_or'])
632
			{
633
				$delete = false;
634
				break;
635
			}
636
		}
637
		if ($delete)
638
			$deletes[$row['id_member']] = 1;
639
	}
640
	$db->free_result($request);
641
642
	// Load the membergroup message limits.
643
	static $message_limit_cache = array();
644
	if (!allowedTo('moderate_forum') && empty($message_limit_cache))
645
	{
646
		$request = $db->query('', '
647
			SELECT
648
				id_group, max_messages
649
			FROM {db_prefix}membergroups',
650
			array(
651
			)
652
		);
653
		while ($row = $db->fetch_assoc($request))
654
			$message_limit_cache[$row['id_group']] = $row['max_messages'];
655
		$db->free_result($request);
656
	}
657
658
	// Load the groups that are allowed to read PMs.
659
	// @todo move into a separate function on $permission.
660
	$allowed_groups = array();
661
	$disallowed_groups = array();
662
	$request = $db->query('', '
663
		SELECT
664
			id_group, add_deny
665
		FROM {db_prefix}permissions
666
		WHERE permission = {string:read_permission}',
667
		array(
668
			'read_permission' => 'pm_read',
669
		)
670
	);
671
672
	while ($row = $db->fetch_assoc($request))
673
	{
674
		if (empty($row['add_deny']))
675
			$disallowed_groups[] = $row['id_group'];
676
		else
677
			$allowed_groups[] = $row['id_group'];
678
	}
679
680
	$db->free_result($request);
681
682
	if (empty($modSettings['permission_enable_deny']))
683
		$disallowed_groups = array();
684
685
	$request = $db->query('', '
686
		SELECT
687
			member_name, real_name, id_member, email_address, lngfile,
688
			pm_email_notify, personal_messages,' . (allowedTo('moderate_forum') ? ' 0' : '
689
			(receive_from = {int:admins_only}' . (empty($modSettings['enable_buddylist']) ? '' : ' OR
690
			(receive_from = {int:buddies_only} AND FIND_IN_SET({string:from_id}, buddy_list) = 0) OR
691
			(receive_from = {int:not_on_ignore_list} AND FIND_IN_SET({string:from_id}, pm_ignore_list) != 0)') . ')') . ' AS ignored,
692
			FIND_IN_SET({string:from_id}, buddy_list) != 0 AS is_buddy, is_activated,
693
			additional_groups, id_group, id_post_group
694
		FROM {db_prefix}members
695
		WHERE id_member IN ({array_int:recipients})
696
		ORDER BY lngfile
697
		LIMIT {int:count_recipients}',
698
		array(
699
			'not_on_ignore_list' => 1,
700
			'buddies_only' => 2,
701
			'admins_only' => 3,
702
			'recipients' => $all_to,
703
			'count_recipients' => count($all_to),
704
			'from_id' => $from['id'],
705
		)
706
	);
707
	$notifications = array();
708
	while ($row = $db->fetch_assoc($request))
709
	{
710
		// Don't do anything for members to be deleted!
711
		if (isset($deletes[$row['id_member']]))
712
			continue;
713
714
		// We need to know this members groups.
715
		$groups = explode(',', $row['additional_groups']);
716
		$groups[] = $row['id_group'];
717
		$groups[] = $row['id_post_group'];
718
719
		$message_limit = -1;
720
721
		// For each group see whether they've gone over their limit - assuming they're not an admin.
722
		if (!in_array(1, $groups))
723
		{
724
			foreach ($groups as $id)
725
			{
726
				if (isset($message_limit_cache[$id]) && $message_limit != 0 && $message_limit < $message_limit_cache[$id])
727
					$message_limit = $message_limit_cache[$id];
728
			}
729
730
			if ($message_limit > 0 && $message_limit <= $row['personal_messages'])
731
			{
732
				$log['failed'][$row['id_member']] = sprintf($txt['pm_error_data_limit_reached'], $row['real_name']);
733
				unset($all_to[array_search($row['id_member'], $all_to)]);
734
				continue;
735
			}
736
737
			// Do they have any of the allowed groups?
738
			if (count(array_intersect($allowed_groups, $groups)) == 0 || count(array_intersect($disallowed_groups, $groups)) != 0)
739
			{
740
				$log['failed'][$row['id_member']] = sprintf($txt['pm_error_user_cannot_read'], $row['real_name']);
741
				unset($all_to[array_search($row['id_member'], $all_to)]);
742
				continue;
743
			}
744
		}
745
746
		// Note that PostgreSQL can return a lowercase t/f for FIND_IN_SET
747
		if (!empty($row['ignored']) && $row['ignored'] != 'f' && $row['id_member'] != $from['id'])
748
		{
749
			$log['failed'][$row['id_member']] = sprintf($txt['pm_error_ignored_by_user'], $row['real_name']);
750
			unset($all_to[array_search($row['id_member'], $all_to)]);
751
			continue;
752
		}
753
754
		// If the receiving account is banned (>=10) or pending deletion (4), refuse to send the PM.
755
		if ($row['is_activated'] >= 10 || ($row['is_activated'] == 4 && !$user_info['is_admin']))
756
		{
757
			$log['failed'][$row['id_member']] = sprintf($txt['pm_error_user_cannot_read'], $row['real_name']);
758
			unset($all_to[array_search($row['id_member'], $all_to)]);
759
			continue;
760
		}
761
762
		// Send a notification, if enabled - taking the buddy list into account.
763
		if (!empty($row['email_address']) && ($row['pm_email_notify'] == 1 || ($row['pm_email_notify'] > 1 && (!empty($modSettings['enable_buddylist']) && $row['is_buddy']))) && $row['is_activated'] == 1)
764
			$notifications[empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']][] = $row['email_address'];
765
766
		$log['sent'][$row['id_member']] = sprintf(isset($txt['pm_successfully_sent']) ? $txt['pm_successfully_sent'] : '', $row['real_name']);
767
	}
768
	$db->free_result($request);
769
770
	// Only 'send' the message if there are any recipients left.
771
	if (empty($all_to))
772
		return $log;
773
774
	// Track the pm count for our stats
775
	if (!empty($modSettings['trackStats']))
776
		trackStats(array('pm' => '+'));
777
778
	// Insert the message itself and then grab the last insert id.
779
	$db->insert('',
780
		'{db_prefix}personal_messages',
781
		array(
782
			'id_pm_head' => 'int', 'id_member_from' => 'int', 'deleted_by_sender' => 'int',
783
			'from_name' => 'string-255', 'msgtime' => 'int', 'subject' => 'string-255', 'body' => 'string-65534',
784
		),
785
		array(
786
			$pm_head, $from['id'], ($store_outbox ? 0 : 1),
787
			$from['username'], time(), $htmlsubject, $htmlmessage,
788
		),
789
		array('id_pm')
790
	);
791
	$id_pm = $db->insert_id('{db_prefix}personal_messages', 'id_pm');
792
793
	// Add the recipients.
794
	$to_list = array();
795
	if (!empty($id_pm))
796
	{
797
		// If this is new we need to set it part of it's own conversation.
798
		if (empty($pm_head))
799
			$db->query('', '
800
				UPDATE {db_prefix}personal_messages
801
				SET id_pm_head = {int:id_pm_head}
802
				WHERE id_pm = {int:id_pm_head}',
803
				array(
804
					'id_pm_head' => $id_pm,
805
				)
806
			);
807
808
		// Some people think manually deleting personal_messages is fun... it's not. We protect against it though :)
809
		$db->query('', '
810
			DELETE FROM {db_prefix}pm_recipients
811
			WHERE id_pm = {int:id_pm}',
812
			array(
813
				'id_pm' => $id_pm,
814
			)
815
		);
816
817
		$insertRows = array();
818
		foreach ($all_to as $to)
819
		{
820
			$insertRows[] = array($id_pm, $to, in_array($to, $recipients['bcc']) ? 1 : 0, isset($deletes[$to]) ? 1 : 0, 1);
821
			if (!in_array($to, $recipients['bcc']))
822
				$to_list[] = $to;
823
		}
824
825
		$db->insert('insert',
826
			'{db_prefix}pm_recipients',
827
			array(
828
				'id_pm' => 'int', 'id_member' => 'int', 'bcc' => 'int', 'deleted' => 'int', 'is_new' => 'int'
829
			),
830
			$insertRows,
831
			array('id_pm', 'id_member')
832
		);
833
	}
834
835
	$maillist = !empty($modSettings['maillist_enabled']) && !empty($modSettings['pbe_pm_enabled']);
836
837
	// If they have post by email enabled, override disallow_sendBody
838
	if (!$maillist && !empty($modSettings['disallow_sendBody']))
839
	{
840
		$message = '';
841
		$subject = censor($subject);
842
	}
843
	else
844
	{
845
		require_once(SUBSDIR . '/Emailpost.subs.php');
846
		pbe_prepare_text($message, $subject);
847
	}
848
849
	$to_names = array();
850
	if (count($to_list) > 1)
851
	{
852
		require_once(SUBSDIR . '/Members.subs.php');
853
		$result = getBasicMemberData($to_list);
854
		foreach ($result as $row)
855
			$to_names[] = un_htmlspecialchars($row['real_name']);
856
	}
857
858
	$replacements = array(
859
		'SUBJECT' => $subject,
860
		'MESSAGE' => $message,
861
		'SENDER' => un_htmlspecialchars($from['name']),
862
		'READLINK' => $scripturl . '?action=pm;pmsg=' . $id_pm . '#msg' . $id_pm,
863
		'REPLYLINK' => $scripturl . '?action=pm;sa=send;f=inbox;pmsg=' . $id_pm . ';quote;u=' . $from['id'],
864
		'TOLIST' => implode(', ', $to_names),
865
	);
866
867
	// Select the right template
868
	$email_template = ($maillist && empty($modSettings['disallow_sendBody']) ? 'pbe_' : '') . 'new_pm' . (empty($modSettings['disallow_sendBody']) ? '_body' : '') . (!empty($to_names) ? '_tolist' : '');
869
870
	foreach ($notifications as $lang => $notification_list)
871
	{
872
		// Using maillist functionality
873
		if ($maillist)
874
		{
875
			$sender_details = query_sender_wrapper($from['id']);
876
			$from_wrapper = !empty($modSettings['maillist_mail_from']) ? $modSettings['maillist_mail_from'] : (empty($modSettings['maillist_sitename_address']) ? $webmaster_email : $modSettings['maillist_sitename_address']);
877
878
			// Add in the signature
879
			$replacements['SIGNATURE'] = $sender_details['signature'];
880
881
			// And off it goes, looking a bit more personal
882
			$mail = loadEmailTemplate($email_template, $replacements, $lang);
883
			$reference = !empty($pm_head) ? $pm_head : null;
884
			sendmail($notification_list, $mail['subject'], $mail['body'], $from['name'], 'p' . $id_pm, false, 2, null, true, $from_wrapper, $reference);
885
		}
886
		else
887
		{
888
			// Off the notification email goes!
889
			$mail = loadEmailTemplate($email_template, $replacements, $lang);
890
			sendmail($notification_list, $mail['subject'], $mail['body'], null, 'p' . $id_pm, false, 2, null, true);
891
		}
892
	}
893
894
	// Integrated After PMs
895
	call_integration_hook('integrate_personal_message_after', array(&$id_pm, &$log, &$recipients, &$from, &$subject, &$message));
896
897
	// Back to what we were on before!
898
	loadLanguage('index+PersonalMessage');
899
900
	// Add one to their unread and read message counts.
901
	foreach ($all_to as $k => $id)
902
	{
903
		if (isset($deletes[$id]))
904
			unset($all_to[$k]);
905
	}
906
907
	if (!empty($all_to))
908
	{
909
		require_once(SUBSDIR . '/Members.subs.php');
910
		updateMemberData($all_to, array('personal_messages' => '+', 'unread_messages' => '+', 'new_pm' => 1));
911
	}
912
913
	return $log;
914
}
915
916
/**
917
 * Load personal messages.
918
 *
919
 * This function loads messages considering the options given, an array of:
920
 * - 'display_mode' - the PMs display mode (i.e. conversation, all)
921
 * - 'is_postgres' - (temporary) boolean to allow choice of PostgreSQL-specific sorting query
922
 * - 'sort_by_query' - query to sort by
923
 * - 'descending' - whether to sort descending
924
 * - 'sort_by' - field to sort by
925
 * - 'pmgs' - personal message id (if any). Note: it may not be set.
926
 * - 'label_query' - query by labels
927
 * - 'start' - start id, if any
928
 *
929
 * @package PersonalMessage
930
 * @param mixed[] $pm_options options for loading
931
 * @param int $id_member id member
932
 */
933
function loadPMs($pm_options, $id_member)
934
{
935
	global $options;
936
937
	$db = database();
938
939
	// First work out what messages we need to see - if grouped is a little trickier...
940
	// Conversation mode
941
	if ($pm_options['display_mode'] == 2)
942
	{
943
		// On a non-default sort, when using PostgreSQL we have to do a harder sort.
944
		if ($db->db_title() == 'PostgreSQL' && $pm_options['sort_by_query'] != 'pm.id_pm')
945
		{
946
			$sub_request = $db->query('', '
947
				SELECT
948
					MAX({raw:sort}) AS sort_param, pm.id_pm_head
949
				FROM {db_prefix}personal_messages AS pm' . ($pm_options['folder'] == 'sent' ? ($pm_options['sort_by'] == 'name' ? '
950
					LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
951
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
952
						AND pmr.id_member = {int:current_member}
953
						AND pmr.deleted = {int:not_deleted}
954
						' . $pm_options['label_query'] . ')') . ($pm_options['sort_by'] == 'name' ? ('
955
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})') : '') . '
956
				WHERE ' . ($pm_options['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
957
					AND pm.deleted_by_sender = {int:not_deleted}' : '1=1') . (empty($pm_options['pmsg']) ? '' : '
958
					AND pm.id_pm = {int:id_pm}') . '
959
				GROUP BY pm.id_pm_head
960
				ORDER BY sort_param' . ($pm_options['descending'] ? ' DESC' : ' ASC') . (empty($pm_options['pmsg']) ? '
961
				LIMIT ' . $pm_options['start'] . ', ' . $pm_options['limit'] : ''),
962
				array(
963
					'current_member' => $id_member,
964
					'not_deleted' => 0,
965
					'id_member' => $pm_options['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
966
					'id_pm' => isset($pm_options['pmsg']) ? $pm_options['pmsg'] : '0',
967
					'sort' => $pm_options['sort_by_query'],
968
				)
969
			);
970
			$sub_pms = array();
971
			while ($row = $db->fetch_assoc($sub_request))
972
				$sub_pms[$row['id_pm_head']] = $row['sort_param'];
973
			$db->free_result($sub_request);
974
975
			// Now we use those results in the next query
976
			$request = $db->query('', '
977
				SELECT
978
					pm.id_pm AS id_pm, pm.id_pm_head
979
				FROM {db_prefix}personal_messages AS pm' . ($pm_options['folder'] == 'sent' ? ($pm_options['sort_by'] == 'name' ? '
980
					LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
981
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
982
						AND pmr.id_member = {int:current_member}
983
						AND pmr.deleted = {int:not_deleted}
984
						' . $pm_options['label_query'] . ')') . ($pm_options['sort_by'] == 'name' ? ('
985
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})') : '') . '
986
				WHERE ' . (empty($sub_pms) ? '0=1' : 'pm.id_pm IN ({array_int:pm_list})') . '
987
				ORDER BY ' . ($pm_options['sort_by_query'] == 'pm.id_pm' && $pm_options['folder'] != 'sent' ? 'id_pm' : '{raw:sort}') . ($pm_options['descending'] ? ' DESC' : ' ASC') . (empty($pm_options['pmsg']) ? '
988
				LIMIT ' . $pm_options['start'] . ', ' . $pm_options['limit'] : ''),
989
				array(
990
					'current_member' => $id_member,
991
					'pm_list' => array_keys($sub_pms),
992
					'not_deleted' => 0,
993
					'sort' => $pm_options['sort_by_query'],
994
					'id_member' => $pm_options['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
995
				)
996
			);
997
		}
998
		// Otherwise we can just use the the pm_conversation_list option
999
		else
1000
		{
1001
			$request = $db->query('pm_conversation_list', '
1002
				SELECT
1003
					MAX(pm.id_pm) AS id_pm, pm.id_pm_head
1004
				FROM {db_prefix}personal_messages AS pm' . ($pm_options['folder'] == 'sent' ? ($pm_options['sort_by'] == 'name' ? '
1005
					LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
1006
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
1007
						AND pmr.id_member = {int:current_member}
1008
						AND pmr.deleted = {int:deleted_by}
1009
						' . $pm_options['label_query'] . ')') . ($pm_options['sort_by'] == 'name' ? ('
1010
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
1011
				WHERE ' . ($pm_options['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
1012
					AND pm.deleted_by_sender = {int:deleted_by}' : '1=1') . (empty($pm_options['pmsg']) ? '' : '
1013
					AND pm.id_pm = {int:pmsg}') . '
1014
				GROUP BY pm.id_pm_head
1015
				ORDER BY ' . ($pm_options['sort_by_query'] == 'pm.id_pm' && $pm_options['folder'] != 'sent' ? 'id_pm' : '{raw:sort}') . ($pm_options['descending'] ? ' DESC' : ' ASC') . (isset($pm_options['pmsg']) ? '
1016
				LIMIT ' . $pm_options['start'] . ', ' . $pm_options['limit'] : ''),
1017
				array(
1018
					'current_member' => $id_member,
1019
					'deleted_by' => 0,
1020
					'sort' => $pm_options['sort_by_query'],
1021
					'pm_member' => $pm_options['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
1022
					'pmsg' => isset($pm_options['pmsg']) ? (int) $pm_options['pmsg'] : 0,
1023
				)
1024
			);
1025
		}
1026
	}
1027
	// If not in conversation view, then this is kinda simple!
1028
	else
1029
	{
1030
		// @todo SLOW This query uses a filesort. (inbox only.)
1031
		$request = $db->query('', '
1032
			SELECT
1033
				pm.id_pm, pm.id_pm_head, pm.id_member_from
1034
			FROM {db_prefix}personal_messages AS pm' . ($pm_options['folder'] == 'sent' ? '' . ($pm_options['sort_by'] == 'name' ? '
1035
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
1036
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
1037
					AND pmr.id_member = {int:current_member}
1038
					AND pmr.deleted = {int:is_deleted}
1039
					' . $pm_options['label_query'] . ')') . ($pm_options['sort_by'] == 'name' ? ('
1040
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
1041
			WHERE ' . ($pm_options['folder'] == 'sent' ? 'pm.id_member_from = {raw:current_member}
1042
				AND pm.deleted_by_sender = {int:is_deleted}' : '1=1') . (empty($pm_options['pmsg']) ? '' : '
1043
				AND pm.id_pm = {int:pmsg}') . '
1044
			ORDER BY ' . ($pm_options['sort_by_query'] == 'pm.id_pm' && $pm_options['folder'] != 'sent' ? 'pmr.id_pm' : '{raw:sort}') . ($pm_options['descending'] ? ' DESC' : ' ASC') . (isset($pm_options['pmsg']) ? '
1045
			LIMIT ' . $pm_options['start'] . ', ' . $pm_options['limit'] : ''),
1046
			array(
1047
				'current_member' => $id_member,
1048
				'is_deleted' => 0,
1049
				'sort' => $pm_options['sort_by_query'],
1050
				'pm_member' => $pm_options['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
1051
				'pmsg' => isset($pm_options['pmsg']) ? (int) $pm_options['pmsg'] : 0,
1052
			)
1053
		);
1054
	}
1055
	// Load the id_pms and initialize recipients.
1056
	$pms = array();
1057
	$lastData = array();
1058
	$posters = $pm_options['folder'] == 'sent' ? array($id_member) : array();
1059
	$recipients = array();
1060
	while ($row = $db->fetch_assoc($request))
1061
	{
1062
		if (!isset($recipients[$row['id_pm']]))
1063
		{
1064
			if (isset($row['id_member_from']))
1065
				$posters[$row['id_pm']] = $row['id_member_from'];
1066
1067
			$pms[$row['id_pm']] = $row['id_pm'];
1068
1069
			$recipients[$row['id_pm']] = array(
1070
				'to' => array(),
1071
				'bcc' => array()
1072
			);
1073
		}
1074
1075
		// Keep track of the last message so we know what the head is without another query!
1076
		if ((empty($pm_options['pmid']) && (empty($options['view_newest_pm_first']) || !isset($lastData))) || empty($lastData) || (!empty($pm_options['pmid']) && $pm_options['pmid'] == $row['id_pm']))
1077
			$lastData = array(
1078
				'id' => $row['id_pm'],
1079
				'head' => $row['id_pm_head'],
1080
			);
1081
	}
1082
	$db->free_result($request);
1083
1084
	return array($pms, $posters, $recipients, $lastData);
1085
}
1086
1087
/**
1088
 * How many PMs have you sent lately?
1089
 *
1090
 * @package PersonalMessage
1091
 * @param int $id_member id member
1092
 * @param int $time time interval (in seconds)
1093
 */
1094
function pmCount($id_member, $time)
1095
{
1096
	$db = database();
1097
1098
	$request = $db->query('', '
1099
		SELECT
1100
			COUNT(*) AS post_count
1101
		FROM {db_prefix}personal_messages AS pm
1102
			INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
1103
		WHERE pm.id_member_from = {int:current_member}
1104
			AND pm.msgtime > {int:msgtime}',
1105
		array(
1106
			'current_member' => $id_member,
1107
			'msgtime' => time() - $time,
1108
		)
1109
	);
1110
	list ($pmCount) = $db->fetch_row($request);
1111
	$db->free_result($request);
1112
1113
	return $pmCount;
1114
}
1115
1116
/**
1117
 * This will apply rules to all unread messages.
1118
 *
1119
 * - If all_messages is set will, clearly, do it to all!
1120
 *
1121
 * @package PersonalMessage
1122
 * @param bool $all_messages = false
1123
 */
1124
function applyRules($all_messages = false)
1125
{
1126
	global $user_info, $context, $options;
1127
1128
	$db = database();
1129
1130
	// Want this - duh!
1131
	loadRules();
1132
1133
	// No rules?
1134
	if (empty($context['rules']))
1135
		return;
1136
1137
	// Just unread ones?
1138
	$ruleQuery = $all_messages ? '' : ' AND pmr.is_new = 1';
1139
1140
	// @todo Apply all should have timeout protection!
1141
	// Get all the messages that match this.
1142
	$request = $db->query('', '
1143
		SELECT
1144
			pmr.id_pm, pm.id_member_from, pm.subject, pm.body, mem.id_group, pmr.labels
1145
		FROM {db_prefix}pm_recipients AS pmr
1146
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1147
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1148
		WHERE pmr.id_member = {int:current_member}
1149
			AND pmr.deleted = {int:not_deleted}
1150
			' . $ruleQuery,
1151
		array(
1152
			'current_member' => $user_info['id'],
1153
			'not_deleted' => 0,
1154
		)
1155
	);
1156
	$actions = array();
1157
	while ($row = $db->fetch_assoc($request))
1158
	{
1159
		foreach ($context['rules'] as $rule)
1160
		{
1161
			$match = false;
1162
1163
			// Loop through all the criteria hoping to make a match.
1164
			foreach ($rule['criteria'] as $criterium)
1165
			{
1166
				if (($criterium['t'] == 'mid' && $criterium['v'] == $row['id_member_from']) || ($criterium['t'] == 'gid' && $criterium['v'] == $row['id_group']) || ($criterium['t'] == 'sub' && strpos($row['subject'], $criterium['v']) !== false) || ($criterium['t'] == 'msg' && strpos($row['body'], $criterium['v']) !== false))
1167
					$match = true;
1168
				// If we're adding and one criteria don't match then we stop!
1169
				elseif ($rule['logic'] == 'and')
1170
				{
1171
					$match = false;
1172
					break;
1173
				}
1174
			}
1175
1176
			// If we have a match the rule must be true - act!
1177
			if ($match)
1178
			{
1179
				if ($rule['delete'])
1180
					$actions['deletes'][] = $row['id_pm'];
1181
				else
1182
				{
1183
					foreach ($rule['actions'] as $ruleAction)
1184
					{
1185
						if ($ruleAction['t'] == 'lab')
1186
						{
1187
							// Get a basic pot started!
1188
							if (!isset($actions['labels'][$row['id_pm']]))
1189
								$actions['labels'][$row['id_pm']] = empty($row['labels']) ? array() : explode(',', $row['labels']);
1190
1191
							$actions['labels'][$row['id_pm']][] = $ruleAction['v'];
1192
						}
1193
					}
1194
				}
1195
			}
1196
		}
1197
	}
1198
	$db->free_result($request);
1199
1200
	// Deletes are easy!
1201
	if (!empty($actions['deletes']))
1202
		deleteMessages($actions['deletes']);
1203
1204
	// Re-label?
1205
	if (!empty($actions['labels']))
1206
	{
1207
		foreach ($actions['labels'] as $pm => $labels)
1208
		{
1209
			// Quickly check each label is valid!
1210
			$realLabels = array();
1211
			foreach ($context['labels'] as $label)
1212
				if (in_array($label['id'], $labels) && ($label['id'] != -1 || empty($options['pm_remove_inbox_label'])))
1213
					$realLabels[] = $label['id'];
1214
1215
			$db->query('', '
1216
				UPDATE {db_prefix}pm_recipients
1217
				SET labels = {string:new_labels}
1218
				WHERE id_pm = {int:id_pm}
1219
					AND id_member = {int:current_member}',
1220
				array(
1221
					'current_member' => $user_info['id'],
1222
					'id_pm' => $pm,
1223
					'new_labels' => empty($realLabels) ? '' : implode(',', $realLabels),
1224
				)
1225
			);
1226
		}
1227
	}
1228
}
1229
1230
/**
1231
 * Load up all the rules for the current user.
1232
 *
1233
 * @package PersonalMessage
1234
 * @param bool $reload = false
1235
 */
1236
function loadRules($reload = false)
1237
{
1238
	global $user_info, $context;
1239
1240
	$db = database();
1241
1242
	if (isset($context['rules']) && !$reload)
1243
		return;
1244
1245
	// This is just a simple list of "all" known rules
1246
	$context['known_rules'] = array(
1247
		// member_id == "Sender Name"
1248
		'mid',
1249
		// group_id == "Sender's Groups"
1250
		'gid',
1251
		// subject == "Message Subject Contains"
1252
		'sub',
1253
		// message == "Message Body Contains"
1254
		'msg',
1255
		// buddy == "Sender is Buddy"
1256
		'bud',
1257
	);
1258
1259
	$request = $db->query('', '
1260
		SELECT
1261
			id_rule, rule_name, criteria, actions, delete_pm, is_or
1262
		FROM {db_prefix}pm_rules
1263
		WHERE id_member = {int:current_member}',
1264
		array(
1265
			'current_member' => $user_info['id'],
1266
		)
1267
	);
1268
	$context['rules'] = array();
1269
	// Simply fill in the data!
1270
	while ($row = $db->fetch_assoc($request))
1271
	{
1272
		$context['rules'][$row['id_rule']] = array(
1273
			'id' => $row['id_rule'],
1274
			'name' => $row['rule_name'],
1275
			'criteria' => Util::unserialize($row['criteria']),
1276
			'actions' => Util::unserialize($row['actions']),
1277
			'delete' => $row['delete_pm'],
1278
			'logic' => $row['is_or'] ? 'or' : 'and',
1279
		);
1280
1281
		if ($row['delete_pm'])
1282
			$context['rules'][$row['id_rule']]['actions'][] = array('t' => 'del', 'v' => 1);
1283
	}
1284
	$db->free_result($request);
1285
}
1286
1287
/**
1288
 * Update PM recipient when they receive or read a new PM
1289
 *
1290
 * @package PersonalMessage
1291
 * @param int $id_member
1292
 * @param boolean $new = false
1293
 */
1294
function toggleNewPM($id_member, $new = false)
1295
{
1296
	$db = database();
1297
1298
	$db->query('', '
1299
		UPDATE {db_prefix}pm_recipients
1300
		SET is_new = ' . ($new ? '{int:new}' : '{int:not_new}') . '
1301
		WHERE id_member = {int:current_member}',
1302
		array(
1303
			'current_member' => $id_member,
1304
			'new' => 1,
1305
			'not_new' => 0
1306
		)
1307
	);
1308
}
1309
1310
/**
1311
 * Load the PM limits for each group or for a specified group
1312
 *
1313
 * @package PersonalMessage
1314
 * @param int|bool $id_group (optional) the id of a membergroup
1315
 */
1316
function loadPMLimits($id_group = false)
1317
{
1318
	$db = database();
1319
1320
	$request = $db->query('', '
1321
		SELECT
1322
			id_group, group_name, max_messages
1323
		FROM {db_prefix}membergroups' . ($id_group ? '
1324
		WHERE id_group = {int:id_group}' : '
1325
		ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name'),
1326
		array(
1327
			'id_group' => $id_group,
1328
			'newbie_group' => 4,
1329
		)
1330
	);
1331
	$groups = array();
1332
	while ($row = $db->fetch_assoc($request))
1333
	{
1334
		if ($row['id_group'] != 1)
1335
			$groups[$row['id_group']] = $row;
1336
	}
1337
	$db->free_result($request);
1338
1339
	return $groups;
1340
}
1341
1342
/**
1343
 * Retrieve the discussion one or more PMs belong to
1344
 *
1345
 * @package PersonalMessage
1346
 * @param int[] $id_pms
1347
 */
1348
function getDiscussions($id_pms)
1349
{
1350
	$db = database();
1351
1352
	$request = $db->query('', '
1353
		SELECT
1354
			id_pm_head, id_pm
1355
		FROM {db_prefix}personal_messages
1356
		WHERE id_pm IN ({array_int:id_pms})',
1357
		array(
1358
			'id_pms' => $id_pms,
1359
		)
1360
	);
1361
	$pm_heads = array();
1362
	while ($row = $db->fetch_assoc($request))
1363
		$pm_heads[$row['id_pm_head']] = $row['id_pm'];
1364
	$db->free_result($request);
1365
1366
	return $pm_heads;
1367
}
1368
1369
/**
1370
 * Return all the PMs belonging to one or more discussions
1371
 *
1372
 * @package PersonalMessage
1373
 * @param int[] $pm_heads array of pm id head nodes
1374
 */
1375
function getPmsFromDiscussion($pm_heads)
1376
{
1377
	$db = database();
1378
1379
	$pms = array();
1380
	$request = $db->query('', '
1381
		SELECT
1382
			id_pm, id_pm_head
1383
		FROM {db_prefix}personal_messages
1384
		WHERE id_pm_head IN ({array_int:pm_heads})',
1385
		array(
1386
			'pm_heads' => $pm_heads,
1387
		)
1388
	);
1389
	// Copy the action from the single to PM to the others.
1390
	while ($row = $db->fetch_assoc($request))
1391
		$pms[$row['id_pm']] = $row['id_pm_head'];
1392
	$db->free_result($request);
1393
1394
	return $pms;
1395
}
1396
1397
/**
1398
 * Determines the PMs which need an updated label.
1399
 *
1400
 * @package PersonalMessage
1401
 * @param mixed[] $to_label
1402
 * @param string[] $label_type
1403
 * @param int $user_id
1404
 * @return integer|null
1405
 */
1406
function changePMLabels($to_label, $label_type, $user_id)
1407
{
1408
	global $options;
1409
1410
	$db = database();
1411
1412
	$to_update = array();
1413
1414
	// Get information about each message...
1415
	$request = $db->query('', '
1416
		SELECT
1417
			id_pm, labels
1418
		FROM {db_prefix}pm_recipients
1419
		WHERE id_member = {int:current_member}
1420
			AND id_pm IN ({array_int:to_label})
1421
		LIMIT ' . count($to_label),
1422
		array(
1423
			'current_member' => $user_id,
1424
			'to_label' => array_keys($to_label),
1425
		)
1426
	);
1427
	while ($row = $db->fetch_assoc($request))
1428
	{
1429
		$labels = $row['labels'] == '' ? array('-1') : explode(',', trim($row['labels']));
1430
1431
		// Already exists?  Then... unset it!
1432
		$id_label = array_search($to_label[$row['id_pm']], $labels);
1433
1434
		if ($id_label !== false && $label_type[$row['id_pm']] !== 'add')
1435
			unset($labels[$id_label]);
1436
		elseif ($label_type[$row['id_pm']] !== 'rem')
1437
			$labels[] = $to_label[$row['id_pm']];
1438
1439
		if (!empty($options['pm_remove_inbox_label']) && $to_label[$row['id_pm']] != '-1' && ($key = array_search('-1', $labels)) !== false)
1440
			unset($labels[$key]);
1441
1442
		$set = implode(',', array_unique($labels));
1443
		if ($set == '')
1444
			$set = '-1';
1445
1446
		$to_update[$row['id_pm']] = $set;
1447
	}
1448
	$db->free_result($request);
1449
1450
	if (!empty($to_update))
1451
		return updatePMLabels($to_update, $user_id);
1452
}
1453
1454
/**
1455
 * Detects personal messages which need a new label.
1456
 *
1457
 * @package PersonalMessage
1458
 * @param mixed[] $searchArray
1459
 * @param mixed[] $new_labels
1460
 * @param int $user_id
1461
 * @return integer|null
1462
 */
1463
function updateLabelsToPM($searchArray, $new_labels, $user_id)
1464
{
1465
	$db = database();
1466
1467
	// Now find the messages to change.
1468
	$request = $db->query('', '
1469
		SELECT
1470
			id_pm, labels
1471
		FROM {db_prefix}pm_recipients
1472
		WHERE FIND_IN_SET({raw:find_label_implode}, labels) != 0
1473
			AND id_member = {int:current_member}',
1474
		array(
1475
			'current_member' => $user_id,
1476
			'find_label_implode' => '\'' . implode('\', labels) != 0 OR FIND_IN_SET(\'', $searchArray) . '\'',
1477
		)
1478
	);
1479
	$to_update = array();
1480
	while ($row = $db->fetch_assoc($request))
1481
	{
1482
		// Do the long task of updating them...
1483
		$toChange = explode(',', $row['labels']);
1484
1485
		foreach ($toChange as $key => $value)
1486
		{
1487
			if (in_array($value, $searchArray))
1488
			{
1489
				if (isset($new_labels[$value]))
1490
					$toChange[$key] = $new_labels[$value];
1491
				else
1492
					unset($toChange[$key]);
1493
			}
1494
		}
1495
1496
		if (empty($toChange))
1497
			$toChange[] = '-1';
1498
1499
		$to_update[$row['id_pm']] = implode(',', array_unique($toChange));
1500
	}
1501
	$db->free_result($request);
1502
1503
	if (!empty($to_update))
1504
		return updatePMLabels($to_update, $user_id);
1505
}
1506
1507
/**
1508
 * Updates PMs with their new label.
1509
 *
1510
 * @package PersonalMessage
1511
 * @param mixed[] $to_update
1512
 * @param int $user_id
1513
 * @return int
1514
 */
1515
function updatePMLabels($to_update, $user_id)
1516
{
1517
	$db = database();
1518
1519
	$updateErrors = 0;
1520
1521
	foreach ($to_update as $id_pm => $set)
1522
	{
1523
		// Check that this string isn't going to be too large for the database.
1524
		if (strlen($set) > 60)
1525
		{
1526
			$updateErrors++;
1527
1528
			// Make the string as long as possible and update anyway
1529
			$set = substr($set, 0, 60);
1530
			$set = substr($set, 0, strrpos($set, ','));
1531
		}
1532
1533
		$db->query('', '
1534
			UPDATE {db_prefix}pm_recipients
1535
			SET labels = {string:labels}
1536
			WHERE id_pm = {int:id_pm}
1537
				AND id_member = {int:current_member}',
1538
			array(
1539
				'current_member' => $user_id,
1540
				'id_pm' => $id_pm,
1541
				'labels' => $set,
1542
			)
1543
		);
1544
	}
1545
1546
	return $updateErrors;
1547
}
1548
1549
/**
1550
 * Gets PMs older than a specific date.
1551
 *
1552
 * @package PersonalMessage
1553
 * @param int $user_id the user's id.
1554
 * @param int $time timestamp with a specific date
1555
 * @return array
1556
 */
1557
function getPMsOlderThan($user_id, $time)
1558
{
1559
	$db = database();
1560
1561
	// Array to store the IDs in.
1562
	$pm_ids = array();
1563
1564
	// Select all the messages they have sent older than $time.
1565
	$request = $db->query('', '
1566
		SELECT
1567
			id_pm
1568
		FROM {db_prefix}personal_messages
1569
		WHERE deleted_by_sender = {int:not_deleted}
1570
			AND id_member_from = {int:current_member}
1571
			AND msgtime < {int:msgtime}',
1572
		array(
1573
			'current_member' => $user_id,
1574
			'not_deleted' => 0,
1575
			'msgtime' => $time,
1576
		)
1577
	);
1578
	while ($row = $db->fetch_row($request))
1579
		$pm_ids[] = $row[0];
1580
	$db->free_result($request);
1581
1582
	// This is the inbox
1583
	$request = $db->query('', '
1584
		SELECT
1585
			pmr.id_pm
1586
		FROM {db_prefix}pm_recipients AS pmr
1587
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1588
		WHERE pmr.deleted = {int:not_deleted}
1589
			AND pmr.id_member = {int:current_member}
1590
			AND pm.msgtime < {int:msgtime}',
1591
		array(
1592
			'current_member' => $user_id,
1593
			'not_deleted' => 0,
1594
			'msgtime' => $time,
1595
		)
1596
	);
1597
	while ($row = $db->fetch_row($request))
1598
		$pm_ids[] = $row[0];
1599
	$db->free_result($request);
1600
1601
	return $pm_ids;
1602
}
1603
1604
/**
1605
 * Used to delete PM rules from the given member.
1606
 *
1607
 * @package PersonalMessage
1608
 * @param int $id_member
1609
 * @param int[] $rule_changes
1610
 */
1611
function deletePMRules($id_member, $rule_changes)
1612
{
1613
	$db = database();
1614
1615
	$db->query('', '
1616
		DELETE FROM {db_prefix}pm_rules
1617
		WHERE id_rule IN ({array_int:rule_list})
1618
		AND id_member = {int:current_member}',
1619
		array(
1620
			'current_member' => $id_member,
1621
			'rule_list' => $rule_changes,
1622
		)
1623
	);
1624
}
1625
1626
/**
1627
 * Updates a personal messaging rule action for the given member.
1628
 *
1629
 * @package PersonalMessage
1630
 * @param int $id_rule
1631
 * @param int $id_member
1632
 * @param mixed[] $actions
1633
 */
1634
function updatePMRuleAction($id_rule, $id_member, $actions)
1635
{
1636
	$db = database();
1637
1638
	$db->query('', '
1639
		UPDATE {db_prefix}pm_rules
1640
		SET actions = {string:actions}
1641
		WHERE id_rule = {int:id_rule}
1642
			AND id_member = {int:current_member}',
1643
		array(
1644
			'current_member' => $id_member,
1645
			'id_rule' => $id_rule,
1646
			'actions' => serialize($actions),
1647
		)
1648
	);
1649
}
1650
1651
/**
1652
 * Add a new PM rule to the database.
1653
 *
1654
 * @package PersonalMessage
1655
 * @param int $id_member
1656
 * @param string $ruleName
1657
 * @param string $criteria
1658
 * @param string $actions
1659
 * @param int $doDelete
1660
 * @param int $isOr
1661
 */
1662
function addPMRule($id_member, $ruleName, $criteria, $actions, $doDelete, $isOr)
1663
{
1664
	$db = database();
1665
1666
	$db->insert('',
1667
		'{db_prefix}pm_rules',
1668
		array(
1669
			'id_member' => 'int', 'rule_name' => 'string', 'criteria' => 'string', 'actions' => 'string',
1670
			'delete_pm' => 'int', 'is_or' => 'int',
1671
		),
1672
		array(
1673
			$id_member, $ruleName, $criteria, $actions, $doDelete, $isOr,
1674
		),
1675
		array('id_rule')
1676
	);
1677
}
1678
1679
/**
1680
 * Updates a personal messaging rule for the given member.
1681
 *
1682
 * @package PersonalMessage
1683
 * @param int $id_member
1684
 * @param int $id_rule
1685
 * @param string $ruleName
1686
 * @param string $criteria
1687
 * @param string $actions
1688
 * @param int $doDelete
1689
 * @param int $isOr
1690
 */
1691
function updatePMRule($id_member, $id_rule, $ruleName, $criteria, $actions, $doDelete, $isOr)
1692
{
1693
	$db = database();
1694
1695
	$db->query('', '
1696
		UPDATE {db_prefix}pm_rules
1697
		SET rule_name = {string:rule_name}, criteria = {string:criteria}, actions = {string:actions},
1698
			delete_pm = {int:delete_pm}, is_or = {int:is_or}
1699
		WHERE id_rule = {int:id_rule}
1700
			AND id_member = {int:current_member}',
1701
		array(
1702
			'current_member' => $id_member,
1703
			'delete_pm' => $doDelete,
1704
			'is_or' => $isOr,
1705
			'id_rule' => $id_rule,
1706
			'rule_name' => $ruleName,
1707
			'criteria' => $criteria,
1708
			'actions' => $actions,
1709
		)
1710
	);
1711
}
1712
1713
/**
1714
 * Used to set a replied status for a given PM.
1715
 *
1716
 * @package PersonalMessage
1717
 * @param int $id_member
1718
 * @param int $replied_to
1719
 */
1720
function setPMRepliedStatus($id_member, $replied_to)
1721
{
1722
	$db = database();
1723
1724
	$db->query('', '
1725
		UPDATE {db_prefix}pm_recipients
1726
		SET is_read = is_read | 2
1727
		WHERE id_pm = {int:replied_to}
1728
			AND id_member = {int:current_member}',
1729
		array(
1730
			'current_member' => $id_member,
1731
			'replied_to' => $replied_to,
1732
		)
1733
	);
1734
}
1735
1736
/**
1737
 * Given the head PM, loads all other PM's that share the same head node
1738
 *
1739
 * - Used to load the conversation view of a PM
1740
 *
1741
 * @package PersonalMessage
1742
 * @param int $head id of the head pm of the conversation
1743
 * @param mixed[] $recipients
1744
 * @param string $folder the current folder we are working in
1745
 */
1746
function loadConversationList($head, &$recipients, $folder = '')
1747
{
1748
	global $user_info;
1749
1750
	$db = database();
1751
1752
	$request = $db->query('', '
1753
		SELECT
1754
			pm.id_pm, pm.id_member_from, pm.deleted_by_sender, pmr.id_member, pmr.deleted
1755
		FROM {db_prefix}personal_messages AS pm
1756
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
1757
		WHERE pm.id_pm_head = {int:id_pm_head}
1758
			AND ((pm.id_member_from = {int:current_member} AND pm.deleted_by_sender = {int:not_deleted})
1759
				OR (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted}))
1760
		ORDER BY pm.id_pm',
1761
		array(
1762
			'current_member' => $user_info['id'],
1763
			'id_pm_head' => $head,
1764
			'not_deleted' => 0,
1765
		)
1766
	);
1767
	$display_pms = array();
1768
	$posters = array();
1769
	while ($row = $db->fetch_assoc($request))
1770
	{
1771
		// This is, frankly, a joke. We will put in a workaround for people sending to themselves - yawn!
1772
		if ($folder == 'sent' && $row['id_member_from'] == $user_info['id'] && $row['deleted_by_sender'] == 1)
1773
			continue;
1774
		elseif (($row['id_member'] == $user_info['id']) && $row['deleted'] == 1)
1775
			continue;
1776
1777
		if (!isset($recipients[$row['id_pm']]))
1778
			$recipients[$row['id_pm']] = array(
1779
				'to' => array(),
1780
				'bcc' => array()
1781
			);
1782
1783
		$display_pms[] = $row['id_pm'];
1784
		$posters[$row['id_pm']] = $row['id_member_from'];
1785
	}
1786
	$db->free_result($request);
1787
1788
	return array($display_pms, $posters);
1789
}
1790
1791
/**
1792
 * Used to determine if any message in a conversation thread is unread
1793
 *
1794
 * - Returns array of keys with the head id and value details of the the newest
1795
 * unread message.
1796
 *
1797
 * @package PersonalMessage
1798
 * @param int[] $pms array of pm ids to search
1799
 */
1800
function loadConversationUnreadStatus($pms)
1801
{
1802
	global $user_info;
1803
1804
	$db = database();
1805
1806
	// Make it an array if its not
1807
	if (!is_array($pms))
0 ignored issues
show
introduced by
The condition is_array($pms) is always true.
Loading history...
1808
		$pms = array($pms);
1809
1810
	// Find the heads for this group of PM's
1811
	$request = $db->query('', '
1812
		SELECT
1813
			id_pm_head, id_pm
1814
		FROM {db_prefix}personal_messages
1815
		WHERE id_pm IN ({array_int:id_pm})',
1816
		array(
1817
			'id_pm' => $pms,
1818
		)
1819
	);
1820
	$head_pms = array();
1821
	while ($row = $db->fetch_assoc($request))
1822
		$head_pms[$row['id_pm_head']] = $row['id_pm'];
1823
	$db->free_result($request);
1824
1825
	// Find any unread PM's this member has under these head pm id's
1826
	$request = $db->query('', '
1827
		SELECT
1828
			MAX(pm.id_pm) AS id_pm, pm.id_pm_head
1829
		FROM {db_prefix}personal_messages AS pm
1830
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
1831
		WHERE pm.id_pm_head IN ({array_int:id_pm_head})
1832
			AND (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted})
1833
			AND (pmr.is_read & 1 = 0)
1834
		GROUP BY pm.id_pm_head',
1835
		array(
1836
			'current_member' => $user_info['id'],
1837
			'id_pm_head' => array_keys($head_pms),
1838
			'not_deleted' => 0,
1839
		)
1840
	);
1841
	$unread_pms = array();
1842
	while ($row = $db->fetch_assoc($request))
1843
	{
1844
		// Return the results under the original index since thats what we are
1845
		// displaying in the subject list
1846
		$index = $head_pms[$row['id_pm_head']];
1847
		$unread_pms[$index] = $row;
1848
	}
1849
	$db->free_result($request);
1850
1851
	return $unread_pms;
1852
}
1853
1854
/**
1855
 * Get all recipients for a given group of PM's, loads some basic member information for each
1856
 *
1857
 * - Will not include bcc-recipients for an inbox
1858
 * - Keeps track if a message has been replied / read
1859
 * - Tracks any message labels in use
1860
 * - If optional search parameter is set to true will return message first label, useful for linking
1861
 *
1862
 * @package PersonalMessage
1863
 * @param int[] $all_pms
1864
 * @param mixed[] $recipients
1865
 * @param string $folder
1866
 * @param boolean $search
1867
 */
1868
function loadPMRecipientInfo($all_pms, &$recipients, $folder = '', $search = false)
1869
{
1870
	global $txt, $user_info, $scripturl, $context;
1871
1872
	$db = database();
1873
1874
	// Get the recipients for all these PM's
1875
	$request = $db->query('', '
1876
		SELECT
1877
			pmr.id_pm, pmr.bcc, pmr.labels, pmr.is_read,
1878
			mem_to.id_member AS id_member_to, mem_to.real_name AS to_name
1879
		FROM {db_prefix}pm_recipients AS pmr
1880
			LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
1881
		WHERE pmr.id_pm IN ({array_int:pm_list})',
1882
		array(
1883
			'pm_list' => $all_pms,
1884
		)
1885
	);
1886
1887
	$message_labels = array();
1888
	foreach ($all_pms as $pmid)
1889
	{
1890
		$message_labels[$pmid] = array();
1891
	}
1892
	$message_replied = array();
1893
	$message_unread = array();
1894
	$message_first_label = array();
1895
	while ($row = $db->fetch_assoc($request))
1896
	{
1897
		// Sent folder recipients
1898
		if ($folder === 'sent' || empty($row['bcc']))
1899
			$recipients[$row['id_pm']][empty($row['bcc']) ? 'to' : 'bcc'][] = empty($row['id_member_to']) ? $txt['guest_title'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . '">' . $row['to_name'] . '</a>';
1900
1901
		// Don't include bcc-recipients if its your inbox, you're not supposed to know :P
1902
		if ($row['id_member_to'] == $user_info['id'] && $folder !== 'sent')
1903
		{
1904
			// Read and replied to status for this message
1905
			$message_replied[$row['id_pm']] = $row['is_read'] & 2;
1906
			$message_unread[$row['id_pm']] = $row['is_read'] == 0;
1907
			$message_labels[$row['id_pm']] = array();
1908
1909
			$row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']);
1910
			foreach ($row['labels'] as $v)
1911
			{
1912
				if (isset($context['labels'][(int) $v]))
1913
					$message_labels[$row['id_pm']][(int) $v] = array('id' => $v, 'name' => $context['labels'][(int) $v]['name']);
1914
1915
				// Here we find the first label on a message - used for linking to posts
1916
				if ($search && (!isset($message_first_label[$row['id_pm']]) && !in_array('-1', $row['labels'])))
1917
					$message_first_label[$row['id_pm']] = (int) $v;
1918
			}
1919
		}
1920
	}
1921
	$db->free_result($request);
1922
1923
	return array($message_labels, $message_replied, $message_unread, ($search ? $message_first_label : ''));
1924
}
1925
1926
/**
1927
 * This is used by preparePMContext_callback.
1928
 *
1929
 * - That function uses these query results and handles the free_result action as well.
1930
 *
1931
 * @package PersonalMessage
1932
 * @param int[] $pms array of PM ids to fetch
1933
 * @param string[] $orderBy raw query defining how to order the results
1934
 */
1935
function loadPMSubjectRequest($pms, $orderBy)
1936
{
1937
	$db = database();
1938
1939
	// Separate query for these bits!
1940
	$subjects_request = $db->query('', '
1941
		SELECT
1942
			pm.id_pm, pm.subject, pm.id_member_from, pm.msgtime, COALESCE(mem.real_name, pm.from_name) AS from_name,
1943
			COALESCE(mem.id_member, 0) AS not_guest
1944
		FROM {db_prefix}personal_messages AS pm
1945
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1946
		WHERE pm.id_pm IN ({array_int:pm_list})
1947
		ORDER BY ' . implode(', ', $orderBy) . '
1948
		LIMIT ' . count($pms),
1949
		array(
1950
			'pm_list' => $pms,
1951
		)
1952
	);
1953
1954
	return $subjects_request;
1955
}
1956
1957
/**
1958
 * Similar to loadSubjectRequest, this is used by preparePMContext_callback.
1959
 *
1960
 * - That function uses these query results and handles the free_result action as well.
1961
 *
1962
 * @package PersonalMessage
1963
 * @param int[] $display_pms list of PM's to fetch
1964
 * @param string $sort_by_query raw query used in the sorting option
1965
 * @param string $sort_by used to signal when addition joins are needed
1966
 * @param boolean $descending if true descending order of display
1967
 * @param int|string $display_mode how are they being viewed, all, conversation, etc
1968
 * @param string $folder current pm folder
1969
 */
1970
function loadPMMessageRequest($display_pms, $sort_by_query, $sort_by, $descending, $display_mode = '', $folder = '')
1971
{
1972
	$db = database();
1973
1974
	$messages_request = $db->query('', '
1975
		SELECT
1976
			pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
1977
		FROM {db_prefix}personal_messages AS pm' . ($folder == 'sent' ? '
1978
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') . ($sort_by == 'name' ? '
1979
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})' : '') . '
1980
		WHERE pm.id_pm IN ({array_int:display_pms})' . ($folder == 'sent' ? '
1981
		GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' : '') . '
1982
		ORDER BY ' . ($display_mode == 2 ? 'pm.id_pm' : $sort_by_query) . ($descending ? ' DESC' : ' ASC') . '
1983
		LIMIT ' . count($display_pms),
1984
		array(
1985
			'display_pms' => $display_pms,
1986
			'id_member' => $folder == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
1987
		)
1988
	);
1989
1990
	return $messages_request;
1991
}
1992
1993
/**
1994
 * Simple function to validate that a PM was sent to the current user
1995
 *
1996
 * @package PersonalMessage
1997
 * @param int $pmsg id of the pm we are checking
1998
 */
1999
function checkPMReceived($pmsg)
2000
{
2001
	global $user_info;
2002
2003
	$db = database();
2004
2005
	$request = $db->query('', '
2006
		SELECT
2007
			id_pm
2008
		FROM {db_prefix}pm_recipients
2009
		WHERE id_pm = {int:id_pm}
2010
			AND id_member = {int:current_member}
2011
		LIMIT 1',
2012
		array(
2013
			'current_member' => $user_info['id'],
2014
			'id_pm' => $pmsg,
2015
		)
2016
	);
2017
	$isReceived = $db->num_rows($request) != 0;
2018
	$db->free_result($request);
2019
2020
	return $isReceived;
2021
}
2022
2023
/**
2024
 * Loads a pm by ID for use as a quoted pm in a new message
2025
 *
2026
 * @package PersonalMessage
2027
 * @param int $pmsg
2028
 * @param boolean $isReceived
2029
 */
2030
function loadPMQuote($pmsg, $isReceived)
2031
{
2032
	global $user_info;
2033
2034
	$db = database();
2035
2036
	// Get the quoted message (and make sure you're allowed to see this quote!).
2037
	$request = $db->query('', '
2038
		SELECT
2039
			pm.id_pm, CASE WHEN pm.id_pm_head = {int:id_pm_head_empty} THEN pm.id_pm ELSE pm.id_pm_head END AS pm_head,
2040
			pm.body, pm.subject, pm.msgtime,
2041
			mem.member_name, COALESCE(mem.id_member, 0) AS id_member, COALESCE(mem.real_name, pm.from_name) AS real_name
2042
		FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : '
2043
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . '
2044
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
2045
		WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? '
2046
			AND pm.id_member_from = {int:current_member}' : '
2047
			AND pmr.id_member = {int:current_member}') . '
2048
		LIMIT 1',
2049
		array(
2050
			'current_member' => $user_info['id'],
2051
			'id_pm_head_empty' => 0,
2052
			'id_pm' => $pmsg,
2053
		)
2054
	);
2055
	$row_quoted = $db->fetch_assoc($request);
2056
	$db->free_result($request);
2057
2058
	return empty($row_quoted) ? false : $row_quoted;
2059
}
2060
2061
/**
2062
 * For a given PM ID, loads all "other" recipients, (excludes the current member)
2063
 *
2064
 * - Will optionally count the number of bcc recipients and return that count
2065
 *
2066
 * @package PersonalMessage
2067
 * @param int $pmsg
2068
 * @param boolean $bcc_count
2069
 */
2070
function loadPMRecipientsAll($pmsg, $bcc_count = false)
2071
{
2072
	global $user_info, $scripturl, $txt;
2073
2074
	$db = database();
2075
2076
	$request = $db->query('', '
2077
		SELECT
2078
			mem.id_member, mem.real_name, pmr.bcc
2079
		FROM {db_prefix}pm_recipients AS pmr
2080
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
2081
		WHERE pmr.id_pm = {int:id_pm}
2082
			AND pmr.id_member != {int:current_member}' . ($bcc_count === true ? '' : '
2083
			AND pmr.bcc = {int:not_bcc}'),
2084
		array(
2085
			'current_member' => $user_info['id'],
2086
			'id_pm' => $pmsg,
2087
			'not_bcc' => 0,
2088
		)
2089
	);
2090
	$recipients = array();
2091
	$hidden_recipients = 0;
2092
	while ($row = $db->fetch_assoc($request))
2093
	{
2094
		// If it's hidden we still don't reveal their names
2095
		if ($bcc_count && $row['bcc'])
2096
			$hidden_recipients++;
2097
2098
		$recipients[] = array(
2099
			'id' => $row['id_member'],
2100
			'name' => htmlspecialchars($row['real_name'], ENT_COMPAT, 'UTF-8'),
2101
			'link' => '[url=' . $scripturl . '?action=profile;u=' . $row['id_member'] . ']' . $row['real_name'] . '[/url]',
2102
		);
2103
	}
2104
2105
	// If bcc count was requested, we return the number of bcc members, but not the names
2106
	if ($bcc_count)
2107
		$recipients[] = array(
2108
			'id' => 'bcc',
2109
			'name' => sprintf($txt['pm_report_pm_hidden'], $hidden_recipients),
2110
			'link' => sprintf($txt['pm_report_pm_hidden'], $hidden_recipients)
2111
		);
2112
2113
	$db->free_result($request);
2114
2115
	return $recipients;
2116
}
2117
2118
/**
2119
 * Simply loads a personal message by ID
2120
 *
2121
 * - Supplied ID must have been sent to the user id requesting it and it must not have been deleted
2122
 *
2123
 * @package PersonalMessage
2124
 *
2125
 * @param int $pm_id
2126
 *
2127
 * @return
2128
 * @throws Elk_Exception no_access
2129
 */
2130
function loadPersonalMessage($pm_id)
2131
{
2132
	global $user_info;
2133
2134
	$db = database();
2135
2136
	// First, pull out the message contents, and verify it actually went to them!
2137
	$request = $db->query('', '
2138
		SELECT
2139
			pm.subject, pm.body, pm.msgtime, pm.id_member_from,
2140
			COALESCE(m.real_name, pm.from_name) AS sender_name,
2141
			pm.from_name AS poster_name, msgtime
2142
		FROM {db_prefix}personal_messages AS pm
2143
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2144
			LEFT JOIN {db_prefix}members AS m ON (m.id_member = pm.id_member_from)
2145
		WHERE pm.id_pm = {int:id_pm}
2146
			AND pmr.id_member = {int:current_member}
2147
			AND pmr.deleted = {int:not_deleted}
2148
		LIMIT 1',
2149
		array(
2150
			'current_member' => $user_info['id'],
2151
			'id_pm' => $pm_id,
2152
			'not_deleted' => 0,
2153
		)
2154
	);
2155
	// Can only be a hacker here!
2156
	if ($db->num_rows($request) == 0)
2157
		throw new Elk_Exception('no_access', false);
2158
	$pm_details = $db->fetch_row($request);
2159
	$db->free_result($request);
2160
2161
	return $pm_details;
2162
}
2163
2164
/**
2165
 * Finds the number of results that a search would produce
2166
 *
2167
 * @package PersonalMessage
2168
 * @param string $userQuery raw query, used if we are searching for specific users
2169
 * @param string $labelQuery raw query, used if we are searching only specific labels
2170
 * @param string $timeQuery raw query, used if we are limiting results to time periods
2171
 * @param string $searchQuery raw query, the actual thing you are searching for in the subject and/or body
2172
 * @param mixed[] $searchq_parameters value parameters used in the above query
2173
 * @return integer
2174
 */
2175
function numPMSeachResults($userQuery, $labelQuery, $timeQuery, $searchQuery, $searchq_parameters)
2176
{
2177
	global $context, $user_info;
2178
2179
	$db = database();
2180
2181
	// Get the amount of results.
2182
	$request = $db->query('', '
2183
		SELECT
2184
			COUNT(*)
2185
		FROM {db_prefix}pm_recipients AS pmr
2186
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
2187
		WHERE ' . ($context['folder'] == 'inbox' ? '
2188
			pmr.id_member = {int:current_member}
2189
			AND pmr.deleted = {int:not_deleted}' : '
2190
			pm.id_member_from = {int:current_member}
2191
			AND pm.deleted_by_sender = {int:not_deleted}') . '
2192
			' . $userQuery . $labelQuery . $timeQuery . '
2193
			AND (' . $searchQuery . ')',
2194
		array_merge($searchq_parameters, array(
2195
			'current_member' => $user_info['id'],
2196
			'not_deleted' => 0,
2197
		))
2198
	);
2199
	list ($numResults) = $db->fetch_row($request);
2200
	$db->free_result($request);
2201
2202
	return $numResults;
2203
}
2204
2205
/**
2206
 * Gets all the matching message ids, senders and head pm nodes, using standard search only (No caching and the like!)
2207
 *
2208
 * @package PersonalMessage
2209
 * @param string $userQuery raw query, used if we are searching for specific users
2210
 * @param string $labelQuery raw query, used if we are searching only specific labels
2211
 * @param string $timeQuery raw query, used if we are limiting results to time periods
2212
 * @param string $searchQuery raw query, the actual thing you are searching for in the subject and/or body
2213
 * @param mixed[] $searchq_parameters value parameters used in the above query
2214
 * @param mixed[] $search_params additional search parameters, like sort and direction
2215
 */
2216
function loadPMSearchMessages($userQuery, $labelQuery, $timeQuery, $searchQuery, $searchq_parameters, $search_params)
2217
{
2218
	global $context, $modSettings, $user_info;
2219
2220
	$db = database();
2221
2222
	$request = $db->query('', '
2223
		SELECT
2224
			pm.id_pm, pm.id_pm_head, pm.id_member_from
2225
		FROM {db_prefix}pm_recipients AS pmr
2226
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
2227
		WHERE ' . ($context['folder'] == 'inbox' ? '
2228
			pmr.id_member = {int:current_member}
2229
			AND pmr.deleted = {int:not_deleted}' : '
2230
			pm.id_member_from = {int:current_member}
2231
			AND pm.deleted_by_sender = {int:not_deleted}') . '
2232
			' . $userQuery . $labelQuery . $timeQuery . '
2233
			AND (' . $searchQuery . ')
2234
		ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . '
2235
		LIMIT ' . $context['start'] . ', ' . $modSettings['search_results_per_page'],
2236
		array_merge($searchq_parameters, array(
2237
			'current_member' => $user_info['id'],
2238
			'not_deleted' => 0,
2239
		))
2240
	);
2241
	$foundMessages = array();
2242
	$posters = array();
2243
	$head_pms = array();
2244
	while ($row = $db->fetch_assoc($request))
2245
	{
2246
		$foundMessages[] = $row['id_pm'];
2247
		$posters[] = $row['id_member_from'];
2248
		$head_pms[$row['id_pm']] = $row['id_pm_head'];
2249
	}
2250
	$db->free_result($request);
2251
2252
	return array($foundMessages, $posters, $head_pms);
2253
}
2254
2255
/**
2256
 * When we are in conversation view, we need to find the base head pm of the
2257
 * conversation.  This will set the root head id to each of the node heads
2258
 *
2259
 * @package PersonalMessage
2260
 * @param int[] $head_pms array of pm ids that were found in the id_pm_head col
2261
 * during the initial search
2262
 */
2263
function loadPMSearchHeads($head_pms)
2264
{
2265
	global $user_info;
2266
2267
	$db = database();
2268
2269
	$request = $db->query('', '
2270
		SELECT
2271
			MAX(pm.id_pm) AS id_pm, pm.id_pm_head
2272
		FROM {db_prefix}personal_messages AS pm
2273
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2274
		WHERE pm.id_pm_head IN ({array_int:head_pms})
2275
			AND pmr.id_member = {int:current_member}
2276
			AND pmr.deleted = {int:not_deleted}
2277
		GROUP BY pm.id_pm_head
2278
		LIMIT {int:limit}',
2279
		array(
2280
			'head_pms' => array_unique($head_pms),
2281
			'current_member' => $user_info['id'],
2282
			'not_deleted' => 0,
2283
			'limit' => count($head_pms),
2284
		)
2285
	);
2286
	$real_pm_ids = array();
2287
	while ($row = $db->fetch_assoc($request))
2288
		$real_pm_ids[$row['id_pm_head']] = $row['id_pm'];
2289
	$db->free_result($request);
2290
2291
	return $real_pm_ids;
2292
}
2293
2294
/**
2295
 * Loads the actual details of the PM's that were found during the search stage
2296
 *
2297
 * @package PersonalMessage
2298
 * @param int[] $foundMessages array of found message id's
2299
 * @param mixed[] $search_params as specified in the form, here used for sorting
2300
 */
2301
function loadPMSearchResults($foundMessages, $search_params)
2302
{
2303
	$db = database();
2304
2305
	// Prepare the query for the callback!
2306
	$request = $db->query('', '
2307
		SELECT
2308
			pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
2309
		FROM {db_prefix}personal_messages AS pm
2310
		WHERE pm.id_pm IN ({array_int:message_list})
2311
		ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . '
2312
		LIMIT ' . count($foundMessages),
2313
		array(
2314
			'message_list' => $foundMessages,
2315
		)
2316
	);
2317
	$search_results = array();
2318
	while ($row = $db->fetch_assoc($request))
2319
		$search_results[] = $row;
2320
	$db->free_result($request);
2321
2322
	return $search_results;
2323
}
2324