Completed
Push — patch_1-1-4 ( 3f780f...826343 )
by Emanuele
25:17 queued 11:40
created

PersonalMessage.subs.php ➔ loadConversationUnreadStatus()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 8
nop 1
dl 0
loc 53
rs 9.0254
c 0
b 0
f 0
ccs 0
cts 42
cp 0
crap 20

How to fix   Long Method   

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 View Code Duplication
	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 View Code Duplication
	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))
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 View Code Duplication
	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 View Code Duplication
		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 View Code Duplication
	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))
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 View Code Duplication
function loadPMSearchHeads($head_pms)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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