Issues (1686)

sources/subs/PersonalMessage.subs.php (38 issues)

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
 * @package   ElkArte Forum
11
 * @copyright ElkArte Forum contributors
12
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
13
 *
14
 * This file contains code covered by:
15
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
16
 *
17
 * @version 2.0 dev
18
 *
19
 */
20
21
use ElkArte\Cache\Cache;
22
use ElkArte\Helper\Util;
23
use ElkArte\Languages\Txt;
24
use ElkArte\Mail\BuildMail;
25
use ElkArte\Mail\PreparseMail;
26
use ElkArte\User;
27
28
/**
29
 * Loads information about the users personal message limit.
30
 *
31
 * @package PersonalMessage
32 4
 */
33
function loadMessageLimit()
34 4
{
35 4
	$db = database();
36
37 4
	$message_limit = 0;
38
	if (User::$info->is_admin)
0 ignored issues
show
Bug Best Practice introduced by
The property is_admin does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
39
	{
40
		$message_limit = 0;
41
	}
42
	elseif (!Cache::instance()->getVar($message_limit, 'msgLimit:' . User::$info->id, 360))
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
43
	{
44
		$db->fetchQuery('
45
			SELECT
46
				MAX(max_messages) AS top_limit, MIN(max_messages) AS bottom_limit
47
			FROM {db_prefix}membergroups
48
			WHERE id_group IN ({array_int:users_groups})',
49
			array(
50
				'users_groups' => User::$info->groups,
0 ignored issues
show
Bug Best Practice introduced by
The property groups does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
51
			)
52
		)->fetch_callback(
53
			function ($row) use (&$message_limit) {
54
				$message_limit = $row['top_limit'] == 0 ? 0 : $row['bottom_limit'];
55
			}
56
		);
57
58
		// Save us doing it again!
59 4
		Cache::instance()->put('msgLimit:' . User::$info->id, $message_limit, 360);
60
	}
61
62
	return $message_limit;
63
}
64
65
/**
66
 * Loads the count of messages on a per label basis.
67
 *
68
 * @param $labels mixed[] array of labels that we are calculating the message count
69
 *
70
 * @return mixed[]
71
 * @package PersonalMessage
72
 */
73 4
function loadPMLabels($labels)
74
{
75
	$db = database();
76 4
77
	// Looks like we need to reseek!
78
	$db->fetchQuery('
79
		SELECT
80
			labels, is_read, COUNT(*) AS num
81
		FROM {db_prefix}pm_recipients
82
		WHERE id_member = {int:current_member}
83
			AND deleted = {int:not_deleted}
84 4
		GROUP BY labels, is_read',
85 4
		array(
86
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
87 4
			'not_deleted' => 0,
88
		)
89
	)->fetch_callback(
90
		function ($row) use (&$labels) {
91
			$this_labels = explode(',', $row['labels']);
92
			foreach ($this_labels as $this_label)
93
			{
94
				$labels[(int) $this_label]['messages'] += $row['num'];
95
96
				if (!($row['is_read'] & 1))
97
				{
98
					$labels[(int) $this_label]['unread_messages'] += $row['num'];
99 4
				}
100
			}
101
		}
102
	);
103 4
104
	// Store it please!
105 4
	Cache::instance()->put('labelCounts:' . User::$info->id, $labels, 720);
106
107
	return $labels;
108
}
109
110
/**
111
 * Get the number of PMs.
112
 *
113
 * @param bool $descending
114
 * @param int|null $pmID
115
 * @param string $labelQuery
116
 * @return int
117
 * @package PersonalMessage
118
 */
119
function getPMCount($descending = false, $pmID = null, $labelQuery = '')
120 2
{
121
	global $context;
122 2
123
	$db = database();
124
125 2
	// Figure out how many messages there are.
126
	if ($context['folder'] === 'sent')
127
	{
128
		$request = $db->fetchQuery('
129
			SELECT
130
				COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT id_pm_head' : '*') . ')
131
			FROM {db_prefix}personal_messages
132
			WHERE id_member_from = {int:current_member}
133
				AND deleted_by_sender = {int:not_deleted}' . ($pmID !== null ? '
134
				AND id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}' : ''),
135
			array(
136
				'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
137
				'not_deleted' => 0,
138
				'id_pm' => $pmID,
139
			)
140
		);
141
	}
142
	else
143 2
	{
144
		$request = $db->fetchQuery('
145 2
			SELECT
146 2
				COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
147 2
			FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
148
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . '
149 2
			WHERE pmr.id_member = {int:current_member}
150 2
				AND pmr.deleted = {int:not_deleted}' . $labelQuery . ($pmID !== null ? '
151
				AND pmr.id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}' : ''),
152 2
			array(
153 2
				'current_member' => User::$info->id,
154 2
				'not_deleted' => 0,
155
				'id_pm' => $pmID,
156
			)
157
		);
158
	}
159 2
160 2
	list ($count) = $request->fetch_row();
161
	$request->free_result();
162 2
163
	return (int) $count;
164
}
165
166
/**
167
 * Delete the specified personal messages.
168
 *
169
 * @param int[]|null $personal_messages array of pm ids
170
 * @param string|null $folder = null
171
 * @param int|int[]|null $owner = null
172
 * @package PersonalMessage
173
 */
174
function deleteMessages($personal_messages, $folder = null, $owner = null)
175
{
176
	$db = database();
177
178
	if ($owner === null)
179
	{
180
		$owner = array(User::$info->id);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
181
	}
182
	elseif (empty($owner))
183
	{
184
		return;
185
	}
186
	elseif (!is_array($owner))
187
	{
188
		$owner = array($owner);
189
	}
190
191
	if ($personal_messages !== null)
192
	{
193
		if (empty($personal_messages) || !is_array($personal_messages))
194
		{
195
			return;
196
		}
197
198
		foreach ($personal_messages as $index => $delete_id)
199
		{
200
			$personal_messages[$index] = (int) $delete_id;
201
		}
202
203
		$where = '
204
				AND id_pm IN ({array_int:pm_list})';
205
	}
206
	else
207
	{
208
		$where = '';
209
	}
210
211
	if ($folder === 'sent' || $folder === null)
212
	{
213
		$db->query('', '
214
			UPDATE {db_prefix}personal_messages
215
			SET 
216
				deleted_by_sender = {int:is_deleted}
217
			WHERE id_member_from IN ({array_int:member_list})
218
				AND deleted_by_sender = {int:not_deleted}' . $where,
219
			array(
220
				'member_list' => $owner,
221
				'is_deleted' => 1,
222
				'not_deleted' => 0,
223
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
224
			)
225
		);
226
	}
227
228
	if ($folder !== 'sent')
229
	{
230
		require_once(SUBSDIR . '/Members.subs.php');
231
232
		// Calculate the number of messages each member's gonna lose...
233
		$db->fetchQuery('
234
			SELECT
235
				id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read
236
			FROM {db_prefix}pm_recipients
237
			WHERE id_member IN ({array_int:member_list})
238
				AND deleted = {int:not_deleted}' . $where . '
239
			GROUP BY id_member, is_read',
240
			array(
241
				'member_list' => $owner,
242
				'not_deleted' => 0,
243
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
244
			)
245
		)->fetch_callback(
246
			function ($row) use ($where) {
247
				// ...And update the statistics accordingly - now including unread messages!.
248
				if ($row['is_read'])
249
				{
250
					updateMemberData($row['id_member'], array('personal_messages' => $where === '' ? 0 : 'personal_messages - ' . $row['num_deleted_messages']));
251
				}
252
				else
253
				{
254
					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']));
255
				}
256
257
				// If this is the current member we need to make their message count correct.
258
				if (User::$info->id == $row['id_member'])
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
259
				{
260
					User::$info->messages -= $row['num_deleted_messages'];
0 ignored issues
show
Bug Best Practice introduced by
The property messages does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
261
					if (!($row['is_read']))
262
					{
263
						User::$info->unread_messages -= $row['num_deleted_messages'];
0 ignored issues
show
Bug Best Practice introduced by
The property unread_messages does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
264
					}
265
				}
266
			}
267
		);
268
269
		// Do the actual deletion.
270
		$db->query('', '
271
			UPDATE {db_prefix}pm_recipients
272
			SET 
273
				deleted = {int:is_deleted}
274
			WHERE id_member IN ({array_int:member_list})
275
				AND deleted = {int:not_deleted}' . $where,
276
			array(
277
				'member_list' => $owner,
278
				'is_deleted' => 1,
279
				'not_deleted' => 0,
280
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
281
			)
282
		);
283
	}
284
285
	// If sender and recipients all have deleted their message, it can be removed.
286
	$remove_pms = array();
287
	$db->fetchQuery('
288
		SELECT
289
			pm.id_pm AS sender, pmr.id_pm
290
		FROM {db_prefix}personal_messages AS pm
291
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted})
292
		WHERE pm.deleted_by_sender = {int:is_deleted}
293
			' . str_replace('id_pm', 'pm.id_pm', $where) . '
294
		GROUP BY sender, pmr.id_pm
295
		HAVING pmr.id_pm IS null',
296
		array(
297
			'not_deleted' => 0,
298
			'is_deleted' => 1,
299
			'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
300
		)
301
	)->fetch_callback(
302
		function ($row) use (&$remove_pms) {
303
			$remove_pms[] = $row['sender'];
304
		}
305
	);
306
307
	if (!empty($remove_pms))
308
	{
309
		$db->query('', '
310
			DELETE FROM {db_prefix}personal_messages
311
			WHERE id_pm IN ({array_int:pm_list})',
312
			array(
313
				'pm_list' => $remove_pms,
314
			)
315
		);
316
317
		$db->query('', '
318
			DELETE FROM {db_prefix}pm_recipients
319
			WHERE id_pm IN ({array_int:pm_list})',
320
			array(
321
				'pm_list' => $remove_pms,
322
			)
323
		);
324
	}
325
326
	// Any cached numbers may be wrong now.
327
	Cache::instance()->put('labelCounts:' . User::$info->id, null, 720);
328
}
329
330
/**
331
 * Mark the specified personal messages read.
332
 *
333
 * @param int[]|int|null $personal_messages null or array of pm ids
334
 * @param string|null $label = null, if label is set, only marks messages with that label
335
 * @param int|null $owner = null, if owner is set, marks messages owned by that member id
336
 * @package PersonalMessage
337
 */
338
function markMessages($personal_messages = null, $label = null, $owner = null)
339
{
340
	if ($owner === null)
341
	{
342
		$owner = User::$info->id;
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
343
	}
344
345
	if (!is_null($personal_messages) && !is_array($personal_messages))
346
	{
347
		$personal_messages = array($personal_messages);
348
	}
349
350
	$db = database();
351
352
	$request = $db->fetchQuery('
353
		UPDATE {db_prefix}pm_recipients
354
		SET 
355
			is_read = is_read | 1
356
		WHERE id_member = {int:id_member}
357
			AND NOT (is_read & 1 >= 1)' . ($label === null ? '' : '
358
			AND FIND_IN_SET({string:label}, labels) != 0') . ($personal_messages !== null ? '
359
			AND id_pm IN ({array_int:personal_messages})' : ''),
360
		array(
361
			'personal_messages' => $personal_messages,
362
			'id_member' => $owner,
363
			'label' => $label,
364
		)
365
	);
366
367
	// If something wasn't marked as read, get the number of unread messages remaining.
368
	if ($request->affected_rows() > 0)
369
	{
370
		updatePMMenuCounts($owner);
371
	}
372
}
373
374
/**
375
 * Mark the specified personal messages as unread.
376
 *
377
 * @param int|int[] $personal_messages
378
 * @package PersonalMessage
379
 */
380
function markMessagesUnread($personal_messages)
381
{
382
	if (empty($personal_messages))
383
	{
384
		return;
385
	}
386
387
	if (!is_array($personal_messages))
388
	{
389
		$personal_messages = array($personal_messages);
390
	}
391
392
	$db = database();
393
394
	$owner = User::$info->id;
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
395
396
	// Flip the "read" bit on this
397
	$request = $db->fetchQuery('
398
		UPDATE {db_prefix}pm_recipients
399
		SET 
400
			is_read = is_read & 2
401
		WHERE id_member = {int:id_member}
402
			AND (is_read & 1 >= 1)
403
			AND id_pm IN ({array_int:personal_messages})',
404
		array(
405
			'personal_messages' => $personal_messages,
406
			'id_member' => $owner,
407
		)
408
	);
409
410
	// If something was marked unread, update the number of unread messages remaining.
411
	if ($request->affected_rows() > 0)
412
	{
413
		updatePMMenuCounts($owner);
414
	}
415
}
416
417
/**
418
 * Updates the number of unread messages for a user
419
 *
420
 * - Updates the per label totals as well as the overall total
421
 *
422
 * @param int $owner
423
 * @package PersonalMessage
424
 */
425
function updatePMMenuCounts($owner)
426
{
427
	global $context;
428
429
	$db = database();
430
431
	if ($owner == User::$info->id)
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
432
	{
433
		foreach ($context['labels'] as $label)
434
		{
435
			$context['labels'][(int) $label['id']]['unread_messages'] = 0;
436
		}
437
	}
438
439
	$total_unread = 0;
440
	$db->fetchQuery('
441
		SELECT
442
			labels, COUNT(*) AS num
443
		FROM {db_prefix}pm_recipients
444
		WHERE id_member = {int:id_member}
445
			AND NOT (is_read & 1 >= 1)
446
			AND deleted = {int:is_not_deleted}
447
		GROUP BY labels',
448
		array(
449
			'id_member' => $owner,
450
			'is_not_deleted' => 0,
451
		)
452
	)->fetch_callback(
453
		function ($row) use ($context, &$total_unread, $owner) {
454
			$total_unread += $row['num'];
455
456
			if ($owner != User::$info->id)
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
457
			{
458
				return;
459
			}
460
461
			$this_labels = explode(',', $row['labels']);
462
			foreach ($this_labels as $this_label)
463
			{
464
				$context['labels'][(int) $this_label]['unread_messages'] += $row['num'];
465
			}
466
		}
467
	);
468
469
	// Need to store all this.
470
	Cache::instance()->put('labelCounts:' . $owner, $context['labels'], 720);
471
	require_once(SUBSDIR . '/Members.subs.php');
472
	updateMemberData($owner, array('unread_messages' => $total_unread));
473
474
	// If it was for the current member, reflect this in the User::$info array too.
475
	if ($owner == User::$info->id)
476
	{
477
		User::$info->unread_messages = $total_unread;
0 ignored issues
show
Bug Best Practice introduced by
The property unread_messages does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __set, consider adding a @property annotation.
Loading history...
478
	}
479
}
480
481
/**
482
 * Check if the PM is available to the current user.
483
 *
484
 * @param int $pmID
485
 * @param string $validFor
486
 * @return bool|null
487
 * @package PersonalMessage
488
 */
489
function isAccessiblePM($pmID, $validFor = 'in_or_outbox')
490
{
491
	$db = database();
492
493
	$request = $db->fetchQuery('
494
		SELECT
495
			pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted} AS valid_for_outbox,
496
			pmr.id_pm IS NOT NULL AS valid_for_inbox
497
		FROM {db_prefix}personal_messages AS pm
498
			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})
499
		WHERE pm.id_pm = {int:id_pm}
500
			AND ((pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted}) OR pmr.id_pm IS NOT NULL)',
501
		array(
502
			'id_pm' => $pmID,
503
			'id_current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
504
			'not_deleted' => 0,
505
		)
506
	);
507
	if ($request->num_rows() === 0)
508
	{
509
		$request->free_result();
510
511
		return false;
512
	}
513
	$validationResult = $request->fetch_assoc();
514
	$request->free_result();
515
516
	switch ($validFor)
517
	{
518
		case 'inbox':
519
			return !empty($validationResult['valid_for_inbox']);
520
521
		case 'outbox':
522
			return !empty($validationResult['valid_for_outbox']);
523
524
		case 'in_or_outbox':
525
			return !empty($validationResult['valid_for_inbox']) || !empty($validationResult['valid_for_outbox']);
526
527
		default:
528
			trigger_error('Undefined validation type given', E_USER_ERROR);
529
	}
530
}
531
532
/**
533
 * Sends a personal message from the specified person to the specified people
534
 * ($from defaults to the user)
535
 *
536
 * @param mixed[] $recipients - an array containing the arrays 'to' and 'bcc', both containing id_member's.
537
 * @param string $subject - should have no slashes and no html entities
538
 * @param string $message - should have no slashes and no html entities
539
 * @param bool $store_outbox
540
 * @param mixed[]|null $from - an array with the id, name, and username of the member.
541
 * @param int $pm_head - the ID of the chain being replied to - if any.
542
 * @return mixed[] an array with log entries telling how many recipients were successful and which recipients it failed to send to.
543
 * @package PersonalMessage
544
 */
545
function sendpm($recipients, $subject, $message, $store_outbox = true, $from = null, $pm_head = 0)
546
{
547
	global $scripturl, $txt, $language, $modSettings, $webmaster_email;
548
549
	$db = database();
550
551 2
	// Make sure the PM language file is loaded, we might need something out of it.
552
	Txt::load('PersonalMessage');
553 2
554
	// Needed for our email and post functions
555
	require_once(SUBSDIR . '/Mail.subs.php');
556 2
	require_once(SUBSDIR . '/Post.subs.php');
557
558
	// Initialize log array.
559 2
	$log = array(
560 2
		'failed' => array(),
561
		'sent' => array()
562
	);
563
564 2
	if ($from === null)
565
	{
566
		$from = array(
567
			'id' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
568 2
			'name' => User::$info->name,
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
569
			'username' => User::$info->username
0 ignored issues
show
Bug Best Practice introduced by
The property username does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
570
		);
571 2
	}
572 2
	// Probably not needed.  /me something should be of the typer.
573 2
	else
574
	{
575
		User::$info->name = $from['name'];
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __set, consider adding a @property annotation.
Loading history...
576
	}
577
578
	// Integrated PMs
579
	call_integration_hook('integrate_personal_message', array(&$recipients, &$from, &$subject, &$message));
580
581
	// This is the one that will go in their inbox.
582
	$htmlmessage = Util::htmlspecialchars($message, ENT_QUOTES, 'UTF-8', true);
583 2
	preparsecode($htmlmessage);
584
	$htmlsubject = strtr(Util::htmlspecialchars($subject), array("\r" => '', "\n" => '', "\t" => ''));
585
	if (Util::strlen($htmlsubject) > 100)
586 2
	{
587 2
		$htmlsubject = Util::substr($htmlsubject, 0, 100);
588 2
	}
589 2
590
	// Make sure is an array
591
	if (!is_array($recipients))
0 ignored issues
show
The condition is_array($recipients) is always true.
Loading history...
592
	{
593
		$recipients = array($recipients);
594
	}
595 2
596
	// Get a list of usernames and convert them to IDs.
597
	$usernames = array();
598
	foreach ($recipients as $rec_type => $rec)
599
	{
600
		foreach ($rec as $id => $member)
601 2
		{
602 2
			if (!is_numeric($recipients[$rec_type][$id]))
603
			{
604 2
				$recipients[$rec_type][$id] = Util::strtolower(trim(preg_replace('/[<>&"\'=\\\]/', '', $recipients[$rec_type][$id])));
605
				$usernames[$recipients[$rec_type][$id]] = 0;
606 2
			}
607
		}
608
	}
609 1
610
	if (!empty($usernames))
611
	{
612
		$request = $db->fetchQuery('
613
			SELECT
614 2
				id_member, member_name
615
			FROM {db_prefix}members
616
			WHERE {column_case_insensitive:member_name} IN ({array_string_case_insensitive:usernames})',
617
			array(
618
				'usernames' => array_keys($usernames),
619
			)
620
		);
621
		while (($row = $request->fetch_assoc()))
622
		{
623
			if (isset($usernames[Util::strtolower($row['member_name'])]))
624
			{
625
				$usernames[Util::strtolower($row['member_name'])] = $row['id_member'];
626
			}
627
		}
628
		$request->free_result();
629
630
		// Replace the usernames with IDs. Drop usernames that couldn't be found.
631
		foreach ($recipients as $rec_type => $rec)
632
		{
633
			foreach ($rec as $id => $member)
634
			{
635
				if (is_numeric($recipients[$rec_type][$id]))
636
				{
637
					continue;
638
				}
639
640
				if (!empty($usernames[$member]))
641
				{
642
					$recipients[$rec_type][$id] = $usernames[$member];
643
				}
644
				else
645
				{
646
					$log['failed'][$id] = sprintf($txt['pm_error_user_not_found'], $recipients[$rec_type][$id]);
647
					unset($recipients[$rec_type][$id]);
648
				}
649
			}
650
		}
651
	}
652
653
	// Make sure there are no duplicate 'to' members.
654
	$recipients['to'] = array_unique($recipients['to']);
655
656
	// Only 'bcc' members that aren't already in 'to'.
657
	$recipients['bcc'] = array_diff(array_unique($recipients['bcc']), $recipients['to']);
658 2
659
	// Combine 'to' and 'bcc' recipients.
660
	$all_to = array_merge($recipients['to'], $recipients['bcc']);
661 2
662
	// Check no-one will want it deleted right away!
663
	$deletes = array();
664 2
	$db->fetchQuery('
665
		SELECT
666
			id_member, criteria, is_or
667 2
		FROM {db_prefix}pm_rules
668 2
		WHERE id_member IN ({array_int:to_members})
669
			AND delete_pm = {int:delete_pm}',
670
		array(
671
			'to_members' => $all_to,
672
			'delete_pm' => 1,
673
		)
674
	)->fetch_callback(
675 2
		function ($row) use (&$deletes, $from, $subject, $message) {
676 2
			// Check whether we have to apply anything...
677
			$criteria = Util::unserialize($row['criteria']);
678 2
679
			// Note we don't check the buddy status, cause deletion from buddy = madness!
680
			$delete = false;
681
			foreach ($criteria as $criterium)
682
			{
683
				if (($criterium['t'] === 'mid' && $criterium['v'] == $from['id'])
684
					|| ($criterium['t'] === 'gid' && in_array($criterium['v'], User::$info->groups))
0 ignored issues
show
It seems like ElkArte\User::info->groups can also be of type null; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

684
					|| ($criterium['t'] === 'gid' && in_array($criterium['v'], /** @scrutinizer ignore-type */ User::$info->groups))
Loading history...
Bug Best Practice introduced by
The property groups does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
685
					|| ($criterium['t'] === 'sub' && strpos($subject, $criterium['v']) !== false)
686
					|| ($criterium['t'] === 'msg' && strpos($message, $criterium['v']) !== false))
687
				{
688
					$delete = true;
689
				}
690
				// If we're adding and one criteria don't match then we stop!
691
				elseif (!$row['is_or'])
692
				{
693
					$delete = false;
694
					break;
695
				}
696
			}
697
			if ($delete)
698
			{
699
				$deletes[$row['id_member']] = 1;
700
			}
701
		}
702
	);
703
704
	// Load the membergroup message limits.
705 2
	static $message_limit_cache = array();
706
	if (!allowedTo('moderate_forum') && empty($message_limit_cache))
707
	{
708
		$db->fetchQuery('
709 2
			SELECT
710 2
				id_group, max_messages
711
			FROM {db_prefix}membergroups',
712
			array()
713
		)->fetch_callback(
714
			function ($row) use (&$message_limit_cache) {
715
				$message_limit_cache[$row['id_group']] = $row['max_messages'];
716
			}
717
		);
718
	}
719
720
	// Load the groups that are allowed to read PMs.
721
	// @todo move into a separate function on $permission.
722
	$allowed_groups = array();
723
	$disallowed_groups = array();
724
	$db->fetchQuery('
725
		SELECT
726 2
			id_group, add_deny
727 2
		FROM {db_prefix}permissions
728 2
		WHERE permission = {string:read_permission}',
729
		array(
730
			'read_permission' => 'pm_read',
731
		)
732
	)->fetch_callback(
733
		function ($row) use (&$disallowed_groups, &$allowed_groups) {
734 2
			if (empty($row['add_deny']))
735
			{
736 2
				$disallowed_groups[] = $row['id_group'];
737
			}
738 2
			else
739
			{
740
				$allowed_groups[] = $row['id_group'];
741
			}
742
		}
743
	);
744 2
745
	if (empty($modSettings['permission_enable_deny']))
746 2
	{
747
		$disallowed_groups = array();
748
	}
749 2
750
	$request = $db->fetchQuery('
751 2
		SELECT
752
			member_name, real_name, id_member, email_address, lngfile,
753
			pm_email_notify, personal_messages,' . (allowedTo('moderate_forum') ? ' 0' : '
754 2
			(receive_from = {int:admins_only}' . (empty($modSettings['enable_buddylist']) ? '' : ' OR
755
			(receive_from = {int:buddies_only} AND FIND_IN_SET({string:from_id}, buddy_list) = 0) OR
756
			(receive_from = {int:not_on_ignore_list} AND FIND_IN_SET({string:from_id}, pm_ignore_list) != 0)') . ')') . ' AS ignored,
757 2
			FIND_IN_SET({string:from_id}, buddy_list) != 0 AS is_buddy, is_activated,
758
			additional_groups, id_group, id_post_group
759
		FROM {db_prefix}members
760 2
		WHERE id_member IN ({array_int:recipients})
761
		ORDER BY lngfile
762
		LIMIT {int:count_recipients}',
763
		array(
764
			'not_on_ignore_list' => 1,
765
			'buddies_only' => 2,
766
			'admins_only' => 3,
767
			'recipients' => $all_to,
768 2
			'count_recipients' => count($all_to),
769 2
			'from_id' => $from['id'],
770 2
		)
771 2
	);
772 2
	$notifications = array();
773 2
	while (($row = $request->fetch_assoc()))
774
	{
775
		// Don't do anything for members to be deleted!
776 2
		if (isset($deletes[$row['id_member']]))
777 2
		{
778
			continue;
779
		}
780 2
781
		// We need to know this members groups.
782
		$groups = array_merge([$row['id_group'], $row['id_post_group']], (empty($row['additional_groups']) ? [] : explode(',', $row['additional_groups'])));
783
784
		$message_limit = -1;
785
786 2
		// For each group see whether they've gone over their limit - assuming they're not an admin.
787 2
		if (!in_array(1, $groups))
788 2
		{
789
			foreach ($groups as $id)
790 2
			{
791
				if (isset($message_limit_cache[$id]) && $message_limit != 0 && $message_limit < $message_limit_cache[$id])
792
				{
793 2
					$message_limit = $message_limit_cache[$id];
794
				}
795
			}
796
797
			if ($message_limit > 0 && $message_limit <= $row['personal_messages'])
798
			{
799
				$log['failed'][$row['id_member']] = sprintf($txt['pm_error_data_limit_reached'], $row['real_name']);
800
				unset($all_to[array_search($row['id_member'], $all_to)]);
801
				continue;
802
			}
803
804
			// Do they have any of the allowed groups?
805
			if (count(array_intersect($allowed_groups, $groups)) == 0 || count(array_intersect($disallowed_groups, $groups)) != 0)
806
			{
807
				$log['failed'][$row['id_member']] = sprintf($txt['pm_error_user_cannot_read'], $row['real_name']);
808
				unset($all_to[array_search($row['id_member'], $all_to)]);
809
				continue;
810
			}
811
		}
812
813
		// Note that PostgreSQL can return a lowercase t/f for FIND_IN_SET
814
		if (!empty($row['ignored']) && $row['ignored'] !== 'f' && $row['id_member'] != $from['id'])
815
		{
816
			$log['failed'][$row['id_member']] = sprintf($txt['pm_error_ignored_by_user'], $row['real_name']);
817
			unset($all_to[array_search($row['id_member'], $all_to)]);
818
			continue;
819
		}
820 2
821
		// If the receiving account is banned (>=10) or pending deletion (4), refuse to send the PM.
822
		if ($row['is_activated'] >= 10 || ($row['is_activated'] == 4 && User::$info->is_admin === false))
0 ignored issues
show
Bug Best Practice introduced by
The property is_admin does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
823
		{
824
			$log['failed'][$row['id_member']] = sprintf($txt['pm_error_user_cannot_read'], $row['real_name']);
825
			unset($all_to[array_search($row['id_member'], $all_to)]);
826
			continue;
827
		}
828 2
829
		// Send a notification, if enabled - taking the buddy list into account.
830
		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)
831
		{
832
			$notifications[empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']][] = $row['email_address'];
833
		}
834
835
		$log['sent'][$row['id_member']] = sprintf($txt['pm_successfully_sent'] ?? '', $row['real_name']);
836 2
	}
837
	$request->free_result();
838
839
	// Only 'send' the message if there are any recipients left.
840
	if (empty($all_to))
841 2
	{
842
		return $log;
843 2
	}
844
845
	// Track the pm count for our stats
846 2
	if (!empty($modSettings['trackStats']))
847
	{
848
		trackStats(array('pm' => '+'));
849
	}
850
851
	// Insert the message itself and then grab the last insert id.
852 2
	$db->insert('',
853
		'{db_prefix}personal_messages',
854 2
		array(
855
			'id_pm_head' => 'int', 'id_member_from' => 'int', 'deleted_by_sender' => 'int',
856
			'from_name' => 'string-255', 'msgtime' => 'int', 'subject' => 'string-255', 'body' => 'string-65534',
857
		),
858 2
		array(
859 2
			$pm_head, $from['id'], ($store_outbox ? 0 : 1),
860
			$from['username'], time(), $htmlsubject, $htmlmessage,
861 2
		),
862
		array('id_pm')
863
	);
864
	$id_pm = $db->insert_id('{db_prefix}personal_messages');
865 2
866 2
	// Add the recipients.
867
	$to_list = array();
868 2
	if (!empty($id_pm))
869
	{
870 2
		// If this is new we need to set it part of it's own conversation.
871
		if (empty($pm_head))
872
		{
873 2
			$db->query('', '
874 2
				UPDATE {db_prefix}personal_messages
875
				SET 
876
					id_pm_head = {int:id_pm_head}
877 2
				WHERE id_pm = {int:id_pm_head}',
878
				array(
879 2
					'id_pm_head' => $id_pm,
880
				)
881
			);
882
		}
883
884
		// Some people think manually deleting personal_messages is fun... it's not. We protect against it though :)
885 2
		$db->query('', '
886
			DELETE FROM {db_prefix}pm_recipients
887
			WHERE id_pm = {int:id_pm}',
888
			array(
889
				'id_pm' => $id_pm,
890
			)
891 2
		);
892
893
		$insertRows = array();
894
		foreach ($all_to as $to)
895 2
		{
896
			$insertRows[] = array($id_pm, $to, in_array($to, $recipients['bcc']) ? 1 : 0, isset($deletes[$to]) ? 1 : 0, 1);
897
			if (!in_array($to, $recipients['bcc']))
898
			{
899 2
				$to_list[] = $to;
900 2
			}
901
		}
902 2
903 2
		$db->insert('insert',
904
			'{db_prefix}pm_recipients',
905 2
			array(
906
				'id_pm' => 'int', 'id_member' => 'int', 'bcc' => 'int', 'deleted' => 'int', 'is_new' => 'int'
907
			),
908
			$insertRows,
909 2
			array('id_pm', 'id_member')
910 2
		);
911
	}
912 2
913
	$maillist = !empty($modSettings['maillist_enabled']) && !empty($modSettings['pbe_pm_enabled']);
914 1
915 2
	// If they have post by email enabled, override disallow_sendBody
916
	if (!$maillist && !empty($modSettings['disallow_sendBody']))
917
	{
918
		$message = '';
919 2
	}
920
921
	$to_names = [];
922 2
	if (count($to_list) > 1)
923
	{
924
		require_once(SUBSDIR . '/Members.subs.php');
925
		$result = getBasicMemberData($to_list);
926
		foreach ($result as $row)
927
		{
928
			$to_names[] = un_htmlspecialchars($row['real_name']);
929 2
		}
930 2
	}
931
932
	$mailPreparse = new PreparseMail();
933 2
	$replacements = [
934 2
		'SUBJECT' => $mailPreparse->preparseSubject($subject),
935
		'MESSAGE' => $mailPreparse->preparseHtml($message),
936
		'SENDER' => un_htmlspecialchars($from['name']),
937
		'READLINK' => $scripturl . '?action=pm;pmsg=' . $id_pm . '#msg' . $id_pm,
938
		'REPLYLINK' => $scripturl . '?action=pm;sa=send;f=inbox;pmsg=' . $id_pm . ';quote;u=' . $from['id'],
939
		'TOLIST' => implode(', ', $to_names),
940
		'UNSUBSCRIBELINK' => $scripturl . '?action=pm;sa=settings',
941
	];
942
943
	// Select the right template
944
	$email_template = ($maillist && empty($modSettings['disallow_sendBody']) ? 'pbe_' : '') . 'new_pm' . (empty($modSettings['disallow_sendBody']) ? '_body' : '') . (!empty($to_names) ? '_tolist' : '');
945 2
946 2
	foreach ($notifications as $lang => $notification_list)
947 2
	{
948 2
		$sendMail = new BuildMail();
949 2
		$sendMail->setEmailReplacements($replacements);
950 2
951
		// Using maillist functionality
952
		if ($maillist)
953
		{
954 2
			$sender_details = query_sender_wrapper($from['id']);
955
956 2
			// @todo guest contact us is routing through here
957
			if (empty($sender_details))
958
			{
959
				continue;
960
			}
961
962
			$from_wrapper = !empty($modSettings['maillist_mail_from']) ? $modSettings['maillist_mail_from'] : (empty($modSettings['maillist_sitename_address']) ? $webmaster_email : $modSettings['maillist_sitename_address']);
963
964
			// Add in the signature
965
			$replacements['SIGNATURE'] = $mailPreparse->preparseSignature($sender_details['signature']);
966
967
			// And off it goes, looking a bit more personal
968
			$mail = loadEmailTemplate($email_template, $replacements, $lang, true);
969
			$reference = !empty($pm_head) ? $pm_head : null;
970
971
			$sendMail->buildEmail($notification_list, $mail['subject'], $mail['body'], $from['name'], 'p' . $id_pm, true, 2, true, $from_wrapper, $reference);
972
		}
973
		else
974
		{
975
			// Off the notification email goes!
976
			$mail = loadEmailTemplate($email_template, $replacements, $lang, true);
977
			$sendMail->buildEmail($notification_list, $mail['subject'], $mail['body'], null, 'p' . $id_pm, true, 2, true);
978
		}
979
	}
980
981 2
	// Integrated After PMs
982
	call_integration_hook('integrate_personal_message_after', array(&$id_pm, &$log, &$recipients, &$from, &$subject, &$message));
983
984 2
	// Back to what we were on before!
985
	Txt::load('index+PersonalMessage');
986
987 2
	// Add one to their unread and read message counts.
988
	foreach ($all_to as $k => $id)
989 2
	{
990
		if (isset($deletes[$id]))
991 1
		{
992
			unset($all_to[$k]);
993
		}
994
	}
995 2
996
	if (!empty($all_to))
997 2
	{
998 2
		require_once(SUBSDIR . '/Members.subs.php');
999
		updateMemberData($all_to, array('personal_messages' => '+', 'unread_messages' => '+', 'new_pm' => 1));
1000
	}
1001 2
1002
	return $log;
1003
}
1004
1005
/**
1006
 * Fetches the senders email wrapper details
1007
 *
1008
 * - Gets the senders signature for inclusion in the email
1009
 * - Gets the senders email address and visibility flag
1010
 *
1011
 * @param string $from
1012
 * @return array
1013
 */
1014
function query_sender_wrapper($from)
1015
{
1016
	$db = database();
1017
1018
	// The signature and email visibility details
1019
	$request = $db->query('', '
1020
		SELECT
1021
			email_address, signature
1022
		FROM {db_prefix}members
1023
		WHERE id_member  = {int:uid}
1024
			AND is_activated = {int:act}
1025
		LIMIT 1',
1026 2
		[
1027
			'uid' => $from,
1028 2
			'act' => 1,
1029
		]
1030
	);
1031
	$result = $request->fetch_assoc();
1032 2
	$request->free_result();
1033
1034
	return $result;
1035
}
1036
1037
/**
1038
 * Load personal messages.
1039
 *
1040
 * This function loads messages considering the options given, an array of:
1041
 * - 'display_mode' - the PMs display mode (i.e. conversation, all)
1042
 * - 'is_postgres' - (temporary) boolean to allow choice of PostgreSQL-specific sorting query
1043
 * - 'sort_by_query' - query to sort by
1044
 * - 'descending' - whether to sort descending
1045
 * - 'sort_by' - field to sort by
1046
 * - 'pmgs' - personal message id (if any). Note: it may not be set.
1047
 * - 'label_query' - query by labels
1048
 * - 'start' - start id, if any
1049
 *
1050
 * @param mixed[] $pm_options options for loading
1051
 * @param int $id_member id member
1052
 *
1053
 * @return array
1054
 * @package PersonalMessage
1055
 */
1056
function loadPMs($pm_options, $id_member)
1057
{
1058
	global $options;
1059
1060
	$db = database();
1061
1062
	// First work out what messages we need to see - if grouped is a little trickier...
1063
	// Conversation mode
1064
	if ($pm_options['display_mode'] === 2)
1065
	{
1066
		// On a non-default sort, when using PostgreSQL we have to do a harder sort.
1067
		if ($db->title() === 'PostgreSQL' && $pm_options['sort_by_query'] !== 'pm.id_pm')
1068
		{
1069
			$sub_pms = array();
1070
			$db->fetchQuery('
1071
				SELECT
1072
					MAX({raw:sort}) AS sort_param, pm.id_pm_head
1073
				FROM {db_prefix}personal_messages AS pm' . ($pm_options['folder'] === 'sent' ? ($pm_options['sort_by'] === 'name' ? '
1074
					LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
1075
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
1076
						AND pmr.id_member = {int:current_member}
1077
						AND pmr.deleted = {int:not_deleted}
1078
						' . $pm_options['label_query'] . ')') . ($pm_options['sort_by'] === 'name' ? ('
1079
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})') : '') . '
1080
				WHERE ' . ($pm_options['folder'] === 'sent' ? 'pm.id_member_from = {int:current_member}
1081
					AND pm.deleted_by_sender = {int:not_deleted}' : '1=1') . (empty($pm_options['pmsg']) ? '' : '
1082
					AND pm.id_pm = {int:id_pm}') . '
1083
				GROUP BY pm.id_pm_head
1084
				ORDER BY sort_param' . ($pm_options['descending'] ? ' DESC' : ' ASC') . (empty($pm_options['pmsg']) ? '
1085
				LIMIT ' . $pm_options['limit'] . ' OFFSET ' . $pm_options['start'] : ''),
1086
				array(
1087
					'current_member' => $id_member,
1088
					'not_deleted' => 0,
1089
					'id_member' => $pm_options['folder'] === 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
1090
					'id_pm' => $pm_options['pmsg'] ?? '0',
1091
					'sort' => $pm_options['sort_by_query'],
1092
				)
1093
			)->fetch_callback(
1094
				function ($row) use (&$sub_pms) {
1095
					$sub_pms[$row['id_pm_head']] = $row['sort_param'];
1096
				}
1097
			);
1098
1099
			// Now we use those results in the next query
1100
			$request = $db->query('', '
1101
				SELECT
1102
					pm.id_pm AS id_pm, pm.id_pm_head
1103
				FROM {db_prefix}personal_messages AS pm' . ($pm_options['folder'] === 'sent' ? ($pm_options['sort_by'] === 'name' ? '
1104
					LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
1105
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
1106
						AND pmr.id_member = {int:current_member}
1107
						AND pmr.deleted = {int:not_deleted}
1108
						' . $pm_options['label_query'] . ')') . ($pm_options['sort_by'] === 'name' ? ('
1109
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})') : '') . '
1110
				WHERE ' . (empty($sub_pms) ? '0=1' : 'pm.id_pm IN ({array_int:pm_list})') . '
1111
				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']) ? '
1112
				LIMIT ' . $pm_options['limit'] . ' OFFSET ' . $pm_options['start'] : ''),
1113
				array(
1114
					'current_member' => $id_member,
1115
					'pm_list' => array_keys($sub_pms),
1116
					'not_deleted' => 0,
1117
					'sort' => $pm_options['sort_by_query'],
1118
					'id_member' => $pm_options['folder'] === 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
1119
				)
1120
			);
1121
		}
1122
		// Otherwise we can just use the pm_conversation_list option
1123 2
		else
1124
		{
1125
			$request = $db->query('pm_conversation_list', '
1126 2
				SELECT
1127
					MAX(pm.id_pm) AS id_pm, pm.id_pm_head
1128
				FROM {db_prefix}personal_messages AS pm' . ($pm_options['folder'] === 'sent' ? ($pm_options['sort_by'] === 'name' ? '
1129
					LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
1130
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
1131 2
						AND pmr.id_member = {int:current_member}
1132 2
						AND pmr.deleted = {int:deleted_by}
1133 2
						' . $pm_options['label_query'] . ')') . ($pm_options['sort_by'] === 'name' ? ('
1134 2
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
1135 2
				WHERE ' . ($pm_options['folder'] === 'sent' ? 'pm.id_member_from = {int:current_member}
1136 2
					AND pm.deleted_by_sender = {int:deleted_by}' : '1=1') . (empty($pm_options['pmsg']) ? '' : '
1137 2
					AND pm.id_pm = {int:pmsg}') . '
1138
				GROUP BY pm.id_pm_head
1139 2
				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']) ? '
1140 2
				LIMIT ' . $pm_options['limit'] . ' OFFSET ' . $pm_options['start'] : ''),
1141 2
				array(
1142 2
					'current_member' => $id_member,
1143 2
					'deleted_by' => 0,
1144
					'sort' => $pm_options['sort_by_query'],
1145
					'pm_member' => $pm_options['folder'] === 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
1146
					'pmsg' => isset($pm_options['pmsg']) ? (int) $pm_options['pmsg'] : 0,
1147
				)
1148 2
			);
1149 2
		}
1150 2
	}
1151 2
	// If not in conversation view, then this is kinda simple!
1152 2
	else
1153
	{
1154
		// @todo SLOW This query uses a filesort. (inbox only.)
1155
		$request = $db->query('', '
1156
			SELECT
1157
				pm.id_pm, pm.id_pm_head, pm.id_member_from
1158
			FROM {db_prefix}personal_messages AS pm' . ($pm_options['folder'] === 'sent' ? ($pm_options['sort_by'] === 'name' ? '
1159
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
1160
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
1161
					AND pmr.id_member = {int:current_member}
1162
					AND pmr.deleted = {int:is_deleted}
1163
					' . $pm_options['label_query'] . ')') . ($pm_options['sort_by'] === 'name' ? ('
1164
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
1165
			WHERE ' . ($pm_options['folder'] === 'sent' ? 'pm.id_member_from = {raw:current_member}
1166
				AND pm.deleted_by_sender = {int:is_deleted}' : '1=1') . (empty($pm_options['pmsg']) ? '' : '
1167
				AND pm.id_pm = {int:pmsg}') . '
1168
			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']) ? '
1169
			LIMIT ' . $pm_options['limit'] . ' OFFSET ' . $pm_options['start'] : ''),
1170
			array(
1171
				'current_member' => $id_member,
1172
				'is_deleted' => 0,
1173
				'sort' => $pm_options['sort_by_query'],
1174
				'pm_member' => $pm_options['folder'] === 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
1175
				'pmsg' => isset($pm_options['pmsg']) ? (int) $pm_options['pmsg'] : 0,
1176
			)
1177
		);
1178 2
	}
1179
	// Load the id_pms and initialize recipients.
1180 2
	$pms = array();
1181
	$lastData = array();
1182
	$posters = $pm_options['folder'] === 'sent' ? array($id_member) : array();
1183
	$recipients = array();
1184
	while (($row = $request->fetch_assoc()))
1185
	{
1186
		if (!isset($recipients[$row['id_pm']]))
1187
		{
1188
			if (isset($row['id_member_from']))
1189
			{
1190
				$posters[$row['id_pm']] = $row['id_member_from'];
1191
			}
1192
1193
			$pms[$row['id_pm']] = $row['id_pm'];
1194
1195
			$recipients[$row['id_pm']] = array(
1196
				'to' => array(),
1197
				'bcc' => array()
1198
			);
1199
		}
1200
1201
		// Keep track of the last message, so we know what the head is without another query!
1202
		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']))
1203
		{
1204
			$lastData = array(
1205
				'id' => $row['id_pm'],
1206
				'head' => $row['id_pm_head'],
1207
			);
1208
		}
1209
	}
1210
	$request->free_result();
1211
1212
	return array($pms, $posters, $recipients, $lastData);
1213
}
1214
1215
/**
1216
 * How many PMs have you sent lately?
1217
 *
1218
 * @param int $id_member id member
1219
 * @param int $time time interval (in seconds)
1220
 *
1221
 * @return mixed
1222
 * @package PersonalMessage
1223
 */
1224
function pmCount($id_member, $time)
1225
{
1226
	$db = database();
1227
1228
	$db->fetchQuery('
1229
		SELECT
1230
			COUNT(*) AS post_count
1231
		FROM {db_prefix}personal_messages AS pm
1232
			INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
1233
		WHERE pm.id_member_from = {int:current_member}
1234
			AND pm.msgtime > {int:msgtime}',
1235
		array(
1236
			'current_member' => $id_member,
1237
			'msgtime' => time() - $time,
1238
		)
1239
	)->fetch_callback(
1240
		function ($row) use (&$pmCount) {
1241
			$pmCount = $row['post_count'];
1242
		}
1243
	);
1244
1245
	return $pmCount;
1246
}
1247
1248
/**
1249
 * This will apply rules to all unread messages.
1250
 *
1251
 * - If all_messages is set will, clearly, do it to all!
1252
 *
1253
 * @param bool $all_messages = false
1254
 * @package PersonalMessage
1255
 */
1256
function applyRules($all_messages = false)
1257
{
1258
	global $context, $options;
1259
1260
	$db = database();
1261
1262
	// Want this - duh!
1263
	loadRules();
1264
1265
	// No rules?
1266
	if (empty($context['rules']))
1267
	{
1268
		return;
1269
	}
1270
1271
	// Just unread ones?
1272
	$ruleQuery = $all_messages ? '' : ' AND pmr.is_new = 1';
1273
1274
	// @todo Apply all should have timeout protection!
1275
	// Get all the messages that match this.
1276
	$actions = array();
1277
	$db->fetchQuery('
1278
		SELECT
1279
			pmr.id_pm, pm.id_member_from, pm.subject, pm.body, mem.id_group, pmr.labels
1280
		FROM {db_prefix}pm_recipients AS pmr
1281
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1282
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1283
		WHERE pmr.id_member = {int:current_member}
1284
			AND pmr.deleted = {int:not_deleted}
1285
			' . $ruleQuery,
1286
		array(
1287
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1288
			'not_deleted' => 0,
1289
		)
1290
	)->fetch_callback(
1291
		function ($row) use ($context, &$actions) {
1292
			foreach ($context['rules'] as $rule)
1293
			{
1294
				$match = false;
1295
1296
				// Loop through all the criteria hoping to make a match.
1297
				foreach ($rule['criteria'] as $criterium)
1298
				{
1299
					if (($criterium['t'] === 'mid' && $criterium['v'] == $row['id_member_from'])
1300
						|| ($criterium['t'] === 'gid' && $criterium['v'] == $row['id_group'])
1301
						|| ($criterium['t'] === 'sub' && strpos($row['subject'], $criterium['v']) !== false)
1302
						|| ($criterium['t'] === 'msg' && strpos($row['body'], $criterium['v']) !== false))
1303
					{
1304
						$match = true;
1305
					}
1306
					// If we're adding and one criteria don't match then we stop!
1307
					elseif ($rule['logic'] === 'and')
1308
					{
1309
						$match = false;
1310
						break;
1311
					}
1312
				}
1313
1314
				// If we have a match the rule must be true - act!
1315
				if ($match)
1316
				{
1317
					if ($rule['delete'])
1318
					{
1319
						$actions['deletes'][] = $row['id_pm'];
1320
					}
1321
					else
1322
					{
1323
						foreach ($rule['actions'] as $ruleAction)
1324
						{
1325
							if ($ruleAction['t'] === 'lab')
1326
							{
1327
								// Get a basic pot started!
1328
								if (!isset($actions['labels'][$row['id_pm']]))
1329
								{
1330
									$actions['labels'][$row['id_pm']] = empty($row['labels'])
1331
										? array()
1332
										: explode(',', $row['labels']);
1333
								}
1334
1335
								$actions['labels'][$row['id_pm']][] = $ruleAction['v'];
1336
							}
1337
						}
1338
					}
1339
				}
1340
			}
1341
		}
1342
	);
1343
1344
	// Deletes are easy!
1345
	if (!empty($actions['deletes']))
1346
	{
1347
		deleteMessages($actions['deletes']);
1348
	}
1349
1350
	// Re-label?
1351
	if (!empty($actions['labels']))
1352
	{
1353
		foreach ($actions['labels'] as $pm => $labels)
1354
		{
1355
			// Quickly check each label is valid!
1356
			$realLabels = array();
1357
			foreach ($context['labels'] as $label)
1358
			{
1359
				if (in_array($label['id'], $labels) && ($label['id'] !== "-1" || empty($options['pm_remove_inbox_label'])))
1360
				{
1361
					$realLabels[] = $label['id'];
1362
				}
1363
			}
1364
1365
			$db->query('', '
1366
				UPDATE {db_prefix}pm_recipients
1367
				SET 
1368
					labels = {string:new_labels}
1369
				WHERE id_pm = {int:id_pm}
1370
					AND id_member = {int:current_member}',
1371
				array(
1372
					'current_member' => User::$info->id,
1373
					'id_pm' => $pm,
1374
					'new_labels' => empty($realLabels) ? '' : implode(',', $realLabels),
1375
				)
1376
			);
1377
		}
1378
	}
1379
}
1380
1381
/**
1382
 * Load up all the rules for the current user.
1383
 *
1384
 * @param bool $reload = false
1385
 * @package PersonalMessage
1386
 */
1387
function loadRules($reload = false)
1388
{
1389
	global $context;
1390
1391
	$db = database();
1392
1393
	if (isset($context['rules']) && !$reload)
1394
	{
1395
		return;
1396
	}
1397
1398
	// This is just a simple list of "all" known rules
1399
	$context['known_rules'] = array(
1400
		// member_id == "Sender Name"
1401
		'mid',
1402
		// group_id == "Sender's Groups"
1403
		'gid',
1404
		// subject == "Message Subject Contains"
1405
		'sub',
1406
		// message == "Message Body Contains"
1407
		'msg',
1408
		// buddy == "Sender is Buddy"
1409
		'bud',
1410
	);
1411
1412
	$context['rules'] = array();
1413
	$db->fetchQuery('
1414
		SELECT
1415
			id_rule, rule_name, criteria, actions, delete_pm, is_or
1416
		FROM {db_prefix}pm_rules
1417
		WHERE id_member = {int:current_member}',
1418
		array(
1419
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1420
		)
1421
	)->fetch_callback(
1422
		function ($row) use (&$context) {
1423
			$context['rules'][$row['id_rule']] = array(
1424
				'id' => $row['id_rule'],
1425
				'name' => $row['rule_name'],
1426
				'criteria' => Util::unserialize($row['criteria']),
1427
				'actions' => Util::unserialize($row['actions']),
1428
				'delete' => $row['delete_pm'],
1429
				'logic' => $row['is_or'] ? 'or' : 'and',
1430
			);
1431
1432
			if ($row['delete_pm'])
1433
			{
1434
				$context['rules'][$row['id_rule']]['actions'][] = array('t' => 'del', 'v' => 1);
1435
			}
1436
		}
1437
	);
1438
}
1439
1440
/**
1441
 * Update PM recipient when they receive or read a new PM
1442
 *
1443
 * @param int $id_member
1444
 * @param bool $new = false
1445
 * @package PersonalMessage
1446
 */
1447
function toggleNewPM($id_member, $new = false)
1448
{
1449
	$db = database();
1450
1451
	$db->fetchQuery('
1452
		UPDATE {db_prefix}pm_recipients
1453
		SET 
1454
			is_new = ' . ($new ? '{int:new}' : '{int:not_new}') . '
1455
		WHERE id_member = {int:current_member}',
1456
		array(
1457
			'current_member' => $id_member,
1458
			'new' => 1,
1459
			'not_new' => 0
1460
		)
1461
	);
1462
}
1463
1464
/**
1465
 * Load the PM limits for each group or for a specified group
1466
 *
1467
 * @param int|bool $id_group (optional) the id of a membergroup
1468
 *
1469
 * @return array
1470
 * @package PersonalMessage
1471
 */
1472
function loadPMLimits($id_group = false)
1473
{
1474
	$db = database();
1475
1476
	$groups = array();
1477
	$db->fetchQuery('
1478
		SELECT
1479
			id_group, group_name, max_messages
1480
		FROM {db_prefix}membergroups' . ($id_group ? '
1481
		WHERE id_group = {int:id_group}' : '
1482
		ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name'),
1483
		array(
1484
			'id_group' => $id_group,
1485
			'newbie_group' => 4,
1486
		)
1487
	)->fetch_callback(
1488
		function ($row) use (&$groups) {
1489
			if ($row['id_group'] != 1)
1490
			{
1491
				$groups[$row['id_group']] = $row;
1492
			}
1493
		}
1494
	);
1495
1496
	return $groups;
1497
}
1498
1499
/**
1500
 * Retrieve the discussion one or more PMs belong to
1501
 *
1502
 * @param int[] $id_pms
1503
 *
1504
 * @return array
1505
 * @package PersonalMessage
1506
 */
1507
function getDiscussions($id_pms)
1508
{
1509
	$db = database();
1510
1511
	$pm_heads = array();
1512
	$db->fetchQuery('
1513
		SELECT
1514
			id_pm_head, id_pm
1515
		FROM {db_prefix}personal_messages
1516
		WHERE id_pm IN ({array_int:id_pms})',
1517
		array(
1518
			'id_pms' => $id_pms,
1519
		)
1520
	)->fetch_callback(
1521
		function ($row) use (&$pm_heads) {
1522
			$pm_heads[$row['id_pm_head']] = $row['id_pm'];
1523
		}
1524
	);
1525
1526
	return $pm_heads;
1527
}
1528
1529
/**
1530
 * Return all the PMs belonging to one or more discussions
1531
 *
1532
 * @param int[] $pm_heads array of pm id head nodes
1533
 *
1534
 * @return array
1535
 * @package PersonalMessage
1536
 */
1537
function getPmsFromDiscussion($pm_heads)
1538
{
1539
	$db = database();
1540
1541
	$pms = array();
1542
	$db->fetchQuery('
1543
		SELECT
1544
			id_pm, id_pm_head
1545
		FROM {db_prefix}personal_messages
1546
		WHERE id_pm_head IN ({array_int:pm_heads})',
1547
		array(
1548
			'pm_heads' => $pm_heads,
1549
		)
1550
	)->fetch_callback(
1551
		function ($row) use (&$pms) {
1552
			// Copy the action from the single to PM to the others.
1553
			$pms[$row['id_pm']] = $row['id_pm_head'];
1554
		}
1555
	);
1556
1557
	return $pms;
1558
}
1559
1560
/**
1561
 * Determines the PMs which need an updated label.
1562
 *
1563
 * @param mixed[] $to_label
1564
 * @param string[] $label_type
1565
 * @param int $user_id
1566
 * @return int|null
1567
 * @package PersonalMessage
1568
 */
1569
function changePMLabels($to_label, $label_type, $user_id)
1570
{
1571
	global $options;
1572
1573
	$db = database();
1574
1575
	$to_update = array();
1576
1577
	// Get information about each message...
1578
	$db->fetchQuery('
1579
		SELECT
1580
			id_pm, labels
1581
		FROM {db_prefix}pm_recipients
1582
		WHERE id_member = {int:current_member}
1583
			AND id_pm IN ({array_int:to_label})
1584
		LIMIT ' . count($to_label),
1585
		array(
1586
			'current_member' => $user_id,
1587
			'to_label' => array_keys($to_label),
1588
		)
1589
	)->fetch_callback(
1590
		function ($row) use ($options, &$to_update, &$to_label, &$label_type) {
1591
			$labels = $row['labels'] === '' ? array('-1') : explode(',', trim($row['labels']));
1592
1593
			// Already exists?  Then... unset it!
1594
			$id_label = array_search($to_label[$row['id_pm']], $labels);
1595
1596
			if ($id_label !== false && $label_type[$row['id_pm']] !== 'add')
1597
			{
1598
				unset($labels[$id_label]);
1599
			}
1600
			elseif ($label_type[$row['id_pm']] !== 'rem')
1601
			{
1602
				$labels[] = $to_label[$row['id_pm']];
1603
			}
1604
1605
			if (!empty($options['pm_remove_inbox_label'])
1606
				&& $to_label[$row['id_pm']] !== '-1'
1607
				&& ($key = array_search('-1', $labels)) !== false)
1608
			{
1609
				unset($labels[$key]);
1610
			}
1611
1612
			$set = implode(',', array_unique($labels));
1613
			if ($set === '')
1614
			{
1615
				$set = '-1';
1616
			}
1617
1618
			$to_update[$row['id_pm']] = $set;
1619
		}
1620
	);
1621
1622
	if (!empty($to_update))
1623
	{
1624
		return updatePMLabels($to_update, $user_id);
1625
	}
1626
}
1627
1628
/**
1629
 * Detects personal messages which need a new label.
1630
 *
1631
 * @param mixed[] $searchArray
1632
 * @param mixed[] $new_labels
1633
 * @param int $user_id
1634
 * @return int|null
1635
 * @package PersonalMessage
1636
 */
1637
function updateLabelsToPM($searchArray, $new_labels, $user_id)
1638
{
1639
	$db = database();
1640
1641
	$to_update = array();
1642
1643
	// Now find the messages to change.
1644
	$db->fetchQuery('
1645
		SELECT
1646
			id_pm, labels
1647
		FROM {db_prefix}pm_recipients
1648
		WHERE FIND_IN_SET({raw:find_label_implode}, labels) != 0
1649
			AND id_member = {int:current_member}',
1650
		array(
1651
			'current_member' => $user_id,
1652
			'find_label_implode' => '\'' . implode('\', labels) != 0 OR FIND_IN_SET(\'', $searchArray) . '\'',
1653
		)
1654
	)->fetch_callback(
1655
		function ($row) use (&$to_update, $searchArray, $new_labels) {
1656
			// Do the long task of updating them...
1657
			$toChange = explode(',', $row['labels']);
1658
1659
			foreach ($toChange as $key => $value)
1660
			{
1661
				if (in_array($value, $searchArray))
1662
				{
1663
					if (isset($searchArray[$value]))
1664
					{
1665
						$toChange[$key] = $new_labels[$value];
1666
					}
1667
					else
1668
					{
1669
						unset($toChange[$key]);
1670
					}
1671
				}
1672
			}
1673
1674
			if (empty($toChange))
1675
			{
1676
				$toChange[] = '-1';
1677
			}
1678
1679
			$to_update[$row['id_pm']] = implode(',', array_unique($toChange));
1680
		}
1681
	);
1682
1683
	if (!empty($to_update))
1684
	{
1685
		return updatePMLabels($to_update, $user_id);
1686
	}
1687
}
1688
1689
/**
1690
 * Updates PMs with their new label.
1691
 *
1692
 * @param mixed[] $to_update
1693
 * @param int $user_id
1694
 * @return int
1695
 * @package PersonalMessage
1696
 */
1697
function updatePMLabels($to_update, $user_id)
1698
{
1699
	$db = database();
1700
1701
	$updateErrors = 0;
1702
1703
	foreach ($to_update as $id_pm => $set)
1704
	{
1705
		// Check that this string isn't going to be too large for the database.
1706
		if (strlen($set) > 60)
1707
		{
1708
			$updateErrors++;
1709
1710
			// Make the string as long as possible and update anyway
1711
			$set = substr($set, 0, 60);
1712
			$set = substr($set, 0, strrpos($set, ','));
1713
		}
1714
1715
		$db->query('', '
1716
			UPDATE {db_prefix}pm_recipients
1717
			SET 
1718
				labels = {string:labels}
1719
			WHERE id_pm = {int:id_pm}
1720
				AND id_member = {int:current_member}',
1721
			array(
1722
				'current_member' => $user_id,
1723
				'id_pm' => $id_pm,
1724
				'labels' => $set,
1725
			)
1726
		);
1727
	}
1728
1729
	return $updateErrors;
1730
}
1731
1732
/**
1733
 * Gets PMs older than a specific date.
1734
 *
1735
 * @param int $user_id the user's id.
1736
 * @param int $time timestamp with a specific date
1737
 * @return array
1738
 * @package PersonalMessage
1739
 */
1740
function getPMsOlderThan($user_id, $time)
1741
{
1742
	$db = database();
1743
1744
	// Array to store the IDs in.
1745
	$pm_ids = array();
1746
1747
	// Select all the messages they have sent older than $time.
1748
	$db->fetchQuery('
1749
		SELECT
1750
			id_pm
1751
		FROM {db_prefix}personal_messages
1752
		WHERE deleted_by_sender = {int:not_deleted}
1753
			AND id_member_from = {int:current_member}
1754
			AND msgtime < {int:msgtime}',
1755
		array(
1756
			'current_member' => $user_id,
1757
			'not_deleted' => 0,
1758
			'msgtime' => $time,
1759
		)
1760
	)->fetch_callback(
1761
		function ($row) use (&$pm_ids) {
1762
			$pm_ids[] = $row['id_pm'];
1763
		}
1764
	);
1765
1766
	// This is the inbox
1767
	$db->fetchQuery('
1768
		SELECT
1769
			pmr.id_pm
1770
		FROM {db_prefix}pm_recipients AS pmr
1771
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1772
		WHERE pmr.deleted = {int:not_deleted}
1773
			AND pmr.id_member = {int:current_member}
1774
			AND pm.msgtime < {int:msgtime}',
1775
		array(
1776
			'current_member' => $user_id,
1777
			'not_deleted' => 0,
1778
			'msgtime' => $time,
1779
		)
1780
	)->fetch_callback(
1781
		function ($row) use (&$pm_ids) {
1782
			$pm_ids[] = $row['id_pm'];
1783
		}
1784
	);
1785
1786
	return $pm_ids;
1787
}
1788
1789
/**
1790
 * Used to delete PM rules from the given member.
1791
 *
1792
 * @param int $id_member
1793
 * @param int[] $rule_changes
1794
 * @package PersonalMessage
1795
 */
1796
function deletePMRules($id_member, $rule_changes)
1797
{
1798
	$db = database();
1799
1800
	$db->query('', '
1801
		DELETE FROM {db_prefix}pm_rules
1802
		WHERE id_rule IN ({array_int:rule_list})
1803
		AND id_member = {int:current_member}',
1804
		array(
1805
			'current_member' => $id_member,
1806
			'rule_list' => $rule_changes,
1807
		)
1808
	);
1809
}
1810
1811
/**
1812
 * Updates a personal messaging rule action for the given member.
1813
 *
1814
 * @param int $id_rule
1815
 * @param int $id_member
1816
 * @param mixed[] $actions
1817
 * @package PersonalMessage
1818
 */
1819
function updatePMRuleAction($id_rule, $id_member, $actions)
1820
{
1821
	$db = database();
1822
1823
	$db->query('', '
1824
		UPDATE {db_prefix}pm_rules
1825
		SET 
1826
			actions = {string:actions}
1827
		WHERE id_rule = {int:id_rule}
1828
			AND id_member = {int:current_member}',
1829
		array(
1830
			'current_member' => $id_member,
1831
			'id_rule' => $id_rule,
1832
			'actions' => serialize($actions),
1833
		)
1834
	);
1835
}
1836
1837
/**
1838
 * Add a new PM rule to the database.
1839
 *
1840
 * @param int $id_member
1841
 * @param string $ruleName
1842
 * @param string $criteria
1843
 * @param string $actions
1844
 * @param int $doDelete
1845
 * @param int $isOr
1846
 * @package PersonalMessage
1847
 */
1848
function addPMRule($id_member, $ruleName, $criteria, $actions, $doDelete, $isOr)
1849
{
1850
	$db = database();
1851
1852
	$db->insert('',
1853
		'{db_prefix}pm_rules',
1854
		array(
1855
			'id_member' => 'int', 'rule_name' => 'string', 'criteria' => 'string', 'actions' => 'string',
1856
			'delete_pm' => 'int', 'is_or' => 'int',
1857
		),
1858
		array(
1859
			$id_member, $ruleName, $criteria, $actions, $doDelete, $isOr,
1860
		),
1861
		array('id_rule')
1862
	);
1863
}
1864
1865
/**
1866
 * Updates a personal messaging rule for the given member.
1867
 *
1868
 * @param int $id_member
1869
 * @param int $id_rule
1870
 * @param string $ruleName
1871
 * @param string $criteria
1872
 * @param string $actions
1873
 * @param int $doDelete
1874
 * @param int $isOr
1875
 * @package PersonalMessage
1876
 */
1877
function updatePMRule($id_member, $id_rule, $ruleName, $criteria, $actions, $doDelete, $isOr)
1878
{
1879
	$db = database();
1880
1881
	$db->query('', '
1882
		UPDATE {db_prefix}pm_rules
1883
		SET 
1884
			rule_name = {string:rule_name}, criteria = {string:criteria}, actions = {string:actions},
1885
			delete_pm = {int:delete_pm}, is_or = {int:is_or}
1886
		WHERE id_rule = {int:id_rule}
1887
			AND id_member = {int:current_member}',
1888
		array(
1889
			'current_member' => $id_member,
1890
			'delete_pm' => $doDelete,
1891
			'is_or' => $isOr,
1892
			'id_rule' => $id_rule,
1893
			'rule_name' => $ruleName,
1894
			'criteria' => $criteria,
1895
			'actions' => $actions,
1896
		)
1897
	);
1898
}
1899
1900
/**
1901
 * Used to set a replied status for a given PM.
1902
 *
1903
 * @param int $id_member
1904
 * @param int $replied_to
1905
 * @package PersonalMessage
1906
 */
1907
function setPMRepliedStatus($id_member, $replied_to)
1908
{
1909
	$db = database();
1910
1911
	$db->query('', '
1912
		UPDATE {db_prefix}pm_recipients
1913
		SET 
1914
			is_read = is_read | 2
1915
		WHERE id_pm = {int:replied_to}
1916
			AND id_member = {int:current_member}',
1917
		array(
1918
			'current_member' => $id_member,
1919
			'replied_to' => $replied_to,
1920
		)
1921
	);
1922
}
1923
1924
/**
1925
 * Given the head PM, loads all other PM's that share the same head node
1926
 *
1927
 * - Used to load the conversation view of a PM
1928
 *
1929
 * @param int $head id of the head pm of the conversation
1930
 * @param mixed[] $recipients
1931
 * @param string $folder the current folder we are working in
1932
 *
1933
 * @return array
1934
 * @package PersonalMessage
1935
 */
1936
function loadConversationList($head, &$recipients, $folder = '')
1937
{
1938
	$db = database();
1939
1940
	$display_pms = array();
1941
	$posters = array();
1942
	$db->fetchQuery('
1943
		SELECT
1944
			pm.id_pm, pm.id_member_from, pm.deleted_by_sender, pmr.id_member, pmr.deleted
1945
		FROM {db_prefix}personal_messages AS pm
1946
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
1947
		WHERE pm.id_pm_head = {int:id_pm_head}
1948
			AND ((pm.id_member_from = {int:current_member} AND pm.deleted_by_sender = {int:not_deleted})
1949
				OR (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted}))
1950
		ORDER BY pm.id_pm',
1951
		array(
1952
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1953
			'id_pm_head' => $head,
1954
			'not_deleted' => 0,
1955
		)
1956
	)->fetch_callback(
1957
		function ($row) use ($folder, &$recipients, &$display_pms, &$posters) {
1958
			// This is, frankly, a joke.
1959
			// We will put in a workaround for people sending to themselves - yawn!
1960
			if ($folder === 'sent' && $row['id_member_from'] == User::$info->id && $row['deleted_by_sender'] == 1)
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1961
			{
1962
				return;
1963
			}
1964
			elseif (($row['id_member'] == User::$info->id) && $row['deleted'] == 1)
1965
			{
1966
				return;
1967
			}
1968
1969
			if (!isset($recipients[$row['id_pm']]))
1970
			{
1971
				$recipients[$row['id_pm']] = array(
1972
					'to' => array(),
1973
					'bcc' => array()
1974
				);
1975
			}
1976
1977
			$display_pms[] = $row['id_pm'];
1978
			$posters[$row['id_pm']] = $row['id_member_from'];
1979
		}
1980
	);
1981
1982
	return array($display_pms, $posters);
1983
}
1984
1985
/**
1986
 * Used to determine if any message in a conversation thread is unread
1987
 *
1988
 * - Returns array of keys with the head id and value details of the newest
1989
 * unread message.
1990
 *
1991
 * @param int[] $pms array of pm ids to search
1992
 *
1993
 * @return array
1994
 * @package PersonalMessage
1995
 */
1996
function loadConversationUnreadStatus($pms)
1997
{
1998
	$db = database();
1999
2000
	// Make it an array if its not
2001
	if (!is_array($pms))
0 ignored issues
show
The condition is_array($pms) is always true.
Loading history...
2002
	{
2003
		$pms = array($pms);
2004
	}
2005
2006
	// Find the heads for this group of PM's
2007
	$head_pms = array();
2008
	$db->fetchQuery('
2009
		SELECT
2010
			id_pm_head, id_pm
2011
		FROM {db_prefix}personal_messages
2012
		WHERE id_pm IN ({array_int:id_pm})',
2013
		array(
2014
			'id_pm' => $pms,
2015
		)
2016
	)->fetch_callback(
2017
		function ($row) use (&$head_pms) {
2018
			$head_pms[$row['id_pm_head']] = $row['id_pm'];
2019
		}
2020
	);
2021
2022
	// Find any unread PM's this member has under these head pm id's
2023
	$unread_pms = array();
2024
	$db->fetchQuery('
2025
		SELECT
2026
			MAX(pm.id_pm) AS id_pm, pm.id_pm_head
2027
		FROM {db_prefix}personal_messages AS pm
2028
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2029
		WHERE pm.id_pm_head IN ({array_int:id_pm_head})
2030
			AND (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted})
2031
			AND (pmr.is_read & 1 = 0)
2032
		GROUP BY pm.id_pm_head',
2033
		array(
2034
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2035
			'id_pm_head' => array_keys($head_pms),
2036
			'not_deleted' => 0,
2037
		)
2038
	)->fetch_callback(
2039
		function ($row) use ($head_pms, &$unread_pms) {
2040
			// Return the results under the original index since that's what we are
2041
			// displaying in the subject list
2042
			$index = $head_pms[$row['id_pm_head']];
2043
			$unread_pms[$index] = $row;
2044
		}
2045
	);
2046
2047
	return $unread_pms;
2048
}
2049
2050
/**
2051
 * Get all recipients for a given group of PM's, loads some basic member information for each
2052
 *
2053
 * - Will not include bcc-recipients for an inbox
2054
 * - Keeps track if a message has been replied / read
2055
 * - Tracks any message labels in use
2056
 * - If optional search parameter is set to true will return message first label, useful for linking
2057
 *
2058
 * @param int[] $all_pms
2059
 * @param mixed[] $recipients
2060
 * @param string $folder
2061
 * @param bool $search
2062
 *
2063
 * @return array
2064
 * @package PersonalMessage
2065
 *
2066
 */
2067
function loadPMRecipientInfo($all_pms, &$recipients, $folder = '', $search = false)
2068
{
2069
	global $txt, $scripturl, $context;
2070
2071
	$db = database();
2072
2073
	$message_labels = array();
2074
	foreach ($all_pms as $pmid)
2075
	{
2076
		$message_labels[$pmid] = array();
2077
	}
2078
	$message_replied = array();
2079
	$message_unread = array();
2080
	$message_first_label = array();
2081
2082
	// Get the recipients for all these PM's
2083
	$request = $db->fetchQuery('
2084
		SELECT
2085
			pmr.id_pm, pmr.bcc, pmr.labels, pmr.is_read,
2086
			mem_to.id_member AS id_member_to, mem_to.real_name AS to_name
2087
		FROM {db_prefix}pm_recipients AS pmr
2088
			LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
2089
		WHERE pmr.id_pm IN ({array_int:pm_list})',
2090
		array(
2091
			'pm_list' => $all_pms,
2092
		)
2093
	);
2094
	while (($row = $request->fetch_assoc()))
2095
	{
2096
		// Sent folder recipients
2097
		if ($folder === 'sent' || empty($row['bcc']))
2098
		{
2099
			$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>';
2100
		}
2101
2102
		// Don't include bcc-recipients if its your inbox, you're not supposed to know :P
2103
		if ($row['id_member_to'] == User::$info->id && $folder !== 'sent')
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2104
		{
2105
			// Read and replied to status for this message
2106
			$message_replied[$row['id_pm']] = $row['is_read'] & 2;
2107
			$message_unread[$row['id_pm']] = $row['is_read'] == 0;
2108
			$message_labels[$row['id_pm']] = array();
2109
2110
			$row['labels'] = $row['labels'] === '' ? array() : explode(',', $row['labels']);
2111
			foreach ($row['labels'] as $v)
2112
			{
2113
				if (isset($context['labels'][(int) $v]))
2114
				{
2115
					$message_labels[$row['id_pm']][(int) $v] = array('id' => $v, 'name' => $context['labels'][(int) $v]['name']);
2116
				}
2117
2118
				// Here we find the first label on a message - used for linking to posts
2119
				if ($search && (!isset($message_first_label[$row['id_pm']]) && !in_array('-1', $row['labels'])))
2120
				{
2121
					$message_first_label[$row['id_pm']] = (int) $v;
2122
				}
2123
			}
2124
		}
2125
	}
2126
	$request->free_result();
2127
2128
	return array($message_labels, $message_replied, $message_unread, ($search ? $message_first_label : ''));
2129
}
2130
2131
/**
2132
 * This is used by preparePMContext_callback.
2133
 *
2134
 * - That function uses these query results and handles the free_result action as well.
2135
 *
2136
 * @param int[] $pms array of PM ids to fetch
2137
 * @param string[] $orderBy raw query defining how to order the results
2138
 * @return bool|\ElkArte\Database\AbstractResult
2139
 * @package PersonalMessage
2140
 */
2141
function loadPMSubjectRequest($pms, $orderBy)
2142
{
2143
	$db = database();
2144
2145
	// Separate query for these bits!
2146
	return $db->query('', '
2147
		SELECT
2148
			pm.id_pm, pm.subject, pm.id_member_from, pm.msgtime, COALESCE(mem.real_name, pm.from_name) AS from_name,
2149
			COALESCE(mem.id_member, 0) AS not_guest,
2150
			{string:empty} as body, {int:smileys_enabled} as smileys_enabled
2151
		FROM {db_prefix}personal_messages AS pm
2152
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
2153
		WHERE pm.id_pm IN ({array_int:pm_list})
2154
		ORDER BY ' . implode(', ', $orderBy) . '
2155
		LIMIT ' . count($pms),
2156
		array(
2157
			'pm_list' => $pms,
2158
			'empty' => '',
2159
			'smileys_enabled' => 1,
2160
			'from_time' => 0,
2161
		)
2162
	);
2163
}
2164
2165
/**
2166
 * Similar to loadSubjectRequest, this is used by preparePMContext_callback.
2167
 *
2168
 * - That function uses these query results and handles the free_result action as well.
2169
 *
2170
 * @param int[] $display_pms list of PM's to fetch
2171
 * @param string $sort_by_query raw query used in the sorting option
2172
 * @param string $sort_by used to signal when addition joins are needed
2173
 * @param bool $descending if true descending order of display
2174
 * @param int|string $display_mode how are they being viewed, all, conversation, etc
2175
 * @param string $folder current pm folder
2176
 * @return bool|\ElkArte\Database\AbstractResult
2177
 * @package PersonalMessage
2178
 */
2179
function loadPMMessageRequest($display_pms, $sort_by_query, $sort_by, $descending, $display_mode = '', $folder = '')
2180
{
2181
	$db = database();
2182
2183
	return $db->query('', '
2184
		SELECT
2185
			pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name,
2186
			{int:smileys_enabled} as smileys_enabled
2187
		FROM {db_prefix}personal_messages AS pm' . ($folder === 'sent' ? '
2188
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') . ($sort_by === 'name' ? '
2189
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})' : '') . '
2190
		WHERE pm.id_pm IN ({array_int:display_pms})' . ($folder === 'sent' ? '
2191
		GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' : '') . '
2192
		ORDER BY ' . ($display_mode == 2 ? 'pm.id_pm' : $sort_by_query) . ($descending ? ' DESC' : ' ASC') . '
2193
		LIMIT ' . count($display_pms),
2194
		array(
2195
			'display_pms' => $display_pms,
2196
			'id_member' => $folder === 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
2197
			'smileys_enabled' => 1,
2198
		)
2199
	);
2200
}
2201
2202
/**
2203
 * Simple function to validate that a PM was sent to the current user
2204
 *
2205
 * @param int $pmsg id of the pm we are checking
2206
 *
2207
 * @return bool
2208
 * @package PersonalMessage
2209
 */
2210
function checkPMReceived($pmsg)
2211
{
2212
	$db = database();
2213
2214
	$request = $db->fetchQuery('
2215
		SELECT
2216
			id_pm
2217
		FROM {db_prefix}pm_recipients
2218
		WHERE id_pm = {int:id_pm}
2219
			AND id_member = {int:current_member}
2220
		LIMIT 1',
2221
		array(
2222
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2223
			'id_pm' => $pmsg,
2224
		)
2225
	);
2226
	$isReceived = $request->num_rows() !== 0;
2227
	$request->free_result();
2228
2229
	return $isReceived;
2230
}
2231
2232
/**
2233
 * Loads a pm by ID for use as a quoted pm in a new message
2234
 *
2235
 * @param int $pmsg
2236
 * @param bool $isReceived
2237
 *
2238
 * @return array
2239
 * @package PersonalMessage
2240
 */
2241
function loadPMQuote($pmsg, $isReceived)
2242
{
2243
	$db = database();
2244
2245
	// Get the quoted message (and make sure you're allowed to see this quote!).
2246
	$request = $db->fetchQuery('
2247
		SELECT
2248
			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,
2249
			pm.body, pm.subject, pm.msgtime,
2250
			mem.member_name, COALESCE(mem.id_member, 0) AS id_member, COALESCE(mem.real_name, pm.from_name) AS real_name
2251
		FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : '
2252
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . '
2253
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
2254
		WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? '
2255
			AND pm.id_member_from = {int:current_member}' : '
2256
			AND pmr.id_member = {int:current_member}') . '
2257
		LIMIT 1',
2258
		array(
2259
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2260
			'id_pm_head_empty' => 0,
2261
			'id_pm' => $pmsg,
2262
		)
2263
	);
2264
	$row_quoted = $request->fetch_assoc();
2265
	$request->free_result();
2266
2267
	return empty($row_quoted) ? false : $row_quoted;
0 ignored issues
show
Bug Best Practice introduced by
The expression return empty($row_quoted) ? false : $row_quoted could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2268
}
2269
2270
/**
2271
 * For a given PM ID, loads all "other" recipients, (excludes the current member)
2272
 *
2273
 * - Will optionally count the number of bcc recipients and return that count
2274
 *
2275
 * @param int $pmsg
2276
 * @param bool $bcc_count
2277
 *
2278
 * @return array
2279
 * @package PersonalMessage
2280
 */
2281
function loadPMRecipientsAll($pmsg, $bcc_count = false)
2282
{
2283
	global $scripturl, $txt;
2284
2285
	$db = database();
2286
2287
	$recipients = array();
2288
	$hidden_recipients = 0;
2289
	$db->fetchQuery('
2290
		SELECT
2291
			mem.id_member, mem.real_name, pmr.bcc
2292
		FROM {db_prefix}pm_recipients AS pmr
2293
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
2294
		WHERE pmr.id_pm = {int:id_pm}
2295
			AND pmr.id_member != {int:current_member}' . ($bcc_count === true ? '' : '
2296
			AND pmr.bcc = {int:not_bcc}'),
2297
		array(
2298
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2299
			'id_pm' => $pmsg,
2300
			'not_bcc' => 0,
2301
		)
2302
	)->fetch_callback(
2303
		function ($row) use (&$recipients, &$hidden_recipients, $bcc_count, $scripturl) {
2304
			// If it's hidden we still don't reveal their names
2305
			if ($bcc_count && $row['bcc'])
2306
			{
2307
				$hidden_recipients++;
2308
			}
2309
2310
			$recipients[] = array(
2311
				'id' => $row['id_member'],
2312
				'name' => htmlspecialchars($row['real_name'], ENT_COMPAT, 'UTF-8'),
2313
				'link' => '[url=' . $scripturl . '?action=profile;u=' . $row['id_member'] . ']' . $row['real_name'] . '[/url]',
2314
			);
2315
		}
2316
	);
2317
2318
	// If bcc count was requested, we return the number of bcc members, but not the names
2319
	if ($bcc_count)
2320
	{
2321
		$recipients[] = array(
2322
			'id' => 'bcc',
2323
			'name' => sprintf($txt['pm_report_pm_hidden'], $hidden_recipients),
2324
			'link' => sprintf($txt['pm_report_pm_hidden'], $hidden_recipients)
2325
		);
2326
	}
2327
2328
	return $recipients;
2329
}
2330
2331
/**
2332
 * Simply loads a personal message by ID
2333
 *
2334
 * - Supplied ID must have been sent to the user id requesting it and it must not have been deleted
2335
 *
2336
 * @param int $pm_id
2337
 *
2338
 * @return array
2339
 * @throws \ElkArte\Exceptions\Exception no_access
2340
 * @package PersonalMessage
2341
 *
2342
 */
2343
function loadPersonalMessage($pm_id)
2344
{
2345
	$db = database();
2346
2347
	// First, pull out the message contents, and verify it actually went to them!
2348
	$request = $db->fetchQuery('
2349
		SELECT
2350
			pm.subject, pm.body, pm.msgtime, pm.id_member_from,
2351
			COALESCE(m.real_name, pm.from_name) AS sender_name,
2352
			pm.from_name AS poster_name, msgtime
2353
		FROM {db_prefix}personal_messages AS pm
2354
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2355
			LEFT JOIN {db_prefix}members AS m ON (m.id_member = pm.id_member_from)
2356
		WHERE pm.id_pm = {int:id_pm}
2357
			AND pmr.id_member = {int:current_member}
2358
			AND pmr.deleted = {int:not_deleted}
2359
		LIMIT 1',
2360
		array(
2361
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2362
			'id_pm' => $pm_id,
2363
			'not_deleted' => 0,
2364
		)
2365
	);
2366
	// Can only be a hacker here!
2367
	if ($request->num_rows() === 0)
2368
	{
2369
		throw new \ElkArte\Exceptions\Exception('no_access', false);
2370
	}
2371
	$pm_details = $request->fetch_row();
2372
	$request->free_result();
2373
2374
	return $pm_details;
2375
}
2376
2377
/**
2378
 * Finds the number of results that a search would produce
2379
 *
2380
 * @param string $userQuery raw query, used if we are searching for specific users
2381
 * @param string $labelQuery raw query, used if we are searching only specific labels
2382
 * @param string $timeQuery raw query, used if we are limiting results to time periods
2383
 * @param string $searchQuery raw query, the actual thing you are searching for in the subject and/or body
2384
 * @param mixed[] $searchq_parameters value parameters used in the above query
2385
 * @return int
2386
 * @package PersonalMessage
2387
 */
2388
function numPMSeachResults($userQuery, $labelQuery, $timeQuery, $searchQuery, $searchq_parameters)
2389
{
2390
	global $context;
2391
2392
	$db = database();
2393
2394
	// Get the amount of results.
2395
	$request = $db->fetchQuery('
2396
		SELECT
2397
			COUNT(*)
2398
		FROM {db_prefix}pm_recipients AS pmr
2399
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
2400
		WHERE ' . ($context['folder'] === 'inbox' ? '
2401
			pmr.id_member = {int:current_member}
2402
			AND pmr.deleted = {int:not_deleted}' : '
2403
			pm.id_member_from = {int:current_member}
2404
			AND pm.deleted_by_sender = {int:not_deleted}') . '
2405
			' . $userQuery . $labelQuery . $timeQuery . '
2406
			AND (' . $searchQuery . ')',
2407
		array_merge($searchq_parameters, array(
2408
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2409
			'not_deleted' => 0,
2410
		))
2411
	);
2412
	list ($numResults) = $request->fetch_row();
2413
	$request->free_result();
2414
2415
	return $numResults;
2416
}
2417
2418
/**
2419
 * Gets all the matching message ids, senders and head pm nodes, using standard search only (No caching and the like!)
2420
 *
2421
 * @param string $userQuery raw query, used if we are searching for specific users
2422
 * @param string $labelQuery raw query, used if we are searching only specific labels
2423
 * @param string $timeQuery raw query, used if we are limiting results to time periods
2424
 * @param string $searchQuery raw query, the actual thing you are searching for in the subject and/or body
2425
 * @param mixed[] $searchq_parameters value parameters used in the above query
2426
 * @param mixed[] $search_params additional search parameters, like sort and direction
2427
 *
2428
 * @return array
2429
 * @package PersonalMessage
2430
 */
2431
function loadPMSearchMessages($userQuery, $labelQuery, $timeQuery, $searchQuery, $searchq_parameters, $search_params)
2432
{
2433
	global $context, $modSettings;
2434
2435
	$db = database();
2436
2437
	$foundMessages = array();
2438
	$posters = array();
2439
	$head_pms = array();
2440
	$db->fetchQuery('
2441
		SELECT
2442
			pm.id_pm, pm.id_pm_head, pm.id_member_from
2443
		FROM {db_prefix}pm_recipients AS pmr
2444
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
2445
		WHERE ' . ($context['folder'] === 'inbox' ? '
2446
			pmr.id_member = {int:current_member}
2447
			AND pmr.deleted = {int:not_deleted}' : '
2448
			pm.id_member_from = {int:current_member}
2449
			AND pm.deleted_by_sender = {int:not_deleted}') . '
2450
			' . $userQuery . $labelQuery . $timeQuery . '
2451
			AND (' . $searchQuery . ')
2452
		ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . '
2453
		LIMIT ' . $context['start'] . ', ' . $modSettings['search_results_per_page'],
2454
		array_merge($searchq_parameters, array(
2455
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2456
			'not_deleted' => 0,
2457
		))
2458
	)->fetch_callback(
2459
		function ($row) use (&$foundMessages, &$posters, &$head_pms)
2460
		{
2461
			$foundMessages[] = $row['id_pm'];
2462
			$posters[] = $row['id_member_from'];
2463
			$head_pms[$row['id_pm']] = $row['id_pm_head'];
2464
		}
2465
	);
2466
2467
	return array($foundMessages, $posters, $head_pms);
2468
}
2469
2470
/**
2471
 * When we are in conversation view, we need to find the base head pm of the
2472
 * conversation.  This will set the root head id to each of the node heads
2473
 *
2474
 * @param int[] $head_pms array of pm ids that were found in the id_pm_head col
2475
 * during the initial search
2476
 *
2477
 * @return array
2478
 * @package PersonalMessage
2479
 */
2480
function loadPMSearchHeads($head_pms)
2481
{
2482
	$db = database();
2483
2484
	$real_pm_ids = array();
2485
	$db->fetchQuery('
2486
		SELECT
2487
			MAX(pm.id_pm) AS id_pm, pm.id_pm_head
2488
		FROM {db_prefix}personal_messages AS pm
2489
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2490
		WHERE pm.id_pm_head IN ({array_int:head_pms})
2491
			AND pmr.id_member = {int:current_member}
2492
			AND pmr.deleted = {int:not_deleted}
2493
		GROUP BY pm.id_pm_head
2494
		LIMIT {int:limit}',
2495
		array(
2496
			'head_pms' => array_unique($head_pms),
2497
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2498
			'not_deleted' => 0,
2499
			'limit' => count($head_pms),
2500
		)
2501
	)->fetch_callback(
2502
		function ($row) use (&$real_pm_ids)
2503
		{
2504
			$real_pm_ids[$row['id_pm_head']] = $row['id_pm'];
2505
		}
2506
	);
2507
2508
	return $real_pm_ids;
2509
}
2510
2511
/**
2512
 * Loads the actual details of the PM's that were found during the search stage
2513
 *
2514
 * @param int[] $foundMessages array of found message id's
2515
 * @param mixed[] $search_params as specified in the form, here used for sorting
2516
 *
2517
 * @return array
2518
 * @package PersonalMessage
2519
 */
2520
function loadPMSearchResults($foundMessages, $search_params)
2521
{
2522
	$db = database();
2523
2524
	// Prepare the query for the callback
2525
	$search_results = array();
2526
	$db->fetchQuery('
2527
		SELECT
2528
			pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
2529
		FROM {db_prefix}personal_messages AS pm
2530
		WHERE pm.id_pm IN ({array_int:message_list})
2531
		ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . '
2532
		LIMIT ' . count($foundMessages),
2533
		array(
2534
			'message_list' => $foundMessages,
2535
		)
2536
	)->fetch_callback(
2537
		function ($row) use (&$search_results) {
2538
			$search_results[] = $row;
2539
		}
2540
	);
2541
2542
	return $search_results;
2543
}
2544