Issues (1061)

Sources/PersonalMessage.php (19 issues)

1
<?php
2
3
/**
4
 * This file is mainly meant for controlling the actions related to personal
5
 * messages. It allows viewing, sending, deleting, and marking personal
6
 * messages. For compatibility reasons, they are often called "instant messages".
7
 *
8
 * Simple Machines Forum (SMF)
9
 *
10
 * @package SMF
11
 * @author Simple Machines https://www.simplemachines.org
12
 * @copyright 2020 Simple Machines and individual contributors
13
 * @license https://www.simplemachines.org/about/smf/license.php BSD
14
 *
15
 * @version 2.1 RC2
16
 */
17
18
if (!defined('SMF'))
19
	die('No direct access...');
20
21
/**
22
 * This helps organize things...
23
 *
24
 * @todo this should be a simple dispatcher....
25
 */
26
function MessageMain()
27
{
28
	global $txt, $scripturl, $sourcedir, $context, $user_info, $user_settings, $smcFunc, $modSettings;
29
30
	// No guests!
31
	is_not_guest();
32
33
	// You're not supposed to be here at all, if you can't even read PMs.
34
	isAllowedTo('pm_read');
35
36
	// This file contains the basic functions for sending a PM.
37
	require_once($sourcedir . '/Subs-Post.php');
38
39
	loadLanguage('PersonalMessage+Drafts');
40
41
	if (!isset($_REQUEST['xml']))
42
		loadTemplate('PersonalMessage');
43
44
	// Load up the members maximum message capacity.
45
	if ($user_info['is_admin'])
46
		$context['message_limit'] = 0;
47
	elseif (($context['message_limit'] = cache_get_data('msgLimit:' . $user_info['id'], 360)) === null)
48
	{
49
		// @todo Why do we do this?  It seems like if they have any limit we should use it.
50
		$request = $smcFunc['db_query']('', '
51
			SELECT MAX(max_messages) AS top_limit, MIN(max_messages) AS bottom_limit
52
			FROM {db_prefix}membergroups
53
			WHERE id_group IN ({array_int:users_groups})',
54
			array(
55
				'users_groups' => $user_info['groups'],
56
			)
57
		);
58
		list ($maxMessage, $minMessage) = $smcFunc['db_fetch_row']($request);
59
		$smcFunc['db_free_result']($request);
60
61
		$context['message_limit'] = $minMessage == 0 ? 0 : $maxMessage;
62
63
		// Save us doing it again!
64
		cache_put_data('msgLimit:' . $user_info['id'], $context['message_limit'], 360);
65
	}
66
67
	// Prepare the context for the capacity bar.
68
	if (!empty($context['message_limit']))
69
	{
70
		$bar = ($user_info['messages'] * 100) / $context['message_limit'];
71
72
		$context['limit_bar'] = array(
73
			'messages' => $user_info['messages'],
74
			'allowed' => $context['message_limit'],
75
			'percent' => $bar,
76
			'bar' => min(100, (int) $bar),
77
			'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], round($bar, 1)),
78
		);
79
	}
80
81
	// a previous message was sent successfully? show a small indication.
82
	if (isset($_GET['done']) && ($_GET['done'] == 'sent'))
83
		$context['pm_sent'] = true;
84
85
	$context['labels'] = array();
86
87
	// Load the label data.
88
	if ($user_settings['new_pm'] || ($context['labels'] = cache_get_data('labelCounts:' . $user_info['id'], 720)) === null)
89
	{
90
		// Looks like we need to reseek!
91
92
		// Inbox "label"
93
		$context['labels'][-1] = array(
94
			'id' => -1,
95
			'name' => $txt['pm_msg_label_inbox'],
96
			'messages' => 0,
97
			'unread_messages' => 0,
98
		);
99
100
		// First get the inbox counts
101
		// The CASE WHEN here is because is_read is set to 3 when you reply to a message
102
		$result = $smcFunc['db_query']('', '
103
			SELECT COUNT(*) AS total, SUM(is_read & 1) AS num_read
104
			FROM {db_prefix}pm_recipients
105
			WHERE id_member = {int:current_member}
106
				AND in_inbox = {int:in_inbox}
107
				AND deleted = {int:not_deleted}',
108
			array(
109
				'current_member' => $user_info['id'],
110
				'in_inbox' => 1,
111
				'not_deleted' => 0,
112
			)
113
		);
114
115
		while ($row = $smcFunc['db_fetch_assoc']($result))
116
		{
117
			$context['labels'][-1]['messages'] = $row['total'];
118
			$context['labels'][-1]['unread_messages'] = $row['total'] - $row['num_read'];
119
		}
120
121
		$smcFunc['db_free_result']($result);
122
123
		// Now load info about all the other labels
124
		$result = $smcFunc['db_query']('', '
125
			SELECT l.id_label, l.name, COALESCE(SUM(pr.is_read & 1), 0) AS num_read, COALESCE(COUNT(pr.id_pm), 0) AS total
126
			FROM {db_prefix}pm_labels AS l
127
				LEFT JOIN {db_prefix}pm_labeled_messages AS pl ON (pl.id_label = l.id_label)
128
				LEFT JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pl.id_pm)
129
			WHERE l.id_member = {int:current_member}
130
			GROUP BY l.id_label, l.name',
131
			array(
132
				'current_member' => $user_info['id'],
133
			)
134
		);
135
136
		while ($row = $smcFunc['db_fetch_assoc']($result))
137
		{
138
			$context['labels'][$row['id_label']] = array(
139
				'id' => $row['id_label'],
140
				'name' => $row['name'],
141
				'messages' => $row['total'],
142
				'unread_messages' => $row['total'] - $row['num_read']
143
			);
144
		}
145
146
		$smcFunc['db_free_result']($result);
147
148
		// Store it please!
149
		cache_put_data('labelCounts:' . $user_info['id'], $context['labels'], 720);
150
	}
151
152
	// Now we have the labels, and assuming we have unsorted mail, apply our rules!
153
	if ($user_settings['new_pm'])
154
	{
155
		ApplyRules();
156
		updateMemberData($user_info['id'], array('new_pm' => 0));
157
		$smcFunc['db_query']('', '
158
			UPDATE {db_prefix}pm_recipients
159
			SET is_new = {int:not_new}
160
			WHERE id_member = {int:current_member}',
161
			array(
162
				'current_member' => $user_info['id'],
163
				'not_new' => 0,
164
			)
165
		);
166
	}
167
168
	// This determines if we have more labels than just the standard inbox.
169
	$context['currently_using_labels'] = count($context['labels']) > 1 ? 1 : 0;
170
171
	// Some stuff for the labels...
172
	$context['current_label_id'] = isset($_REQUEST['l']) && isset($context['labels'][$_REQUEST['l']]) ? (int) $_REQUEST['l'] : -1;
173
	$context['current_label'] = &$context['labels'][$context['current_label_id']]['name'];
174
	$context['folder'] = !isset($_REQUEST['f']) || $_REQUEST['f'] != 'sent' ? 'inbox' : 'sent';
175
176
	// This is convenient.  Do you know how annoying it is to do this every time?!
177
	$context['current_label_redirect'] = 'action=pm;f=' . $context['folder'] . (isset($_GET['start']) ? ';start=' . $_GET['start'] : '') . (isset($_REQUEST['l']) ? ';l=' . $_REQUEST['l'] : '');
178
	$context['can_issue_warning'] = allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1;
179
180
	// Are PM drafts enabled?
181
	$context['drafts_pm_save'] = !empty($modSettings['drafts_pm_enabled']) && allowedTo('pm_draft');
182
	$context['drafts_autosave'] = !empty($context['drafts_pm_save']) && !empty($modSettings['drafts_autosave_enabled']);
183
184
	// Build the linktree for all the actions...
185
	$context['linktree'][] = array(
186
		'url' => $scripturl . '?action=pm',
187
		'name' => $txt['personal_messages']
188
	);
189
190
	// Preferences...
191
	$context['display_mode'] = $user_settings['pm_prefs'] & 3;
192
193
	$subActions = array(
194
		'popup' => 'MessagePopup',
195
		'manlabels' => 'ManageLabels',
196
		'manrules' => 'ManageRules',
197
		'pmactions' => 'MessageActionsApply',
198
		'prune' => 'MessagePrune',
199
		'removeall2' => 'MessageKillAll',
200
		'report' => 'ReportMessage',
201
		'search' => 'MessageSearch',
202
		'search2' => 'MessageSearch2',
203
		'send' => 'MessagePost',
204
		'send2' => 'MessagePost2',
205
		'settings' => 'MessageSettings',
206
		'showpmdrafts' => 'MessageDrafts',
207
	);
208
209
	if (!isset($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']]))
210
	{
211
		$_REQUEST['sa'] = '';
212
		MessageFolder();
213
	}
214
	else
215
	{
216
		if (!isset($_REQUEST['xml']) && $_REQUEST['sa'] != 'popup')
217
			messageIndexBar($_REQUEST['sa']);
218
219
		call_helper($subActions[$_REQUEST['sa']]);
220
	}
221
}
222
223
/**
224
 * A menu to easily access different areas of the PM section
225
 *
226
 * @param string $area The area we're currently in
227
 */
228
function messageIndexBar($area)
229
{
230
	global $txt, $context, $scripturl, $sourcedir, $modSettings, $user_info;
231
232
	$pm_areas = array(
233
		'folders' => array(
234
			'title' => $txt['pm_messages'],
235
			'areas' => array(
236
				'inbox' => array(
237
					'label' => $txt['inbox'],
238
					'custom_url' => $scripturl . '?action=pm',
239
					'amt' => 0,
240
				),
241
				'send' => array(
242
					'label' => $txt['new_message'],
243
					'custom_url' => $scripturl . '?action=pm;sa=send',
244
					'permission' => 'pm_send',
245
					'amt' => 0,
246
				),
247
				'sent' => array(
248
					'label' => $txt['sent_items'],
249
					'custom_url' => $scripturl . '?action=pm;f=sent',
250
					'amt' => 0,
251
				),
252
				'drafts' => array(
253
					'label' => $txt['drafts_show'],
254
					'custom_url' => $scripturl . '?action=pm;sa=showpmdrafts',
255
					'permission' => 'pm_draft',
256
					'enabled' => !empty($modSettings['drafts_pm_enabled']),
257
					'amt' => 0,
258
				),
259
			),
260
			'amt' => 0,
261
		),
262
		'labels' => array(
263
			'title' => $txt['pm_labels'],
264
			'areas' => array(),
265
			'amt' => 0,
266
		),
267
		'actions' => array(
268
			'title' => $txt['pm_actions'],
269
			'areas' => array(
270
				'search' => array(
271
					'label' => $txt['pm_search_bar_title'],
272
					'custom_url' => $scripturl . '?action=pm;sa=search',
273
				),
274
				'prune' => array(
275
					'label' => $txt['pm_prune'],
276
					'custom_url' => $scripturl . '?action=pm;sa=prune'
277
				),
278
			),
279
		),
280
		'pref' => array(
281
			'title' => $txt['pm_preferences'],
282
			'areas' => array(
283
				'manlabels' => array(
284
					'label' => $txt['pm_manage_labels'],
285
					'custom_url' => $scripturl . '?action=pm;sa=manlabels',
286
				),
287
				'manrules' => array(
288
					'label' => $txt['pm_manage_rules'],
289
					'custom_url' => $scripturl . '?action=pm;sa=manrules',
290
				),
291
				'settings' => array(
292
					'label' => $txt['pm_settings'],
293
					'custom_url' => $scripturl . '?action=pm;sa=settings',
294
				),
295
			),
296
		),
297
	);
298
299
	// Handle labels.
300
	if (empty($context['currently_using_labels']))
301
		unset($pm_areas['labels']);
302
	else
303
	{
304
		// Note we send labels by id as it will have less problems in the querystring.
305
		foreach ($context['labels'] as $label)
306
		{
307
			if ($label['id'] == -1)
308
				continue;
309
310
			// Count the amount of unread items in labels.
311
			$pm_areas['labels']['amt'] += $label['unread_messages'];
312
313
			// Add the label to the menu.
314
			$pm_areas['labels']['areas']['label' . $label['id']] = array(
315
				'label' => $label['name'],
316
				'custom_url' => $scripturl . '?action=pm;l=' . $label['id'],
317
				'amt' => $label['unread_messages'],
318
				'unread_messages' => $label['unread_messages'],
319
				'messages' => $label['messages'],
320
			);
321
		}
322
	}
323
324
	$pm_areas['folders']['areas']['inbox']['unread_messages'] = &$context['labels'][-1]['unread_messages'];
325
	$pm_areas['folders']['areas']['inbox']['messages'] = &$context['labels'][-1]['messages'];
326
	if (!empty($context['labels'][-1]['unread_messages']))
327
	{
328
		$pm_areas['folders']['areas']['inbox']['amt'] = $context['labels'][-1]['unread_messages'];
329
		$pm_areas['folders']['amt'] = $context['labels'][-1]['unread_messages'];
330
	}
331
332
	// Do we have a limit on the amount of messages we can keep?
333
	if (!empty($context['message_limit']))
334
	{
335
		$bar = round(($user_info['messages'] * 100) / $context['message_limit'], 1);
336
337
		$context['limit_bar'] = array(
338
			'messages' => $user_info['messages'],
339
			'allowed' => $context['message_limit'],
340
			'percent' => $bar,
341
			'bar' => $bar > 100 ? 100 : (int) $bar,
342
			'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], $bar)
343
		);
344
	}
345
346
	require_once($sourcedir . '/Subs-Menu.php');
347
348
	// Set a few options for the menu.
349
	$menuOptions = array(
350
		'current_area' => $area,
351
		'disable_url_session_check' => true,
352
	);
353
354
	// Actually create the menu!
355
	$pm_include_data = createMenu($pm_areas, $menuOptions);
356
	unset($pm_areas);
357
358
	// No menu means no access.
359
	if (!$pm_include_data && (!$user_info['is_guest'] || validateSession()))
0 ignored issues
show
The condition $pm_include_data is always false.
Loading history...
360
		fatal_lang_error('no_access', false);
361
362
	// Make a note of the Unique ID for this menu.
363
	$context['pm_menu_id'] = $context['max_menu_id'];
364
	$context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id'];
365
366
	// Set the selected item.
367
	$current_area = $pm_include_data['current_area'];
368
	$context['menu_item_selected'] = $current_area;
369
370
	// Set the template for this area and add the profile layer.
371
	if (!isset($_REQUEST['xml']))
372
		$context['template_layers'][] = 'pm';
373
}
374
375
/**
376
 * The popup for when we ask for the popup from the user.
377
 */
378
function MessagePopup()
379
{
380
	global $context, $modSettings, $smcFunc, $memberContext, $scripturl, $user_settings, $db_show_debug;
381
382
	// We do not want to output debug information here.
383
	$db_show_debug = false;
384
385
	// We only want to output our little layer here.
386
	$context['template_layers'] = array();
387
	$context['sub_template'] = 'pm_popup';
388
389
	$context['can_send_pm'] = allowedTo('pm_send');
390
	$context['can_draft'] = allowedTo('pm_draft') && !empty($modSettings['drafts_pm_enabled']);
391
392
	// So are we loading stuff?
393
	$request = $smcFunc['db_query']('', '
394
		SELECT id_pm
395
		FROM {db_prefix}pm_recipients AS pmr
396
		WHERE pmr.id_member = {int:current_member}
397
			AND is_read = {int:not_read}
398
		ORDER BY id_pm',
399
		array(
400
			'current_member' => $context['user']['id'],
401
			'not_read' => 0,
402
		)
403
	);
404
	$pms = array();
405
	while ($row = $smcFunc['db_fetch_row']($request))
406
		$pms[] = $row[0];
407
	$smcFunc['db_free_result']($request);
408
409
	if (!empty($pms))
410
	{
411
		// Just quickly, it's possible that the number of PMs can get out of sync.
412
		$count_unread = count($pms);
413
		if ($count_unread != $user_settings['unread_messages'])
414
		{
415
			updateMemberData($context['user']['id'], array('unread_messages' => $count_unread));
416
			$context['user']['unread_messages'] = count($pms);
417
		}
418
419
		// Now, actually fetch me some PMs. Make sure we track the senders, got some work to do for them.
420
		$senders = array();
421
422
		$request = $smcFunc['db_query']('', '
423
			SELECT pm.id_pm, pm.id_pm_head, COALESCE(mem.id_member, pm.id_member_from) AS id_member_from,
424
				COALESCE(mem.real_name, pm.from_name) AS member_from, pm.msgtime AS timestamp, pm.subject
425
			FROM {db_prefix}personal_messages AS pm
426
				LEFT JOIN {db_prefix}members AS mem ON (pm.id_member_from = mem.id_member)
427
			WHERE pm.id_pm IN ({array_int:id_pms})',
428
			array(
429
				'id_pms' => $pms,
430
			)
431
		);
432
		while ($row = $smcFunc['db_fetch_assoc']($request))
433
		{
434
			if (!empty($row['id_member_from']))
435
				$senders[] = $row['id_member_from'];
436
437
			$row['replied_to_you'] = $row['id_pm'] != $row['id_pm_head'];
438
			$row['time'] = timeformat($row['timestamp']);
439
			$row['pm_link'] = '<a href="' . $scripturl . '?action=pm;f=inbox;pmsg=' . $row['id_pm'] . '">' . $row['subject'] . '</a>';
440
			$context['unread_pms'][$row['id_pm']] = $row;
441
		}
442
		$smcFunc['db_free_result']($request);
443
444
		$senders = loadMemberData($senders);
445
		foreach ($senders as $member)
446
			loadMemberContext($member);
447
448
		// Having loaded everyone, attach them to the PMs.
449
		foreach ($context['unread_pms'] as $id_pm => $details)
450
			if (!empty($memberContext[$details['id_member_from']]))
451
				$context['unread_pms'][$id_pm]['member'] = &$memberContext[$details['id_member_from']];
452
	}
453
}
454
455
/**
456
 * A folder, ie. inbox/sent etc.
457
 */
458
function MessageFolder()
459
{
460
	global $txt, $scripturl, $modSettings, $context, $subjects_request;
461
	global $messages_request, $user_info, $recipients, $options, $smcFunc, $user_settings;
462
463
	// Changing view?
464
	if (isset($_GET['view']))
465
	{
466
		$context['display_mode'] = $context['display_mode'] > 1 ? 0 : $context['display_mode'] + 1;
467
		updateMemberData($user_info['id'], array('pm_prefs' => ($user_settings['pm_prefs'] & 252) | $context['display_mode']));
468
	}
469
470
	// Make sure the starting location is valid.
471
	if (isset($_GET['start']) && $_GET['start'] != 'new')
472
		$_GET['start'] = (int) $_GET['start'];
473
	elseif (!isset($_GET['start']) && !empty($options['view_newest_pm_first']))
474
		$_GET['start'] = 0;
475
	else
476
		$_GET['start'] = 'new';
477
478
	// Set up some basic theme stuff.
479
	$context['from_or_to'] = $context['folder'] != 'sent' ? 'from' : 'to';
480
	$context['get_pmessage'] = 'prepareMessageContext';
481
	$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
482
	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
483
484
	// Prevent signature images from going outside the box.
485
	if ($context['signature_enabled'])
486
	{
487
		list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
488
		$sig_limits = explode(',', $sig_limits);
489
490
		if (!empty($sig_limits[5]) || !empty($sig_limits[6]))
491
			addInlineCss('
492
	.signature img { ' . (!empty($sig_limits[5]) ? 'max-width: ' . (int) $sig_limits[5] . 'px; ' : '') . (!empty($sig_limits[6]) ? 'max-height: ' . (int) $sig_limits[6] . 'px; ' : '') . '}');
493
	}
494
495
	$labelJoin = '';
496
	$labelQuery = '';
497
	$labelQuery2 = '';
498
499
	// SMF logic: If you're viewing a label, it's still the inbox
500
	if ($context['folder'] == 'inbox' && $context['current_label_id'] == -1)
501
	{
502
		$labelQuery = '
503
			AND pmr.in_inbox = 1';
504
	}
505
	elseif ($context['folder'] != 'sent')
506
	{
507
		$labelJoin = '
508
			INNER JOIN {db_prefix}pm_labeled_messages AS pl ON (pl.id_pm = pmr.id_pm)';
509
510
		$labelQuery2 = '
511
			AND pl.id_label = ' . $context['current_label_id'];
512
	}
513
514
	// Set the index bar correct!
515
	messageIndexBar($context['current_label_id'] == -1 ? $context['folder'] : 'label' . $context['current_label_id']);
516
517
	// Sorting the folder.
518
	$sort_methods = array(
519
		'date' => 'pm.id_pm',
520
		'name' => 'COALESCE(mem.real_name, \'\')',
521
		'subject' => 'pm.subject',
522
	);
523
524
	// They didn't pick one, use the forum default.
525
	if (!isset($_GET['sort']) || !isset($sort_methods[$_GET['sort']]))
526
	{
527
		$context['sort_by'] = 'date';
528
		$_GET['sort'] = 'pm.id_pm';
529
		// An overriding setting?
530
		$descending = !empty($options['view_newest_pm_first']);
531
	}
532
	// Otherwise use the defaults: ascending, by date.
533
	else
534
	{
535
		$context['sort_by'] = $_GET['sort'];
536
		$_GET['sort'] = $sort_methods[$_GET['sort']];
537
		$descending = isset($_GET['desc']);
538
	}
539
540
	$context['sort_direction'] = $descending ? 'down' : 'up';
541
542
	// Set the text to resemble the current folder.
543
	$pmbox = $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'];
544
	$txt['delete_all'] = str_replace('PMBOX', $pmbox, $txt['delete_all']);
545
546
	// Now, build the link tree!
547
	if ($context['current_label_id'] == -1)
548
		$context['linktree'][] = array(
549
			'url' => $scripturl . '?action=pm;f=' . $context['folder'],
550
			'name' => $pmbox
551
		);
552
553
	// Build it further for a label.
554
	if ($context['current_label_id'] != -1)
555
		$context['linktree'][] = array(
556
			'url' => $scripturl . '?action=pm;f=' . $context['folder'] . ';l=' . $context['current_label_id'],
557
			'name' => $txt['pm_current_label'] . ': ' . $context['current_label']
558
		);
559
560
	// Figure out how many messages there are.
561
	if ($context['folder'] == 'sent')
562
		$request = $smcFunc['db_query']('', '
563
			SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
564
			FROM {db_prefix}personal_messages AS pm
565
			WHERE pm.id_member_from = {int:current_member}
566
				AND pm.deleted_by_sender = {int:not_deleted}',
567
			array(
568
				'current_member' => $user_info['id'],
569
				'not_deleted' => 0,
570
			)
571
		);
572
	else
573
		$request = $smcFunc['db_query']('', '
574
			SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
575
			FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
576
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . $labelJoin . '
577
			WHERE pmr.id_member = {int:current_member}
578
				AND pmr.deleted = {int:not_deleted}' . $labelQuery . $labelQuery2,
579
			array(
580
				'current_member' => $user_info['id'],
581
				'not_deleted' => 0,
582
			)
583
		);
584
	list ($max_messages) = $smcFunc['db_fetch_row']($request);
585
	$smcFunc['db_free_result']($request);
586
587
	// Only show the button if there are messages to delete.
588
	$context['show_delete'] = $max_messages > 0;
589
	$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
590
591
	// Start on the last page.
592
	if (!is_numeric($_GET['start']) || $_GET['start'] >= $max_messages)
593
		$_GET['start'] = ($max_messages - 1) - (($max_messages - 1) % $maxPerPage);
594
	elseif ($_GET['start'] < 0)
595
		$_GET['start'] = 0;
596
597
	// ... but wait - what if we want to start from a specific message?
598
	if (isset($_GET['pmid']))
599
	{
600
		$pmID = (int) $_GET['pmid'];
601
602
		// Make sure you have access to this PM.
603
		if (!isAccessiblePM($pmID, $context['folder'] == 'sent' ? 'outbox' : 'inbox'))
604
			fatal_lang_error('no_access', false);
605
606
		$context['current_pm'] = $pmID;
607
608
		// With only one page of PM's we're gonna want page 1.
609
		if ($max_messages <= $maxPerPage)
610
			$_GET['start'] = 0;
611
		// If we pass kstart we assume we're in the right place.
612
		elseif (!isset($_GET['kstart']))
613
		{
614
			if ($context['folder'] == 'sent')
615
				$request = $smcFunc['db_query']('', '
616
					SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
617
					FROM {db_prefix}personal_messages
618
					WHERE id_member_from = {int:current_member}
619
						AND deleted_by_sender = {int:not_deleted}
620
						AND id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}',
621
					array(
622
						'current_member' => $user_info['id'],
623
						'not_deleted' => 0,
624
						'id_pm' => $pmID,
625
					)
626
				);
627
			else
628
				$request = $smcFunc['db_query']('', '
629
					SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
630
					FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
631
						INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . $labelJoin . '
632
					WHERE pmr.id_member = {int:current_member}
633
						AND pmr.deleted = {int:not_deleted}' . $labelQuery . $labelQuery2 . '
634
						AND pmr.id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}',
635
					array(
636
						'current_member' => $user_info['id'],
637
						'not_deleted' => 0,
638
						'id_pm' => $pmID,
639
					)
640
				);
641
642
			list ($_GET['start']) = $smcFunc['db_fetch_row']($request);
643
			$smcFunc['db_free_result']($request);
644
645
			// To stop the page index's being abnormal, start the page on the page the message would normally be located on...
646
			$_GET['start'] = $maxPerPage * (int) ($_GET['start'] / $maxPerPage);
647
		}
648
	}
649
650
	// Sanitize and validate pmsg variable if set.
651
	if (isset($_GET['pmsg']))
652
	{
653
		$pmsg = (int) $_GET['pmsg'];
654
655
		if (!isAccessiblePM($pmsg, $context['folder'] == 'sent' ? 'outbox' : 'inbox'))
656
			fatal_lang_error('no_access', false);
657
	}
658
659
	// Set up the page index.
660
	$context['page_index'] = constructPageIndex($scripturl . '?action=pm;f=' . $context['folder'] . (isset($_REQUEST['l']) ? ';l=' . (int) $_REQUEST['l'] : '') . ';sort=' . $context['sort_by'] . ($descending ? ';desc' : ''), $_GET['start'], $max_messages, $maxPerPage);
661
	$context['start'] = $_GET['start'];
662
663
	// Determine the navigation context.
664
	$context['links'] = array(
665
		'first' => $_GET['start'] >= $maxPerPage ? $scripturl . '?action=pm;start=0' : '',
666
		'prev' => $_GET['start'] >= $maxPerPage ? $scripturl . '?action=pm;start=' . ($_GET['start'] - $maxPerPage) : '',
667
		'next' => $_GET['start'] + $maxPerPage < $max_messages ? $scripturl . '?action=pm;start=' . ($_GET['start'] + $maxPerPage) : '',
668
		'last' => $_GET['start'] + $maxPerPage < $max_messages ? $scripturl . '?action=pm;start=' . (floor(($max_messages - 1) / $maxPerPage) * $maxPerPage) : '',
669
		'up' => $scripturl,
670
	);
671
	$context['page_info'] = array(
672
		'current_page' => $_GET['start'] / $maxPerPage + 1,
673
		'num_pages' => floor(($max_messages - 1) / $maxPerPage) + 1
674
	);
675
676
	// First work out what messages we need to see - if grouped is a little trickier...
677
	if ($context['display_mode'] == 2)
678
	{
679
		if ($context['folder'] != 'sent' && $context['folder'] != 'inbox')
680
		{
681
			$labelJoin = '
682
				INNER JOIN {db_prefix}pm_labeled_messages AS pl ON (pl.id_pm = pm.id_pm)';
683
684
			$labelQuery = '';
685
			$labelQuery2 = '
686
				AND pl.id_label = ' . $context['current_label_id'];
687
		}
688
689
		$request = $smcFunc['db_query']('', '
690
			SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
691
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? '
692
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
693
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
694
					AND pmr.id_member = {int:current_member}
695
					AND pmr.deleted = {int:deleted_by}
696
					' . $labelQuery . ')') . $labelJoin . ($context['sort_by'] == 'name' ? ('
697
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
698
			WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
699
				AND pm.deleted_by_sender = {int:deleted_by}' : '1=1') . (empty($pmsg) ? '' : '
700
				AND pm.id_pm = {int:pmsg}') . $labelQuery2 . '
701
			GROUP BY pm.id_pm_head' . ($_GET['sort'] != 'pm.id_pm' ? ',' . $_GET['sort'] : '') . '
702
			ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' ? 'id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($_GET['pmsg']) ? '
703
			LIMIT ' . $_GET['start'] . ', ' . $maxPerPage : ''),
704
			array(
705
				'current_member' => $user_info['id'],
706
				'deleted_by' => 0,
707
				'sort' => $_GET['sort'],
708
				'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
709
				'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
710
			)
711
		);
712
	}
713
	// This is kinda simple!
714
	else
715
	{
716
		// @todo SLOW This query uses a filesort. (inbox only.)
717
		$request = $smcFunc['db_query']('', '
718
			SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
719
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' . ($context['sort_by'] == 'name' ? '
720
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
721
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
722
					AND pmr.id_member = {int:current_member}
723
					AND pmr.deleted = {int:is_deleted}
724
					' . $labelQuery . ')') . $labelJoin . ($context['sort_by'] == 'name' ? ('
725
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
726
			WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
727
				AND pm.deleted_by_sender = {int:is_deleted}' : '1=1') . (empty($pmsg) ? '' : '
728
				AND pm.id_pm = {int:pmsg}') . $labelQuery2 . '
729
			ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'pmr.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? '
730
			LIMIT ' . $_GET['start'] . ', ' . $maxPerPage : ''),
731
			array(
732
				'current_member' => $user_info['id'],
733
				'is_deleted' => 0,
734
				'sort' => $_GET['sort'],
735
				'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
736
				'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
737
			)
738
		);
739
	}
740
	// Load the id_pms and initialize recipients.
741
	$pms = array();
742
	$lastData = array();
743
	$posters = $context['folder'] == 'sent' ? array($user_info['id']) : array();
744
	$recipients = array();
745
746
	while ($row = $smcFunc['db_fetch_assoc']($request))
747
	{
748
		if (!isset($recipients[$row['id_pm']]))
749
		{
750
			if (isset($row['id_member_from']))
751
				$posters[$row['id_pm']] = $row['id_member_from'];
752
			$pms[$row['id_pm']] = $row['id_pm'];
753
			$recipients[$row['id_pm']] = array(
754
				'to' => array(),
755
				'bcc' => array()
756
			);
757
		}
758
759
		// Keep track of the last message so we know what the head is without another query!
760
		if ((empty($pmID) && (empty($options['view_newest_pm_first']) || !isset($lastData))) || empty($lastData) || (!empty($pmID) && $pmID == $row['id_pm']))
761
			$lastData = array(
762
				'id' => $row['id_pm'],
763
				'head' => $row['id_pm_head'],
764
			);
765
	}
766
	$smcFunc['db_free_result']($request);
767
768
	// Make sure that we have been given a correct head pm id!
769
	if ($context['display_mode'] == 2 && !empty($pmID) && $pmID != $lastData['id'])
770
		fatal_lang_error('no_access', false);
771
772
	if (!empty($pms))
773
	{
774
		// Select the correct current message.
775
		if (empty($pmID))
776
			$context['current_pm'] = $lastData['id'];
777
778
		// This is a list of the pm's that are used for "full" display.
779
		if ($context['display_mode'] == 0)
780
			$display_pms = $pms;
781
		else
782
			$display_pms = array($context['current_pm']);
783
784
		// At this point we know the main id_pm's. But - if we are looking at conversations we need the others!
785
		if ($context['display_mode'] == 2)
786
		{
787
			$request = $smcFunc['db_query']('', '
788
				SELECT pm.id_pm, pm.id_member_from, pm.deleted_by_sender, pmr.id_member, pmr.deleted
789
				FROM {db_prefix}personal_messages AS pm
790
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
791
				WHERE pm.id_pm_head = {int:id_pm_head}
792
					AND ((pm.id_member_from = {int:current_member} AND pm.deleted_by_sender = {int:not_deleted})
793
					OR (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted}))
794
				ORDER BY pm.id_pm',
795
				array(
796
					'current_member' => $user_info['id'],
797
					'id_pm_head' => $lastData['head'],
798
					'not_deleted' => 0,
799
				)
800
			);
801
			while ($row = $smcFunc['db_fetch_assoc']($request))
802
			{
803
				// This is, frankly, a joke. We will put in a workaround for people sending to themselves - yawn!
804
				if ($context['folder'] == 'sent' && $row['id_member_from'] == $user_info['id'] && $row['deleted_by_sender'] == 1)
805
					continue;
806
				elseif ($row['id_member'] == $user_info['id'] & $row['deleted'] == 1)
0 ignored issues
show
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
807
					continue;
808
809
				if (!isset($recipients[$row['id_pm']]))
810
					$recipients[$row['id_pm']] = array(
811
						'to' => array(),
812
						'bcc' => array()
813
					);
814
				$display_pms[] = $row['id_pm'];
815
				$posters[$row['id_pm']] = $row['id_member_from'];
816
			}
817
			$smcFunc['db_free_result']($request);
818
		}
819
820
		// This is pretty much EVERY pm!
821
		$all_pms = array_merge($pms, $display_pms);
822
		$all_pms = array_unique($all_pms);
823
824
		// Get recipients (don't include bcc-recipients for your inbox, you're not supposed to know :P).
825
		$request = $smcFunc['db_query']('', '
826
			SELECT pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc, pmr.in_inbox, pmr.is_read
827
			FROM {db_prefix}pm_recipients AS pmr
828
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
829
			WHERE pmr.id_pm IN ({array_int:pm_list})',
830
			array(
831
				'pm_list' => $all_pms,
832
			)
833
		);
834
		$context['message_labels'] = array();
835
		$context['message_replied'] = array();
836
		$context['message_unread'] = array();
837
		while ($row = $smcFunc['db_fetch_assoc']($request))
838
		{
839
			if ($context['folder'] == 'sent' || empty($row['bcc']))
840
			{
841
				$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>';
842
843
				$context['folder'] == 'sent' && $context['display_mode'] != 2 ? $context['message_replied'][$row['id_pm']] = $row['is_read'] & 2 : '';
844
			}
845
846
			if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
847
			{
848
				$context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
849
				$context['message_unread'][$row['id_pm']] = $row['is_read'] == 0;
850
851
				// Get the labels for this PM
852
				$request2 = $smcFunc['db_query']('', '
853
					SELECT id_label
854
					FROM {db_prefix}pm_labeled_messages
855
					WHERE id_pm = {int:current_pm}',
856
					array(
857
						'current_pm' => $row['id_pm'],
858
					)
859
				);
860
861
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
862
				{
863
					$l_id = $row2['id_label'];
864
					if (isset($context['labels'][$l_id]))
865
						$context['message_labels'][$row['id_pm']][$l_id] = array('id' => $l_id, 'name' => $context['labels'][$l_id]['name']);
866
				}
867
868
				$smcFunc['db_free_result']($request2);
869
870
				// Is this in the inbox as well?
871
				if ($row['in_inbox'] == 1)
872
				{
873
					$context['message_labels'][$row['id_pm']][-1] = array('id' => -1, 'name' => $context['labels'][-1]['name']);
874
				}
875
			}
876
		}
877
		$smcFunc['db_free_result']($request);
878
879
		// Make sure we don't load unnecessary data.
880
		if ($context['display_mode'] == 1)
881
		{
882
			foreach ($posters as $k => $v)
883
				if (!in_array($k, $display_pms))
884
					unset($posters[$k]);
885
		}
886
887
		// Load any users....
888
		loadMemberData($posters);
889
890
		// If we're on grouped/restricted view get a restricted list of messages.
891
		if ($context['display_mode'] != 0)
892
		{
893
			// Get the order right.
894
			$orderBy = array();
895
			foreach (array_reverse($pms) as $pm)
896
				$orderBy[] = 'pm.id_pm = ' . $pm;
897
898
			// Seperate query for these bits!
899
			$subjects_request = $smcFunc['db_query']('', '
900
				SELECT pm.id_pm, pm.subject, COALESCE(pm.id_member_from, 0) AS id_member_from, pm.msgtime, COALESCE(mem.real_name, pm.from_name) AS from_name,
901
					mem.id_member
902
				FROM {db_prefix}personal_messages AS pm
903
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
904
				WHERE pm.id_pm IN ({array_int:pm_list})
905
				ORDER BY ' . implode(', ', $orderBy) . '
906
				LIMIT {int:limit}',
907
				array(
908
					'pm_list' => $pms,
909
					'limit' => count($pms),
910
				)
911
			);
912
		}
913
914
		// Execute the query!
915
		$messages_request = $smcFunc['db_query']('', '
916
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
917
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '
918
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') . ($context['sort_by'] == 'name' ? '
919
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})' : '') . '
920
			WHERE pm.id_pm IN ({array_int:display_pms})' . ($context['folder'] == 'sent' ? '
921
			GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' : '') . '
922
			ORDER BY ' . ($context['display_mode'] == 2 ? 'pm.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . '
923
			LIMIT {int:limit}',
924
			array(
925
				'display_pms' => $display_pms,
926
				'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
927
				'limit' => count($display_pms),
928
				'sort' => $_GET['sort'],
929
			)
930
		);
931
932
		// Build the conversation button array.
933
		if ($context['display_mode'] == 2)
934
		{
935
			$context['conversation_buttons'] = array(
936
				'delete' => array('text' => 'delete_conversation', 'image' => 'delete.png', 'url' => $scripturl . '?action=pm;sa=pmactions;pm_actions[' . $context['current_pm'] . ']=delete;conversation;f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'], 'custom' => 'data-confirm="' . $txt['remove_conversation'] . '"', 'class' => 'you_sure'),
937
			);
938
939
			// Allow mods to add additional buttons here
940
			call_integration_hook('integrate_conversation_buttons');
941
		}
942
	}
943
	else
944
		$messages_request = false;
945
946
	$context['can_send_pm'] = allowedTo('pm_send');
947
	$context['can_send_email'] = allowedTo('moderate_forum');
948
	$context['sub_template'] = 'folder';
949
	$context['page_title'] = $txt['pm_inbox'];
950
951
	// Finally mark the relevant messages as read.
952
	if ($context['folder'] != 'sent' && !empty($context['labels'][(int) $context['current_label_id']]['unread_messages']))
953
	{
954
		// If the display mode is "old sk00l" do them all...
955
		if ($context['display_mode'] == 0)
956
			markMessages(null, $context['current_label_id']);
957
		// Otherwise do just the current one!
958
		elseif (!empty($context['current_pm']))
959
			markMessages($display_pms, $context['current_label_id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $display_pms does not seem to be defined for all execution paths leading up to this point.
Loading history...
960
	}
961
}
962
963
/**
964
 * Get a personal message for the theme.  (used to save memory.)
965
 *
966
 * @param string $type The type of message
967
 * @param bool $reset Whether to reset the internal pointer
968
 * @return bool|array False on failure, otherwise an array of info
969
 */
970
function prepareMessageContext($type = 'subject', $reset = false)
971
{
972
	global $txt, $scripturl, $modSettings, $context, $messages_request, $memberContext, $recipients, $smcFunc;
973
	global $user_info, $subjects_request;
974
975
	// Count the current message number....
976
	static $counter = null;
977
	if ($counter === null || $reset)
978
		$counter = $context['start'];
979
980
	static $temp_pm_selected = null;
981
	if ($temp_pm_selected === null)
982
	{
983
		$temp_pm_selected = isset($_SESSION['pm_selected']) ? $_SESSION['pm_selected'] : array();
984
		$_SESSION['pm_selected'] = array();
985
	}
986
987
	// If we're in non-boring view do something exciting!
988
	if ($context['display_mode'] != 0 && $subjects_request && $type == 'subject')
989
	{
990
		$subject = $smcFunc['db_fetch_assoc']($subjects_request);
991
		if (!$subject)
992
		{
993
			$smcFunc['db_free_result']($subjects_request);
994
			return false;
995
		}
996
997
		$subject['subject'] = $subject['subject'] == '' ? $txt['no_subject'] : $subject['subject'];
998
		censorText($subject['subject']);
999
1000
		$output = array(
1001
			'id' => $subject['id_pm'],
1002
			'member' => array(
1003
				'id' => $subject['id_member_from'],
1004
				'name' => $subject['from_name'],
1005
				'link' => ($subject['id_member_from'] != 0) ? '<a href="' . $scripturl . '?action=profile;u=' . $subject['id_member_from'] . '">' . $subject['from_name'] . '</a>' : $subject['from_name'],
1006
			),
1007
			'recipients' => &$recipients[$subject['id_pm']],
1008
			'subject' => $subject['subject'],
1009
			'time' => timeformat($subject['msgtime']),
1010
			'timestamp' => forum_time(true, $subject['msgtime']),
1011
			'number_recipients' => count($recipients[$subject['id_pm']]['to']),
1012
			'labels' => &$context['message_labels'][$subject['id_pm']],
1013
			'fully_labeled' => count(isset($context['message_labels'][$subject['id_pm']]) ? $context['message_labels'][$subject['id_pm']] : array()) == count($context['labels']),
1014
			'is_replied_to' => &$context['message_replied'][$subject['id_pm']],
1015
			'is_unread' => &$context['message_unread'][$subject['id_pm']],
1016
			'is_selected' => !empty($temp_pm_selected) && in_array($subject['id_pm'], $temp_pm_selected),
1017
		);
1018
1019
		return $output;
1020
	}
1021
1022
	// Bail if it's false, ie. no messages.
1023
	if ($messages_request == false)
1024
		return false;
1025
1026
	// Reset the data?
1027
	if ($reset == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1028
		return @$smcFunc['db_data_seek']($messages_request, 0);
1029
1030
	// Get the next one... bail if anything goes wrong.
1031
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1032
	if (!$message)
1033
	{
1034
		if ($type != 'subject')
1035
			$smcFunc['db_free_result']($messages_request);
1036
1037
		return false;
1038
	}
1039
1040
	// Use '(no subject)' if none was specified.
1041
	$message['subject'] = $message['subject'] == '' ? $txt['no_subject'] : $message['subject'];
1042
1043
	// Load the message's information - if it's not there, load the guest information.
1044
	if (!loadMemberContext($message['id_member_from'], true))
1045
	{
1046
		$memberContext[$message['id_member_from']]['name'] = $message['from_name'];
1047
		$memberContext[$message['id_member_from']]['id'] = 0;
1048
1049
		// Sometimes the forum sends messages itself (Warnings are an example) - in this case don't label it from a guest.
1050
		$memberContext[$message['id_member_from']]['group'] = $message['from_name'] == $context['forum_name_html_safe'] ? '' : $txt['guest_title'];
1051
		$memberContext[$message['id_member_from']]['link'] = $message['from_name'];
1052
		$memberContext[$message['id_member_from']]['email'] = '';
1053
		$memberContext[$message['id_member_from']]['show_email'] = false;
1054
		$memberContext[$message['id_member_from']]['is_guest'] = true;
1055
	}
1056
	else
1057
	{
1058
		$memberContext[$message['id_member_from']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member_from'] == $user_info['id'] && !$user_info['is_guest']);
1059
		$memberContext[$message['id_member_from']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member_from']]['warning_status'] && ($context['user']['can_mod'] || (!empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $message['id_member_from'] == $user_info['id'])));
1060
		// Show the email if it's your own PM
1061
		$memberContext[$message['id_member_from']]['show_email'] |= $message['id_member_from'] == $user_info['id'];
1062
	}
1063
1064
	$memberContext[$message['id_member_from']]['show_profile_buttons'] = $modSettings['show_profile_buttons'] && (!empty($memberContext[$message['id_member_from']]['can_view_profile']) || (!empty($memberContext[$message['id_member_from']]['website']['url']) && !isset($context['disabled_fields']['website'])) || $memberContext[$message['id_member_from']]['show_email'] || $context['can_send_pm']);
1065
1066
	// Censor all the important text...
1067
	censorText($message['body']);
1068
	censorText($message['subject']);
1069
1070
	// Run UBBC interpreter on the message.
1071
	$message['body'] = parse_bbc($message['body'], true, 'pm' . $message['id_pm']);
1072
1073
	// Send the array.
1074
	$output = array(
1075
		'id' => $message['id_pm'],
1076
		'member' => &$memberContext[$message['id_member_from']],
1077
		'subject' => $message['subject'],
1078
		'time' => timeformat($message['msgtime']),
1079
		'timestamp' => forum_time(true, $message['msgtime']),
1080
		'counter' => $counter,
1081
		'body' => $message['body'],
1082
		'recipients' => &$recipients[$message['id_pm']],
1083
		'number_recipients' => count($recipients[$message['id_pm']]['to']),
1084
		'labels' => &$context['message_labels'][$message['id_pm']],
1085
		'fully_labeled' => count(isset($context['message_labels'][$message['id_pm']]) ? $context['message_labels'][$message['id_pm']] : array()) == count($context['labels']),
1086
		'is_replied_to' => &$context['message_replied'][$message['id_pm']],
1087
		'is_unread' => &$context['message_unread'][$message['id_pm']],
1088
		'is_selected' => !empty($temp_pm_selected) && in_array($message['id_pm'], $temp_pm_selected),
1089
		'is_message_author' => $message['id_member_from'] == $user_info['id'],
1090
		'can_report' => !empty($modSettings['enableReportPM']),
1091
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
1092
	);
1093
1094
	$counter++;
1095
1096
	// Any custom profile fields?
1097
	if (!empty($memberContext[$message['id_member_from']]['custom_fields']))
1098
		foreach ($memberContext[$message['id_member_from']]['custom_fields'] as $custom)
1099
			$output['custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
1100
1101
	call_integration_hook('integrate_prepare_pm_context', array(&$output, &$message, $counter));
1102
1103
	$output['quickbuttons'] = array(
1104
		'reply_to_all' => array(
1105
			'label' => $txt['reply_to_all'],
1106
			'href' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $output['id'] . ($output['member']['id'] != $user_info['id'] ? ';quote' : '') . ';u=all',
1107
			'icon' => 'reply_all_button',
1108
			'show' => $context['can_send_pm'] && !$output['member']['is_guest'] && ($output['number_recipients'] > 1 || $output['member']['id'] == $user_info['id']),
1109
		),
1110
		'reply' => array(
1111
			'label' => $txt['reply'],
1112
			'href' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $output['id'] . ';u=' . $output['member']['id'],
1113
			'icon' => 'reply_button',
1114
			'show' => $context['can_send_pm'] && !$output['member']['is_guest'] && $output['member']['id'] != $user_info['id'],
1115
		),
1116
		'quote' => array(
1117
			'label' => $txt['quote_action'],
1118
			'href' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $output['id'] . ';quote' . ($output['number_recipients'] > 1 || $output['member']['id'] == $user_info['id'] ? ';u=all' : (!$output['member']['is_guest'] ? ';u=' . $output['member']['id'] : '')),
1119
			'icon' => 'quote',
1120
			'show' => $context['can_send_pm'],
1121
		),
1122
		'delete' => array(
1123
			'label' => $txt['delete'],
1124
			'href' => $scripturl . '?action=pm;sa=pmactions;pm_actions%5b' . $output['id'] . '%5D=delete;f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'],
1125
			'javascript' => 'data-confirm="' . JavaScriptEscape($txt['remove_message_question']) . '"',
1126
			'class' => 'you_sure',
1127
			'icon' => 'remove_button',
1128
		),
1129
		'more' => array(
1130
			'report' => array(
1131
				'label' => $txt['pm_report_to_admin'],
1132
				'href' => $scripturl . '?action=pm;sa=report;l=' . $context['current_label_id'] . ';pmsg=' . $output['id'],
1133
				'icon' => 'error',
1134
				'show' => $output['can_report']
1135
			),
1136
		),
1137
		'quickmod' => array(
1138
			'content' => '<input type="checkbox" name="pms[]" id="deletedisplay' . $output['id'] . '" value="' . $output['id'] . '" onclick="document.getElementById(\'deletelisting' . $output['id'] . '\').checked = this.checked;">',
1139
			'show' => empty($context['display_mode'])
1140
		)
1141
	);
1142
1143
	return $output;
1144
}
1145
1146
/**
1147
 * Allows searching through personal messages.
1148
 */
1149
function MessageSearch()
1150
{
1151
	global $context, $txt, $scripturl, $smcFunc;
1152
1153
	if (isset($_REQUEST['params']))
1154
	{
1155
		$temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
1156
		$context['search_params'] = array();
1157
		foreach ($temp_params as $i => $data)
1158
		{
1159
			@list ($k, $v) = explode('|\'|', $data);
1160
			$context['search_params'][$k] = $v;
1161
		}
1162
	}
1163
	if (isset($_REQUEST['search']))
1164
		$context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']);
1165
1166
	if (isset($context['search_params']['search']))
1167
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1168
	if (isset($context['search_params']['userspec']))
1169
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1170
1171
	if (!empty($context['search_params']['searchtype']))
1172
		$context['search_params']['searchtype'] = 2;
1173
1174
	if (!empty($context['search_params']['minage']))
1175
		$context['search_params']['minage'] = (int) $context['search_params']['minage'];
1176
1177
	if (!empty($context['search_params']['maxage']))
1178
		$context['search_params']['maxage'] = (int) $context['search_params']['maxage'];
1179
1180
	$context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']);
1181
	$context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']);
1182
1183
	// Create the array of labels to be searched.
1184
	$context['search_labels'] = array();
1185
	$searchedLabels = isset($context['search_params']['labels']) && $context['search_params']['labels'] != '' ? explode(',', $context['search_params']['labels']) : array();
1186
	foreach ($context['labels'] as $label)
1187
	{
1188
		$context['search_labels'][] = array(
1189
			'id' => $label['id'],
1190
			'name' => $label['name'],
1191
			'checked' => !empty($searchedLabels) ? in_array($label['id'], $searchedLabels) : true,
1192
		);
1193
	}
1194
1195
	// Are all the labels checked?
1196
	$context['check_all'] = empty($searchedLabels) || count($context['search_labels']) == count($searchedLabels);
1197
1198
	// Load the error text strings if there were errors in the search.
1199
	if (!empty($context['search_errors']))
1200
	{
1201
		loadLanguage('Errors');
1202
		$context['search_errors']['messages'] = array();
1203
		foreach ($context['search_errors'] as $search_error => $dummy)
1204
		{
1205
			if ($search_error == 'messages')
1206
				continue;
1207
1208
			$context['search_errors']['messages'][] = $txt['error_' . $search_error];
1209
		}
1210
	}
1211
1212
	$context['page_title'] = $txt['pm_search_title'];
1213
	$context['sub_template'] = 'search';
1214
	$context['linktree'][] = array(
1215
		'url' => $scripturl . '?action=pm;sa=search',
1216
		'name' => $txt['pm_search_bar_title'],
1217
	);
1218
}
1219
1220
/**
1221
 * Actually do the search of personal messages.
1222
 */
1223
function MessageSearch2()
1224
{
1225
	global $scripturl, $modSettings, $user_info, $context, $txt;
1226
	global $memberContext, $smcFunc;
1227
1228
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search'])
1229
		fatal_lang_error('loadavg_search_disabled', false);
1230
1231
	/**
1232
	 * @todo For the moment force the folder to the inbox.
1233
	 * @todo Maybe set the inbox based on a cookie or theme setting?
1234
	 */
1235
	$context['folder'] = 'inbox';
1236
1237
	// Some useful general permissions.
1238
	$context['can_send_pm'] = allowedTo('pm_send');
1239
1240
	// Some hardcoded veriables that can be tweaked if required.
1241
	$maxMembersToSearch = 500;
1242
1243
	// Extract all the search parameters.
1244
	$search_params = array();
1245
	if (isset($_REQUEST['params']))
1246
	{
1247
		$temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
1248
		foreach ($temp_params as $i => $data)
1249
		{
1250
			@list ($k, $v) = explode('|\'|', $data);
1251
			$search_params[$k] = $v;
1252
		}
1253
	}
1254
1255
	$context['start'] = isset($_GET['start']) ? (int) $_GET['start'] : 0;
1256
1257
	// Store whether simple search was used (needed if the user wants to do another query).
1258
	if (!isset($search_params['advanced']))
1259
		$search_params['advanced'] = empty($_REQUEST['advanced']) ? 0 : 1;
1260
1261
	// 1 => 'allwords' (default, don't set as param) / 2 => 'anywords'.
1262
	if (!empty($search_params['searchtype']) || (!empty($_REQUEST['searchtype']) && $_REQUEST['searchtype'] == 2))
1263
		$search_params['searchtype'] = 2;
1264
1265
	// Minimum age of messages. Default to zero (don't set param in that case).
1266
	if (!empty($search_params['minage']) || (!empty($_REQUEST['minage']) && $_REQUEST['minage'] > 0))
1267
		$search_params['minage'] = !empty($search_params['minage']) ? (int) $search_params['minage'] : (int) $_REQUEST['minage'];
1268
1269
	// Maximum age of messages. Default to infinite (9999 days: param not set).
1270
	if (!empty($search_params['maxage']) || (!empty($_REQUEST['maxage']) && $_REQUEST['maxage'] != 9999))
1271
		$search_params['maxage'] = !empty($search_params['maxage']) ? (int) $search_params['maxage'] : (int) $_REQUEST['maxage'];
1272
1273
	$search_params['subject_only'] = !empty($search_params['subject_only']) || !empty($_REQUEST['subject_only']);
1274
	$search_params['show_complete'] = !empty($search_params['show_complete']) || !empty($_REQUEST['show_complete']);
1275
1276
	// Default the user name to a wildcard matching every user (*).
1277
	if (!empty($search_params['user_spec']) || (!empty($_REQUEST['userspec']) && $_REQUEST['userspec'] != '*'))
1278
		$search_params['userspec'] = isset($search_params['userspec']) ? $search_params['userspec'] : $_REQUEST['userspec'];
1279
1280
	// This will be full of all kinds of parameters!
1281
	$searchq_parameters = array();
1282
1283
	// If there's no specific user, then don't mention it in the main query.
1284
	if (empty($search_params['userspec']))
1285
		$userQuery = '';
1286
	else
1287
	{
1288
		$userString = strtr($smcFunc['htmlspecialchars']($search_params['userspec'], ENT_QUOTES), array('&quot;' => '"'));
1289
		$userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_'));
1290
1291
		preg_match_all('~"([^"]+)"~', $userString, $matches);
1292
		$possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString)));
1293
1294
		for ($k = 0, $n = count($possible_users); $k < $n; $k++)
1295
		{
1296
			$possible_users[$k] = trim($possible_users[$k]);
1297
1298
			if (strlen($possible_users[$k]) == 0)
1299
				unset($possible_users[$k]);
1300
		}
1301
1302
		if (!empty($possible_users))
1303
		{
1304
			// We need to bring this into the query and do it nice and cleanly.
1305
			$where_params = array();
1306
			$where_clause = array();
1307
			foreach ($possible_users as $k => $v)
1308
			{
1309
				$where_params['name_' . $k] = $v;
1310
				$where_clause[] = '{raw:real_name} LIKE {string:name_' . $k . '}';
1311
				if (!isset($where_params['real_name']))
1312
					$where_params['real_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name';
1313
			}
1314
1315
			// Who matches those criteria?
1316
			// @todo This doesn't support sent item searching.
1317
			$request = $smcFunc['db_query']('', '
1318
				SELECT id_member
1319
				FROM {db_prefix}members
1320
				WHERE ' . implode(' OR ', $where_clause),
1321
				$where_params
1322
			);
1323
1324
			// Simply do nothing if there're too many members matching the criteria.
1325
			if ($smcFunc['db_num_rows']($request) > $maxMembersToSearch)
1326
				$userQuery = '';
1327
			elseif ($smcFunc['db_num_rows']($request) == 0)
1328
			{
1329
				$userQuery = 'AND pm.id_member_from = 0 AND ({raw:pm_from_name} LIKE {raw:guest_user_name_implode})';
1330
				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
1331
				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
1332
			}
1333
			else
1334
			{
1335
				$memberlist = array();
1336
				while ($row = $smcFunc['db_fetch_assoc']($request))
1337
					$memberlist[] = $row['id_member'];
1338
				$userQuery = 'AND (pm.id_member_from IN ({array_int:member_list}) OR (pm.id_member_from = 0 AND ({raw:pm_from_name} LIKE {raw:guest_user_name_implode})))';
1339
				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
1340
				$searchq_parameters['member_list'] = $memberlist;
1341
				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
1342
			}
1343
			$smcFunc['db_free_result']($request);
1344
		}
1345
		else
1346
			$userQuery = '';
1347
	}
1348
1349
	// Setup the sorting variables...
1350
	// @todo Add more in here!
1351
	$sort_columns = array(
1352
		'pm.id_pm',
1353
	);
1354
	if (empty($search_params['sort']) && !empty($_REQUEST['sort']))
1355
		list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, '');
1356
	$search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'pm.id_pm';
1357
	$search_params['sort_dir'] = !empty($search_params['sort_dir']) && $search_params['sort_dir'] == 'asc' ? 'asc' : 'desc';
1358
1359
	// Sort out any labels we may be searching by.
1360
	$context['search_in'] = array();
1361
	$labelQuery = '';
1362
	$labelJoin = '';
1363
	if ($context['folder'] == 'inbox' && !empty($search_params['advanced']) && $context['currently_using_labels'])
1364
	{
1365
		// Came here from pagination?  Put them back into $_REQUEST for sanitization.
1366
		if (isset($search_params['labels']))
1367
			$_REQUEST['searchlabel'] = explode(',', $search_params['labels']);
1368
1369
		// Assuming we have some labels - make them all integers.
1370
		if (!empty($_REQUEST['searchlabel']) && is_array($_REQUEST['searchlabel']))
1371
		{
1372
			foreach ($_REQUEST['searchlabel'] as $key => $id)
1373
				$_REQUEST['searchlabel'][$key] = (int) $id;
1374
		}
1375
		else
1376
			$_REQUEST['searchlabel'] = array();
1377
1378
		// Now that everything is cleaned up a bit, make the labels a param.
1379
		$search_params['labels'] = implode(',', $_REQUEST['searchlabel']);
1380
1381
		// No labels selected? That must be an error!
1382
		if (empty($_REQUEST['searchlabel']))
1383
			$context['search_errors']['no_labels_selected'] = true;
1384
		// Otherwise prepare the query!
1385
		elseif (count($_REQUEST['searchlabel']) != count($context['labels']))
1386
		{
1387
			// Special case here... "inbox" isn't a real label anymore...
1388
			if (in_array(-1, $_REQUEST['searchlabel']))
1389
			{
1390
				$context['search_in'][] = $context['labels'][-1]['name'];
1391
1392
				$labelQuery = '	AND pmr.in_inbox = {int:in_inbox}';
1393
				$searchq_parameters['in_inbox'] = 1;
1394
1395
				// Now we get rid of that...
1396
				$temp = array_diff($_REQUEST['searchlabel'], array(-1));
1397
				$_REQUEST['searchlabel'] = $temp;
1398
			}
1399
1400
			// Still have something?
1401
			if (!empty($_REQUEST['searchlabel']))
1402
			{
1403
				if ($labelQuery == '')
1404
				{
1405
					// Not searching the inbox - PM must be labeled
1406
					$labelQuery = ' AND pml.id_label IN ({array_int:labels})';
1407
					$labelJoin = ' INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1408
				}
1409
				else
1410
				{
1411
					// Searching the inbox - PM doesn't have to be labeled
1412
					$labelQuery = ' AND (' . substr($labelQuery, 5) . ' OR pml.id_label IN ({array_int:labels}))';
1413
					$labelJoin = ' LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1414
				}
1415
1416
				$searchq_parameters['labels'] = $_REQUEST['searchlabel'];
1417
1418
				foreach ($_REQUEST['searchlabel'] as $label_key)
1419
					$context['search_in'][] = $context['labels'][$label_key]['name'];
1420
			}
1421
		}
1422
	}
1423
1424
	if (empty($context['search_in']))
1425
		$context['search_in'][] = $context['folder'];
1426
1427
	// What are we actually searching for?
1428
	$search_params['search'] = !empty($search_params['search']) ? $search_params['search'] : (isset($_REQUEST['search']) ? $_REQUEST['search'] : '');
1429
	// If we ain't got nothing - we should error!
1430
	if (!isset($search_params['search']) || $search_params['search'] == '')
1431
		$context['search_errors']['invalid_search_string'] = true;
1432
1433
	// Extract phrase parts first (e.g. some words "this is a phrase" some more words.)
1434
	preg_match_all('~(?:^|\s)([-]?)"([^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), $search_params['search'], $matches, PREG_PATTERN_ORDER);
1435
	$searchArray = $matches[2];
1436
1437
	// Remove the phrase parts and extract the words.
1438
	$tempSearch = explode(' ', preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']));
1439
1440
	// A minus sign in front of a word excludes the word.... so...
1441
	$excludedWords = array();
1442
1443
	// .. first, we check for things like -"some words", but not "-some words".
1444
	foreach ($matches[1] as $index => $word)
1445
		if ($word == '-')
1446
		{
1447
			$word = $smcFunc['strtolower'](trim($searchArray[$index]));
1448
			if (strlen($word) > 0)
1449
				$excludedWords[] = $word;
1450
			unset($searchArray[$index]);
1451
		}
1452
1453
	// Now we look for -test, etc.... normaller.
1454
	foreach ($tempSearch as $index => $word)
1455
	{
1456
		if (strpos(trim($word), '-') === 0)
1457
		{
1458
			$word = substr($smcFunc['strtolower']($word), 1);
1459
			if (strlen($word) > 0)
1460
				$excludedWords[] = $word;
1461
			unset($tempSearch[$index]);
1462
		}
1463
	}
1464
1465
	$searchArray = array_merge($searchArray, $tempSearch);
1466
1467
	// Trim everything and make sure there are no words that are the same.
1468
	foreach ($searchArray as $index => $value)
1469
	{
1470
		$searchArray[$index] = $smcFunc['strtolower'](trim($value));
1471
		if ($searchArray[$index] == '')
1472
			unset($searchArray[$index]);
1473
		else
1474
		{
1475
			// Sort out entities first.
1476
			$searchArray[$index] = $smcFunc['htmlspecialchars']($searchArray[$index]);
1477
		}
1478
	}
1479
	$searchArray = array_unique($searchArray);
1480
1481
	// Create an array of replacements for highlighting.
1482
	$context['mark'] = array();
1483
	foreach ($searchArray as $word)
1484
		$context['mark'][$word] = '<strong class="highlight">' . $word . '</strong>';
1485
1486
	// This contains *everything*
1487
	$searchWords = array_merge($searchArray, $excludedWords);
1488
1489
	// Make sure at least one word is being searched for.
1490
	if (empty($searchArray))
1491
		$context['search_errors']['invalid_search_string'] = true;
1492
1493
	// Sort out the search query so the user can edit it - if they want.
1494
	$context['search_params'] = $search_params;
1495
	if (isset($context['search_params']['search']))
1496
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1497
	if (isset($context['search_params']['userspec']))
1498
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1499
1500
	// Now we have all the parameters, combine them together for pagination and the like...
1501
	$context['params'] = array();
1502
	foreach ($search_params as $k => $v)
1503
		$context['params'][] = $k . '|\'|' . $v;
1504
	$context['params'] = base64_encode(implode('|"|', $context['params']));
1505
1506
	// Compile the subject query part.
1507
	$andQueryParts = array();
1508
1509
	foreach ($searchWords as $index => $word)
1510
	{
1511
		if ($word == '')
1512
			continue;
1513
1514
		if ($search_params['subject_only'])
1515
			$andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}';
1516
		else
1517
			$andQueryParts[] = '(pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '} ' . (in_array($word, $excludedWords) ? 'AND pm.body NOT' : 'OR pm.body') . ' LIKE {string:search_' . $index . '})';
1518
		$searchq_parameters['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%';
1519
	}
1520
1521
	$searchQuery = ' 1=1';
1522
	if (!empty($andQueryParts))
1523
		$searchQuery = implode(!empty($search_params['searchtype']) && $search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts);
1524
1525
	// Age limits?
1526
	$timeQuery = '';
1527
	if (!empty($search_params['minage']))
1528
		$timeQuery .= ' AND pm.msgtime < ' . (time() - $search_params['minage'] * 86400);
1529
	if (!empty($search_params['maxage']))
1530
		$timeQuery .= ' AND pm.msgtime > ' . (time() - $search_params['maxage'] * 86400);
1531
1532
	// If we have errors - return back to the first screen...
1533
	if (!empty($context['search_errors']))
1534
	{
1535
		$_REQUEST['params'] = $context['params'];
1536
		return MessageSearch();
0 ignored issues
show
Are you sure the usage of MessageSearch() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1537
	}
1538
1539
	// Get the amount of results.
1540
	$request = $smcFunc['db_query']('', '
1541
		SELECT COUNT(*)
1542
		FROM {db_prefix}pm_recipients AS pmr
1543
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1544
			' . $labelJoin . '
1545
		WHERE ' . ($context['folder'] == 'inbox' ? '
1546
			pmr.id_member = {int:current_member}
1547
			AND pmr.deleted = {int:not_deleted}' : '
1548
			pm.id_member_from = {int:current_member}
1549
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1550
			' . $userQuery . $labelQuery . $timeQuery . '
1551
			AND (' . $searchQuery . ')',
1552
		array_merge($searchq_parameters, array(
1553
			'current_member' => $user_info['id'],
1554
			'not_deleted' => 0,
1555
		))
1556
	);
1557
	list ($numResults) = $smcFunc['db_fetch_row']($request);
1558
	$smcFunc['db_free_result']($request);
1559
1560
	// Get all the matching messages... using standard search only (No caching and the like!)
1561
	// @todo This doesn't support sent item searching yet.
1562
	$request = $smcFunc['db_query']('', '
1563
		SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
1564
		FROM {db_prefix}pm_recipients AS pmr
1565
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1566
			' . $labelJoin . '
1567
		WHERE ' . ($context['folder'] == 'inbox' ? '
1568
			pmr.id_member = {int:current_member}
1569
			AND pmr.deleted = {int:not_deleted}' : '
1570
			pm.id_member_from = {int:current_member}
1571
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1572
			' . $userQuery . $labelQuery . $timeQuery . '
1573
			AND (' . $searchQuery . ')
1574
		ORDER BY {raw:sort} {raw:sort_dir}
1575
		LIMIT {int:start}, {int:max}',
1576
		array_merge($searchq_parameters, array(
1577
			'current_member' => $user_info['id'],
1578
			'not_deleted' => 0,
1579
			'sort' => $search_params['sort'],
1580
			'sort_dir' => $search_params['sort_dir'],
1581
			'start' => $context['start'],
1582
			'max' => $modSettings['search_results_per_page'],
1583
		))
1584
	);
1585
	$foundMessages = array();
1586
	$posters = array();
1587
	$head_pms = array();
1588
	while ($row = $smcFunc['db_fetch_assoc']($request))
1589
	{
1590
		$foundMessages[] = $row['id_pm'];
1591
		$posters[] = $row['id_member_from'];
1592
		$head_pms[$row['id_pm']] = $row['id_pm_head'];
1593
	}
1594
	$smcFunc['db_free_result']($request);
1595
1596
	// Find the real head pms!
1597
	if ($context['display_mode'] == 2 && !empty($head_pms))
1598
	{
1599
		$request = $smcFunc['db_query']('', '
1600
			SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
1601
			FROM {db_prefix}personal_messages AS pm
1602
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
1603
			WHERE pm.id_pm_head IN ({array_int:head_pms})
1604
				AND pmr.id_member = {int:current_member}
1605
				AND pmr.deleted = {int:not_deleted}
1606
			GROUP BY pm.id_pm_head
1607
			LIMIT {int:limit}',
1608
			array(
1609
				'head_pms' => array_unique($head_pms),
1610
				'current_member' => $user_info['id'],
1611
				'not_deleted' => 0,
1612
				'limit' => count($head_pms),
1613
			)
1614
		);
1615
		$real_pm_ids = array();
1616
		while ($row = $smcFunc['db_fetch_assoc']($request))
1617
			$real_pm_ids[$row['id_pm_head']] = $row['id_pm'];
1618
		$smcFunc['db_free_result']($request);
1619
	}
1620
1621
	// Load the users...
1622
	loadMemberData($posters);
1623
1624
	// Sort out the page index.
1625
	$context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=search2;params=' . $context['params'], $_GET['start'], $numResults, $modSettings['search_results_per_page'], false);
1626
1627
	$context['num_results'] = $numResults;
1628
1629
	$context['message_labels'] = array();
1630
	$context['message_replied'] = array();
1631
	$context['personal_messages'] = array();
1632
1633
	if (!empty($foundMessages))
1634
	{
1635
		// Now get recipients (but don't include bcc-recipients for your inbox, you're not supposed to know :P!)
1636
		$request = $smcFunc['db_query']('', '
1637
			SELECT
1638
				pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name,
1639
				pmr.bcc, pmr.in_inbox, pmr.is_read
1640
			FROM {db_prefix}pm_recipients AS pmr
1641
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
1642
			WHERE pmr.id_pm IN ({array_int:message_list})',
1643
			array(
1644
				'message_list' => $foundMessages,
1645
			)
1646
		);
1647
		while ($row = $smcFunc['db_fetch_assoc']($request))
1648
		{
1649
			if ($context['folder'] == 'sent' || empty($row['bcc']))
1650
				$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>';
1651
1652
			if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
1653
			{
1654
				$context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
1655
1656
				$row['labels'] = '';
1657
1658
				// Get the labels for this PM
1659
				$request2 = $smcFunc['db_query']('', '
1660
					SELECT id_label
1661
					FROM {db_prefix}pm_labeled_messages
1662
					WHERE id_pm = {int:current_pm}',
1663
					array(
1664
						'current_pm' => $row['id_pm'],
1665
					)
1666
				);
1667
1668
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
1669
				{
1670
					$l_id = $row2['id_label'];
1671
					if (isset($context['labels'][$l_id]))
1672
						$context['message_labels'][$row['id_pm']][$l_id] = array('id' => $l_id, 'name' => $context['labels'][$l_id]['name']);
1673
1674
					// Here we find the first label on a message - for linking to posts in results
1675
					if (!isset($context['first_label'][$row['id_pm']]) && $row['in_inbox'] != 1)
1676
						$context['first_label'][$row['id_pm']] = $l_id;
1677
				}
1678
1679
				$smcFunc['db_free_result']($request2);
1680
1681
				// Is this in the inbox as well?
1682
				if ($row['in_inbox'] == 1)
1683
				{
1684
					$context['message_labels'][$row['id_pm']][-1] = array('id' => -1, 'name' => $context['labels'][-1]['name']);
1685
				}
1686
1687
				$row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']);
1688
			}
1689
		}
1690
1691
		// Prepare the query for the callback!
1692
		$request = $smcFunc['db_query']('', '
1693
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
1694
			FROM {db_prefix}personal_messages AS pm
1695
			WHERE pm.id_pm IN ({array_int:message_list})
1696
			ORDER BY {raw:sort} {raw:sort_dir}
1697
			LIMIT {int:limit}',
1698
			array(
1699
				'message_list' => $foundMessages,
1700
				'limit' => count($foundMessages),
1701
				'sort' => $search_params['sort'],
1702
				'sort_dir' => $search_params['sort_dir'],
1703
			)
1704
		);
1705
		$counter = 0;
1706
		while ($row = $smcFunc['db_fetch_assoc']($request))
1707
		{
1708
			// If there's no message subject, use the default.
1709
			$row['subject'] = $row['subject'] == '' ? $txt['no_subject'] : $row['subject'];
1710
1711
			// Load this posters context info, if it ain't there then fill in the essentials...
1712
			if (!loadMemberContext($row['id_member_from'], true))
1713
			{
1714
				$memberContext[$row['id_member_from']]['name'] = $row['from_name'];
1715
				$memberContext[$row['id_member_from']]['id'] = 0;
1716
				$memberContext[$row['id_member_from']]['group'] = $txt['guest_title'];
1717
				$memberContext[$row['id_member_from']]['link'] = $row['from_name'];
1718
				$memberContext[$row['id_member_from']]['email'] = '';
1719
				$memberContext[$row['id_member_from']]['is_guest'] = true;
1720
			}
1721
			else
1722
			{
1723
				$memberContext[$row['id_member_from']]['can_view_profile'] = allowedTo('profile_view') || ($row['id_member_from'] == $user_info['id'] && !$user_info['is_guest']);
1724
				$memberContext[$row['id_member_from']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$row['id_member_from']]['warning_status'] && ($context['user']['can_mod'] || (!empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $row['id_member_from'] == $user_info['id'])));
1725
				// Show the email if it's your own PM
1726
				$memberContext[$row['id_member_from']]['show_email'] |= $row['id_member_from'] == $user_info['id'];
1727
			}
1728
1729
			$memberContext[$row['id_member_from']]['show_profile_buttons'] = $modSettings['show_profile_buttons'] && (!empty($memberContext[$row['id_member_from']]['can_view_profile']) || (!empty($memberContext[$row['id_member_from']]['website']['url']) && !isset($context['disabled_fields']['website'])) || $memberContext[$row['id_member_from']]['show_email'] || $context['can_send_pm']);
1730
1731
			// Censor anything we don't want to see...
1732
			censorText($row['body']);
1733
			censorText($row['subject']);
1734
1735
			// Parse out any BBC...
1736
			$row['body'] = parse_bbc($row['body'], true, 'pm' . $row['id_pm']);
1737
1738
			$href = $scripturl . '?action=pm;f=' . $context['folder'] . (isset($context['first_label'][$row['id_pm']]) ? ';l=' . $context['first_label'][$row['id_pm']] : '') . ';pmid=' . ($context['display_mode'] == 2 && isset($real_pm_ids[$head_pms[$row['id_pm']]]) ? $real_pm_ids[$head_pms[$row['id_pm']]] : $row['id_pm']) . '#msg' . $row['id_pm'];
1739
			$context['personal_messages'][] = array(
1740
				'id' => $row['id_pm'],
1741
				'member' => &$memberContext[$row['id_member_from']],
1742
				'subject' => $row['subject'],
1743
				'body' => $row['body'],
1744
				'time' => timeformat($row['msgtime']),
1745
				'recipients' => &$recipients[$row['id_pm']],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $recipients does not seem to be defined for all execution paths leading up to this point.
Loading history...
1746
				'labels' => &$context['message_labels'][$row['id_pm']],
1747
				'fully_labeled' => count($context['message_labels'][$row['id_pm']]) == count($context['labels']),
1748
				'is_replied_to' => &$context['message_replied'][$row['id_pm']],
1749
				'href' => $href,
1750
				'link' => '<a href="' . $href . '">' . $row['subject'] . '</a>',
1751
				'counter' => ++$counter,
1752
				'can_see_ip' => allowedTo('moderate_forum') || ($row['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
1753
				'quickbuttons' => array(
1754
					'reply_to_all' => array(
1755
						'label' => $txt['reply_to_all'],
1756
						'href' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $row['id_pm'] . ($row['id_member_from'] != $user_info['id'] ? ';quote' : '') . ';u=all',
1757
						'icon' => 'reply_all_button',
1758
						'show' => $context['can_send_pm'] && !$memberContext[$row['id_member_from']]['is_guest'] && (count($recipients[$row['id_pm']]['to']) > 1 || $row['id_member_from'] == $user_info['id']),
1759
					),
1760
					'reply' => array(
1761
						'label' => $txt['reply'],
1762
						'href' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $row['id_pm'] . ';u=' . $row['id_member_from'],
1763
						'icon' => 'reply_button',
1764
						'show' => $context['can_send_pm'] && !$memberContext[$row['id_member_from']]['is_guest'] && $row['id_member_from'] != $user_info['id'],
1765
					),
1766
					'quote' => array(
1767
						'label' => $txt['quote_action'],
1768
						'href' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $row['id_pm'] . ';quote' . (count($recipients[$row['id_pm']]['to']) > 1 || $row['id_member_from'] == $user_info['id'] ? ';u=all' : (!$memberContext[$row['id_member_from']]['is_guest'] ? ';u=' . $row['id_member_from'] : '')),
1769
						'icon' => 'quote',
1770
						'show' => $context['can_send_pm'],
1771
					),
1772
					'delete' => array(
1773
						'label' => $txt['delete'],
1774
						'href' => $scripturl . '?action=pm;sa=pmactions;pm_actions%5b' . $row['id_pm'] . '%5D=delete;f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'],
1775
						'javascript' => 'data-confirm="' . JavaScriptEscape($txt['remove_message_question']) . '"',
1776
						'class' => 'you_sure',
1777
						'icon' => 'remove_button',
1778
					),
1779
					'more' => array(
1780
						'report' => array(
1781
							'label' => $txt['pm_report_to_admin'],
1782
							'href' => $scripturl . '?action=pm;sa=report;l=' . $context['current_label_id'] . ';pmsg=' . $row['id_pm'],
1783
							'icon' => 'error',
1784
							'show' => !empty($modSettings['enableReportPM']),
1785
						),
1786
					),
1787
				)
1788
			);
1789
		}
1790
		$smcFunc['db_free_result']($request);
1791
	}
1792
1793
	call_integration_hook('integrate_search_pm_context');
1794
1795
	// Finish off the context.
1796
	$context['page_title'] = $txt['pm_search_title'];
1797
	$context['sub_template'] = 'search_results';
1798
	$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search';
1799
	$context['linktree'][] = array(
1800
		'url' => $scripturl . '?action=pm;sa=search',
1801
		'name' => $txt['pm_search_bar_title'],
1802
	);
1803
}
1804
1805
/**
1806
 * Send a new message?
1807
 */
1808
function MessagePost()
1809
{
1810
	global $txt, $sourcedir, $scripturl, $modSettings;
1811
	global $context, $smcFunc, $language, $user_info;
1812
1813
	isAllowedTo('pm_send');
1814
1815
	loadLanguage('PersonalMessage');
1816
	// Just in case it was loaded from somewhere else.
1817
	loadTemplate('PersonalMessage');
1818
	loadJavaScriptFile('PersonalMessage.js', array('defer' => false, 'minimize' => true), 'smf_pms');
1819
	loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
1820
	$context['sub_template'] = 'send';
1821
1822
	// Extract out the spam settings - cause it's neat.
1823
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
1824
1825
	// Set the title...
1826
	$context['page_title'] = $txt['send_message'];
1827
1828
	$context['reply'] = isset($_REQUEST['pmsg']) || isset($_REQUEST['quote']);
1829
1830
	// Check whether we've gone over the limit of messages we can send per hour.
1831
	if (!empty($modSettings['pm_posts_per_hour']) && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) && $user_info['mod_cache']['bq'] == '0=1' && $user_info['mod_cache']['gq'] == '0=1')
1832
	{
1833
		// How many messages have they sent this last hour?
1834
		$request = $smcFunc['db_query']('', '
1835
			SELECT COUNT(pr.id_pm) AS post_count
1836
			FROM {db_prefix}personal_messages AS pm
1837
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
1838
			WHERE pm.id_member_from = {int:current_member}
1839
				AND pm.msgtime > {int:msgtime}',
1840
			array(
1841
				'current_member' => $user_info['id'],
1842
				'msgtime' => time() - 3600,
1843
			)
1844
		);
1845
		list ($postCount) = $smcFunc['db_fetch_row']($request);
1846
		$smcFunc['db_free_result']($request);
1847
1848
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
1849
			fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
1850
	}
1851
1852
	// Quoting/Replying to a message?
1853
	if (!empty($_REQUEST['pmsg']))
1854
	{
1855
		$pmsg = (int) $_REQUEST['pmsg'];
1856
1857
		// Make sure this is yours.
1858
		if (!isAccessiblePM($pmsg))
1859
			fatal_lang_error('no_access', false);
1860
1861
		// Work out whether this is one you've received?
1862
		$request = $smcFunc['db_query']('', '
1863
			SELECT
1864
				id_pm
1865
			FROM {db_prefix}pm_recipients
1866
			WHERE id_pm = {int:id_pm}
1867
				AND id_member = {int:current_member}
1868
			LIMIT 1',
1869
			array(
1870
				'current_member' => $user_info['id'],
1871
				'id_pm' => $pmsg,
1872
			)
1873
		);
1874
		$isReceived = $smcFunc['db_num_rows']($request) != 0;
1875
		$smcFunc['db_free_result']($request);
1876
1877
		// Get the quoted message (and make sure you're allowed to see this quote!).
1878
		$request = $smcFunc['db_query']('', '
1879
			SELECT
1880
				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,
1881
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
1882
				COALESCE(mem.real_name, pm.from_name) AS real_name
1883
			FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : '
1884
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . '
1885
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1886
			WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? '
1887
				AND pm.id_member_from = {int:current_member}' : '
1888
				AND pmr.id_member = {int:current_member}') . '
1889
			LIMIT 1',
1890
			array(
1891
				'current_member' => $user_info['id'],
1892
				'id_pm_head_empty' => 0,
1893
				'id_pm' => $pmsg,
1894
			)
1895
		);
1896
		if ($smcFunc['db_num_rows']($request) == 0)
1897
			fatal_lang_error('pm_not_yours', false);
1898
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
1899
		$smcFunc['db_free_result']($request);
1900
1901
		// Censor the message.
1902
		censorText($row_quoted['subject']);
1903
		censorText($row_quoted['body']);
1904
1905
		// Add 'Re: ' to it....
1906
		if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix')))
1907
		{
1908
			if ($language === $user_info['language'])
1909
				$context['response_prefix'] = $txt['response_prefix'];
1910
			else
1911
			{
1912
				loadLanguage('index', $language, false);
1913
				$context['response_prefix'] = $txt['response_prefix'];
1914
				loadLanguage('index');
1915
			}
1916
			cache_put_data('response_prefix', $context['response_prefix'], 600);
1917
		}
1918
		$form_subject = $row_quoted['subject'];
1919
		if ($context['reply'] && trim($context['response_prefix']) != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0)
1920
			$form_subject = $context['response_prefix'] . $form_subject;
1921
1922
		if (isset($_REQUEST['quote']))
1923
		{
1924
			// Remove any nested quotes and <br>...
1925
			$form_message = preg_replace('~<br ?/?' . '>~i', "\n", $row_quoted['body']);
1926
			if (!empty($modSettings['removeNestedQuotes']))
1927
				$form_message = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $form_message);
1928
			if (empty($row_quoted['id_member']))
1929
				$form_message = '[quote author=&quot;' . $row_quoted['real_name'] . '&quot;]' . "\n" . $form_message . "\n" . '[/quote]';
1930
			else
1931
				$form_message = '[quote author=' . $row_quoted['real_name'] . ' link=action=profile;u=' . $row_quoted['id_member'] . ' date=' . $row_quoted['msgtime'] . ']' . "\n" . $form_message . "\n" . '[/quote]';
1932
		}
1933
		else
1934
			$form_message = '';
1935
1936
		// Do the BBC thang on the message.
1937
		$row_quoted['body'] = parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']);
1938
1939
		// Set up the quoted message array.
1940
		$context['quoted_message'] = array(
1941
			'id' => $row_quoted['id_pm'],
1942
			'pm_head' => $row_quoted['pm_head'],
1943
			'member' => array(
1944
				'name' => $row_quoted['real_name'],
1945
				'username' => $row_quoted['member_name'],
1946
				'id' => $row_quoted['id_member'],
1947
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
1948
				'link' => !empty($row_quoted['id_member']) ? '<a href="' . $scripturl . '?action=profile;u=' . $row_quoted['id_member'] . '">' . $row_quoted['real_name'] . '</a>' : $row_quoted['real_name'],
1949
			),
1950
			'subject' => $row_quoted['subject'],
1951
			'time' => timeformat($row_quoted['msgtime']),
1952
			'timestamp' => forum_time(true, $row_quoted['msgtime']),
1953
			'body' => $row_quoted['body']
1954
		);
1955
	}
1956
	else
1957
	{
1958
		$context['quoted_message'] = false;
1959
		$form_subject = '';
1960
		$form_message = '';
1961
	}
1962
1963
	$context['recipients'] = array(
1964
		'to' => array(),
1965
		'bcc' => array(),
1966
	);
1967
1968
	// Sending by ID?  Replying to all?  Fetch the real_name(s).
1969
	if (isset($_REQUEST['u']))
1970
	{
1971
		// If the user is replying to all, get all the other members this was sent to..
1972
		if ($_REQUEST['u'] == 'all' && isset($row_quoted))
1973
		{
1974
			// Firstly, to reply to all we clearly already have $row_quoted - so have the original member from.
1975
			if ($row_quoted['id_member'] != $user_info['id'])
1976
				$context['recipients']['to'][] = array(
1977
					'id' => $row_quoted['id_member'],
1978
					'name' => $smcFunc['htmlspecialchars']($row_quoted['real_name']),
1979
				);
1980
1981
			// Now to get the others.
1982
			$request = $smcFunc['db_query']('', '
1983
				SELECT mem.id_member, mem.real_name
1984
				FROM {db_prefix}pm_recipients AS pmr
1985
					INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
1986
				WHERE pmr.id_pm = {int:id_pm}
1987
					AND pmr.id_member != {int:current_member}
1988
					AND pmr.bcc = {int:not_bcc}',
1989
				array(
1990
					'current_member' => $user_info['id'],
1991
					'id_pm' => $pmsg,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pmsg does not seem to be defined for all execution paths leading up to this point.
Loading history...
1992
					'not_bcc' => 0,
1993
				)
1994
			);
1995
			while ($row = $smcFunc['db_fetch_assoc']($request))
1996
				$context['recipients']['to'][] = array(
1997
					'id' => $row['id_member'],
1998
					'name' => $row['real_name'],
1999
				);
2000
			$smcFunc['db_free_result']($request);
2001
		}
2002
		else
2003
		{
2004
			$_REQUEST['u'] = explode(',', $_REQUEST['u']);
2005
			foreach ($_REQUEST['u'] as $key => $uID)
2006
				$_REQUEST['u'][$key] = (int) $uID;
2007
2008
			$_REQUEST['u'] = array_unique($_REQUEST['u']);
2009
2010
			$request = $smcFunc['db_query']('', '
2011
				SELECT id_member, real_name
2012
				FROM {db_prefix}members
2013
				WHERE id_member IN ({array_int:member_list})
2014
				LIMIT {int:limit}',
2015
				array(
2016
					'member_list' => $_REQUEST['u'],
2017
					'limit' => count($_REQUEST['u']),
2018
				)
2019
			);
2020
			while ($row = $smcFunc['db_fetch_assoc']($request))
2021
				$context['recipients']['to'][] = array(
2022
					'id' => $row['id_member'],
2023
					'name' => $row['real_name'],
2024
				);
2025
			$smcFunc['db_free_result']($request);
2026
		}
2027
2028
		// Get a literal name list in case the user has JavaScript disabled.
2029
		$names = array();
2030
		foreach ($context['recipients']['to'] as $to)
2031
			$names[] = $to['name'];
2032
		$context['to_value'] = empty($names) ? '' : '&quot;' . implode('&quot;, &quot;', $names) . '&quot;';
2033
	}
2034
	else
2035
		$context['to_value'] = '';
2036
2037
	// Set the defaults...
2038
	$context['subject'] = $form_subject;
2039
	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
2040
	$context['post_error'] = array();
2041
2042
	// And build the link tree.
2043
	$context['linktree'][] = array(
2044
		'url' => $scripturl . '?action=pm;sa=send',
2045
		'name' => $txt['new_message']
2046
	);
2047
2048
	// Generate a list of drafts that they can load in to the editor
2049
	if (!empty($context['drafts_pm_save']))
2050
	{
2051
		require_once($sourcedir . '/Drafts.php');
2052
		$pm_seed = isset($_REQUEST['pmsg']) ? $_REQUEST['pmsg'] : (isset($_REQUEST['quote']) ? $_REQUEST['quote'] : 0);
2053
		ShowDrafts($user_info['id'], $pm_seed, 1);
2054
	}
2055
2056
	// Needed for the WYSIWYG editor.
2057
	require_once($sourcedir . '/Subs-Editor.php');
2058
2059
	// Now create the editor.
2060
	$editorOptions = array(
2061
		'id' => 'message',
2062
		'value' => $context['message'],
2063
		'height' => '175px',
2064
		'width' => '100%',
2065
		'labels' => array(
2066
			'post_button' => $txt['send_message'],
2067
		),
2068
		'preview_type' => 2,
2069
		'required' => true,
2070
	);
2071
	create_control_richedit($editorOptions);
2072
2073
	// Store the ID for old compatibility.
2074
	$context['post_box_name'] = $editorOptions['id'];
2075
2076
	$context['bcc_value'] = '';
2077
2078
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2079
	if ($context['require_verification'])
2080
	{
2081
		$verificationOptions = array(
2082
			'id' => 'pm',
2083
		);
2084
		$context['require_verification'] = create_control_verification($verificationOptions);
2085
		$context['visual_verification_id'] = $verificationOptions['id'];
2086
	}
2087
2088
	call_integration_hook('integrate_pm_post');
2089
2090
	// Register this form and get a sequence number in $context.
2091
	checkSubmitOnce('register');
2092
}
2093
2094
/**
2095
 * This function allows the user to view their PM drafts
2096
 */
2097
function MessageDrafts()
2098
{
2099
	global $sourcedir, $user_info;
2100
2101
	// validate with loadMemberData()
2102
	$memberResult = loadMemberData($user_info['id'], false);
2103
	if (!$memberResult)
0 ignored issues
show
Bug Best Practice introduced by
The expression $memberResult of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2104
		fatal_lang_error('not_a_user', false);
2105
	list ($memID) = $memberResult;
2106
2107
	// drafts is where the functions reside
2108
	require_once($sourcedir . '/Drafts.php');
2109
	showPMDrafts($memID);
2110
}
2111
2112
/**
2113
 * An error in the message...
2114
 *
2115
 * @param array $error_types An array of strings indicating which type of errors occurred
2116
 * @param array $named_recipients
2117
 * @param $recipient_ids
2118
 */
2119
function messagePostError($error_types, $named_recipients, $recipient_ids = array())
2120
{
2121
	global $txt, $context, $scripturl, $modSettings;
2122
	global $smcFunc, $user_info, $sourcedir;
2123
2124
	if (!isset($_REQUEST['xml']))
2125
	{
2126
		$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send';
2127
		$context['sub_template'] = 'send';
2128
		loadJavaScriptFile('PersonalMessage.js', array('defer' => false, 'minimize' => true), 'smf_pms');
2129
		loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
2130
	}
2131
	else
2132
		$context['sub_template'] = 'pm';
2133
2134
	$context['page_title'] = $txt['send_message'];
2135
2136
	// Got some known members?
2137
	$context['recipients'] = array(
2138
		'to' => array(),
2139
		'bcc' => array(),
2140
	);
2141
	if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
2142
	{
2143
		$allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
2144
2145
		$request = $smcFunc['db_query']('', '
2146
			SELECT id_member, real_name
2147
			FROM {db_prefix}members
2148
			WHERE id_member IN ({array_int:member_list})',
2149
			array(
2150
				'member_list' => $allRecipients,
2151
			)
2152
		);
2153
		while ($row = $smcFunc['db_fetch_assoc']($request))
2154
		{
2155
			$recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
2156
			$context['recipients'][$recipientType][] = array(
2157
				'id' => $row['id_member'],
2158
				'name' => $row['real_name'],
2159
			);
2160
		}
2161
		$smcFunc['db_free_result']($request);
2162
	}
2163
2164
	// Set everything up like before....
2165
	$context['subject'] = isset($_REQUEST['subject']) ? $smcFunc['htmlspecialchars']($_REQUEST['subject']) : '';
2166
	$context['message'] = isset($_REQUEST['message']) ? str_replace(array('  '), array('&nbsp; '), $smcFunc['htmlspecialchars']($_REQUEST['message'])) : '';
2167
	$context['reply'] = !empty($_REQUEST['replied_to']);
2168
2169
	if ($context['reply'])
2170
	{
2171
		$_REQUEST['replied_to'] = (int) $_REQUEST['replied_to'];
2172
2173
		$request = $smcFunc['db_query']('', '
2174
			SELECT
2175
				pm.id_pm, CASE WHEN pm.id_pm_head = {int:no_id_pm_head} THEN pm.id_pm ELSE pm.id_pm_head END AS pm_head,
2176
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
2177
				COALESCE(mem.real_name, pm.from_name) AS real_name
2178
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' : '
2179
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:replied_to})') . '
2180
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
2181
			WHERE pm.id_pm = {int:replied_to}' . ($context['folder'] == 'sent' ? '
2182
				AND pm.id_member_from = {int:current_member}' : '
2183
				AND pmr.id_member = {int:current_member}') . '
2184
			LIMIT 1',
2185
			array(
2186
				'current_member' => $user_info['id'],
2187
				'no_id_pm_head' => 0,
2188
				'replied_to' => $_REQUEST['replied_to'],
2189
			)
2190
		);
2191
		if ($smcFunc['db_num_rows']($request) == 0)
2192
		{
2193
			if (!isset($_REQUEST['xml']))
2194
				fatal_lang_error('pm_not_yours', false);
2195
			else
2196
				$error_types[] = 'pm_not_yours';
2197
		}
2198
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
2199
		$smcFunc['db_free_result']($request);
2200
2201
		censorText($row_quoted['subject']);
2202
		censorText($row_quoted['body']);
2203
2204
		$context['quoted_message'] = array(
2205
			'id' => $row_quoted['id_pm'],
2206
			'pm_head' => $row_quoted['pm_head'],
2207
			'member' => array(
2208
				'name' => $row_quoted['real_name'],
2209
				'username' => $row_quoted['member_name'],
2210
				'id' => $row_quoted['id_member'],
2211
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
2212
				'link' => !empty($row_quoted['id_member']) ? '<a href="' . $scripturl . '?action=profile;u=' . $row_quoted['id_member'] . '">' . $row_quoted['real_name'] . '</a>' : $row_quoted['real_name'],
2213
			),
2214
			'subject' => $row_quoted['subject'],
2215
			'time' => timeformat($row_quoted['msgtime']),
2216
			'timestamp' => forum_time(true, $row_quoted['msgtime']),
2217
			'body' => parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']),
2218
		);
2219
	}
2220
2221
	// Build the link tree....
2222
	$context['linktree'][] = array(
2223
		'url' => $scripturl . '?action=pm;sa=send',
2224
		'name' => $txt['new_message']
2225
	);
2226
2227
	// Set each of the errors for the template.
2228
	loadLanguage('Errors');
2229
2230
	$context['error_type'] = 'minor';
2231
2232
	$context['post_error'] = array(
2233
		'messages' => array(),
2234
		// @todo error handling: maybe fatal errors can be error_type => serious
2235
		'error_type' => '',
2236
	);
2237
2238
	foreach ($error_types as $error_type)
2239
	{
2240
		$context['post_error'][$error_type] = true;
2241
		if (isset($txt['error_' . $error_type]))
2242
		{
2243
			if ($error_type == 'long_message')
2244
				$txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']);
2245
2246
			$context['post_error']['messages'][] = $txt['error_' . $error_type];
2247
		}
2248
2249
		// If it's not a minor error flag it as such.
2250
		if (!in_array($error_type, array('new_reply', 'not_approved', 'new_replies', 'old_topic', 'need_qr_verification', 'no_subject')))
2251
			$context['error_type'] = 'serious';
2252
	}
2253
2254
	// We need to load the editor once more.
2255
	require_once($sourcedir . '/Subs-Editor.php');
2256
2257
	// Create it...
2258
	$editorOptions = array(
2259
		'id' => 'message',
2260
		'value' => $context['message'],
2261
		'width' => '90%',
2262
		'height' => '175px',
2263
		'labels' => array(
2264
			'post_button' => $txt['send_message'],
2265
		),
2266
		'preview_type' => 2,
2267
	);
2268
	create_control_richedit($editorOptions);
2269
2270
	// ... and store the ID again...
2271
	$context['post_box_name'] = $editorOptions['id'];
2272
2273
	// Check whether we need to show the code again.
2274
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2275
	if ($context['require_verification'] && !isset($_REQUEST['xml']))
2276
	{
2277
		require_once($sourcedir . '/Subs-Editor.php');
2278
		$verificationOptions = array(
2279
			'id' => 'pm',
2280
		);
2281
		$context['require_verification'] = create_control_verification($verificationOptions);
2282
		$context['visual_verification_id'] = $verificationOptions['id'];
2283
	}
2284
2285
	$context['to_value'] = empty($named_recipients['to']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['to']) . '&quot;';
2286
	$context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['bcc']) . '&quot;';
2287
2288
	call_integration_hook('integrate_pm_error');
2289
2290
	// No check for the previous submission is needed.
2291
	checkSubmitOnce('free');
2292
2293
	// Acquire a new form sequence number.
2294
	checkSubmitOnce('register');
2295
}
2296
2297
/**
2298
 * Send it!
2299
 */
2300
function MessagePost2()
2301
{
2302
	global $txt, $context, $sourcedir;
2303
	global $user_info, $modSettings, $smcFunc;
2304
2305
	isAllowedTo('pm_send');
2306
	require_once($sourcedir . '/Subs-Auth.php');
2307
2308
	// PM Drafts enabled and needed?
2309
	if ($context['drafts_pm_save'] && (isset($_POST['save_draft']) || isset($_POST['id_pm_draft'])))
2310
	{
2311
		$context['id_pm_draft'] = !empty($_POST['id_pm_draft']) ? (int) $_POST['id_pm_draft'] : 0;
2312
		require_once($sourcedir . '/Drafts.php');
2313
	}
2314
2315
	loadLanguage('PersonalMessage', '', false);
2316
2317
	// Extract out the spam settings - it saves database space!
2318
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
2319
2320
	// Initialize the errors we're about to make.
2321
	$post_errors = array();
2322
2323
	// Check whether we've gone over the limit of messages we can send per hour - fatal error if fails!
2324
	if (!empty($modSettings['pm_posts_per_hour']) && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) && $user_info['mod_cache']['bq'] == '0=1' && $user_info['mod_cache']['gq'] == '0=1')
2325
	{
2326
		// How many have they sent this last hour?
2327
		$request = $smcFunc['db_query']('', '
2328
			SELECT COUNT(pr.id_pm) AS post_count
2329
			FROM {db_prefix}personal_messages AS pm
2330
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
2331
			WHERE pm.id_member_from = {int:current_member}
2332
				AND pm.msgtime > {int:msgtime}',
2333
			array(
2334
				'current_member' => $user_info['id'],
2335
				'msgtime' => time() - 3600,
2336
			)
2337
		);
2338
		list ($postCount) = $smcFunc['db_fetch_row']($request);
2339
		$smcFunc['db_free_result']($request);
2340
2341
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
2342
		{
2343
			if (!isset($_REQUEST['xml']))
2344
				fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
2345
			else
2346
				$post_errors[] = 'pm_too_many_per_hour';
2347
		}
2348
	}
2349
2350
	// If your session timed out, show an error, but do allow to re-submit.
2351
	if (!isset($_REQUEST['xml']) && checkSession('post', '', false) != '')
2352
		$post_errors[] = 'session_timeout';
2353
2354
	$_REQUEST['subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : '';
2355
	$_REQUEST['to'] = empty($_POST['to']) ? (empty($_GET['to']) ? '' : $_GET['to']) : $_POST['to'];
2356
	$_REQUEST['bcc'] = empty($_POST['bcc']) ? (empty($_GET['bcc']) ? '' : $_GET['bcc']) : $_POST['bcc'];
2357
2358
	// Route the input from the 'u' parameter to the 'to'-list.
2359
	if (!empty($_POST['u']))
2360
		$_POST['recipient_to'] = explode(',', $_POST['u']);
2361
2362
	// Construct the list of recipients.
2363
	$recipientList = array();
2364
	$namedRecipientList = array();
2365
	$namesNotFound = array();
2366
	foreach (array('to', 'bcc') as $recipientType)
2367
	{
2368
		// First, let's see if there's user ID's given.
2369
		$recipientList[$recipientType] = array();
2370
		if (!empty($_POST['recipient_' . $recipientType]) && is_array($_POST['recipient_' . $recipientType]))
2371
		{
2372
			foreach ($_POST['recipient_' . $recipientType] as $recipient)
2373
				$recipientList[$recipientType][] = (int) $recipient;
2374
		}
2375
2376
		// Are there also literal names set?
2377
		if (!empty($_REQUEST[$recipientType]))
2378
		{
2379
			// We're going to take out the "s anyway ;).
2380
			$recipientString = strtr($_REQUEST[$recipientType], array('\\"' => '"'));
2381
2382
			preg_match_all('~"([^"]+)"~', $recipientString, $matches);
2383
			$namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))));
2384
2385
			foreach ($namedRecipientList[$recipientType] as $index => $recipient)
2386
			{
2387
				if (strlen(trim($recipient)) > 0)
2388
					$namedRecipientList[$recipientType][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($recipient)));
2389
				else
2390
					unset($namedRecipientList[$recipientType][$index]);
2391
			}
2392
2393
			if (!empty($namedRecipientList[$recipientType]))
2394
			{
2395
				$foundMembers = findMembers($namedRecipientList[$recipientType]);
2396
2397
				// Assume all are not found, until proven otherwise.
2398
				$namesNotFound[$recipientType] = $namedRecipientList[$recipientType];
2399
2400
				foreach ($foundMembers as $member)
2401
				{
2402
					$testNames = array(
2403
						$smcFunc['strtolower']($member['username']),
2404
						$smcFunc['strtolower']($member['name']),
2405
						$smcFunc['strtolower']($member['email']),
2406
					);
2407
2408
					if (count(array_intersect($testNames, $namedRecipientList[$recipientType])) !== 0)
2409
					{
2410
						$recipientList[$recipientType][] = $member['id'];
2411
2412
						// Get rid of this username, since we found it.
2413
						$namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames);
2414
					}
2415
				}
2416
			}
2417
		}
2418
2419
		// Selected a recipient to be deleted? Remove them now.
2420
		if (!empty($_POST['delete_recipient']))
2421
			$recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $_POST['delete_recipient']));
2422
2423
		// Make sure we don't include the same name twice
2424
		$recipientList[$recipientType] = array_unique($recipientList[$recipientType]);
2425
	}
2426
2427
	// Are we changing the recipients some how?
2428
	$is_recipient_change = !empty($_POST['delete_recipient']) || !empty($_POST['to_submit']) || !empty($_POST['bcc_submit']);
2429
2430
	// Check if there's at least one recipient.
2431
	if (empty($recipientList['to']) && empty($recipientList['bcc']))
2432
		$post_errors[] = 'no_to';
2433
2434
	// Make sure that we remove the members who did get it from the screen.
2435
	if (!$is_recipient_change)
2436
	{
2437
		foreach ($recipientList as $recipientType => $dummy)
2438
		{
2439
			if (!empty($namesNotFound[$recipientType]))
2440
			{
2441
				$post_errors[] = 'bad_' . $recipientType;
2442
2443
				// Since we already have a post error, remove the previous one.
2444
				$post_errors = array_diff($post_errors, array('no_to'));
2445
2446
				foreach ($namesNotFound[$recipientType] as $name)
2447
					$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2448
			}
2449
		}
2450
	}
2451
2452
	// Did they make any mistakes?
2453
	if ($_REQUEST['subject'] == '')
2454
		$post_errors[] = 'no_subject';
2455
	if (!isset($_REQUEST['message']) || $_REQUEST['message'] == '')
2456
		$post_errors[] = 'no_message';
2457
	elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_REQUEST['message']) > $modSettings['max_messageLength'])
2458
		$post_errors[] = 'long_message';
2459
	else
2460
	{
2461
		// Preparse the message.
2462
		$message = $_REQUEST['message'];
2463
		preparsecode($message);
2464
2465
		// Make sure there's still some content left without the tags.
2466
		if ($smcFunc['htmltrim'](strip_tags(parse_bbc($smcFunc['htmlspecialchars']($message, ENT_QUOTES), false), '<img>')) === '' && (!allowedTo('bbc_html') || strpos($message, '[html]') === false))
2467
			$post_errors[] = 'no_message';
2468
	}
2469
2470
	// Wrong verification code?
2471
	if (!$user_info['is_admin'] && !isset($_REQUEST['xml']) && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'])
2472
	{
2473
		require_once($sourcedir . '/Subs-Editor.php');
2474
		$verificationOptions = array(
2475
			'id' => 'pm',
2476
		);
2477
		$context['require_verification'] = create_control_verification($verificationOptions, true);
2478
2479
		if (is_array($context['require_verification']))
2480
			$post_errors = array_merge($post_errors, $context['require_verification']);
2481
	}
2482
2483
	// If they did, give a chance to make ammends.
2484
	if (!empty($post_errors) && !$is_recipient_change && !isset($_REQUEST['preview']) && !isset($_REQUEST['xml']))
2485
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
0 ignored issues
show
Are you sure the usage of messagePostError($post_e...ntList, $recipientList) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
2486
2487
	// Want to take a second glance before you send?
2488
	if (isset($_REQUEST['preview']))
2489
	{
2490
		// Set everything up to be displayed.
2491
		$context['preview_subject'] = $smcFunc['htmlspecialchars']($_REQUEST['subject']);
2492
		$context['preview_message'] = $smcFunc['htmlspecialchars']($_REQUEST['message'], ENT_QUOTES);
2493
		preparsecode($context['preview_message'], true);
2494
2495
		// Parse out the BBC if it is enabled.
2496
		$context['preview_message'] = parse_bbc($context['preview_message']);
2497
2498
		// Censor, as always.
2499
		censorText($context['preview_subject']);
2500
		censorText($context['preview_message']);
2501
2502
		// Set a descriptive title.
2503
		$context['page_title'] = $txt['preview'] . ' - ' . $context['preview_subject'];
2504
2505
		// Pretend they messed up but don't ignore if they really did :P.
2506
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
0 ignored issues
show
Are you sure the usage of messagePostError($post_e...ntList, $recipientList) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
2507
	}
2508
2509
	// Adding a recipient cause javascript ain't working?
2510
	elseif ($is_recipient_change)
2511
	{
2512
		// Maybe we couldn't find one?
2513
		foreach ($namesNotFound as $recipientType => $names)
2514
		{
2515
			$post_errors[] = 'bad_' . $recipientType;
2516
			foreach ($names as $name)
2517
				$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2518
		}
2519
2520
		return messagePostError(array(), $namedRecipientList, $recipientList);
0 ignored issues
show
Are you sure the usage of messagePostError(array()...ntList, $recipientList) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
2521
	}
2522
2523
	// Want to save this as a draft and think about it some more?
2524
	if ($context['drafts_pm_save'] && isset($_POST['save_draft']))
2525
	{
2526
		SavePMDraft($post_errors, $recipientList);
2527
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
0 ignored issues
show
Are you sure the usage of messagePostError($post_e...ntList, $recipientList) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
2528
	}
2529
2530
	// Before we send the PM, let's make sure we don't have an abuse of numbers.
2531
	elseif (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum')))
2532
	{
2533
		$context['send_log'] = array(
2534
			'sent' => array(),
2535
			'failed' => array(sprintf($txt['pm_too_many_recipients'], $modSettings['max_pm_recipients'])),
2536
		);
2537
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
0 ignored issues
show
Are you sure the usage of messagePostError($post_e...ntList, $recipientList) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
2538
	}
2539
2540
	// Protect from message spamming.
2541
	spamProtection('pm');
2542
2543
	// Prevent double submission of this form.
2544
	checkSubmitOnce('check');
2545
2546
	// Do the actual sending of the PM.
2547
	if (!empty($recipientList['to']) || !empty($recipientList['bcc']))
2548
		$context['send_log'] = sendpm($recipientList, $_REQUEST['subject'], $_REQUEST['message'], true, null, !empty($_REQUEST['pm_head']) ? (int) $_REQUEST['pm_head'] : 0);
2549
	else
2550
		$context['send_log'] = array(
2551
			'sent' => array(),
2552
			'failed' => array()
2553
		);
2554
2555
	// Mark the message as "replied to".
2556
	if (!empty($context['send_log']['sent']) && !empty($_REQUEST['replied_to']) && isset($_REQUEST['f']) && $_REQUEST['f'] == 'inbox')
2557
	{
2558
		$smcFunc['db_query']('', '
2559
			UPDATE {db_prefix}pm_recipients
2560
			SET is_read = is_read | 2
2561
			WHERE id_pm = {int:replied_to}
2562
				AND id_member = {int:current_member}',
2563
			array(
2564
				'current_member' => $user_info['id'],
2565
				'replied_to' => (int) $_REQUEST['replied_to'],
2566
			)
2567
		);
2568
	}
2569
2570
	// If one or more of the recipient were invalid, go back to the post screen with the failed usernames.
2571
	if (!empty($context['send_log']['failed']))
2572
		return messagePostError($post_errors, $namesNotFound, array(
0 ignored issues
show
Are you sure the usage of messagePostError($post_e...send_log']['failed']))) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
2573
			'to' => array_intersect($recipientList['to'], $context['send_log']['failed']),
2574
			'bcc' => array_intersect($recipientList['bcc'], $context['send_log']['failed'])
2575
		));
2576
2577
	// Message sent successfully?
2578
	if (!empty($context['send_log']) && empty($context['send_log']['failed']))
2579
	{
2580
		$context['current_label_redirect'] = $context['current_label_redirect'] . ';done=sent';
2581
2582
		// If we had a PM draft for this one, then its time to remove it since it was just sent
2583
		if ($context['drafts_pm_save'] && !empty($_POST['id_pm_draft']))
2584
			DeleteDraft($_POST['id_pm_draft']);
2585
	}
2586
2587
	// Go back to the where they sent from, if possible...
2588
	redirectexit($context['current_label_redirect']);
2589
}
2590
2591
/**
2592
 * This function performs all additional stuff...
2593
 */
2594
function MessageActionsApply()
2595
{
2596
	global $context, $user_info, $options, $smcFunc;
2597
2598
	checkSession('request');
2599
2600
	if (isset($_REQUEST['del_selected']))
2601
		$_REQUEST['pm_action'] = 'delete';
2602
2603
	if (isset($_REQUEST['pm_action']) && $_REQUEST['pm_action'] != '' && !empty($_REQUEST['pms']) && is_array($_REQUEST['pms']))
2604
	{
2605
		foreach ($_REQUEST['pms'] as $pm)
2606
			$_REQUEST['pm_actions'][(int) $pm] = $_REQUEST['pm_action'];
2607
	}
2608
2609
	if (empty($_REQUEST['pm_actions']))
2610
		redirectexit($context['current_label_redirect']);
2611
2612
	// If we are in conversation, we may need to apply this to every message in the conversation.
2613
	if ($context['display_mode'] == 2 && isset($_REQUEST['conversation']))
2614
	{
2615
		$id_pms = array();
2616
		foreach ($_REQUEST['pm_actions'] as $pm => $dummy)
2617
			$id_pms[] = (int) $pm;
2618
2619
		$request = $smcFunc['db_query']('', '
2620
			SELECT id_pm_head, id_pm
2621
			FROM {db_prefix}personal_messages
2622
			WHERE id_pm IN ({array_int:id_pms})',
2623
			array(
2624
				'id_pms' => $id_pms,
2625
			)
2626
		);
2627
		$pm_heads = array();
2628
		while ($row = $smcFunc['db_fetch_assoc']($request))
2629
			$pm_heads[$row['id_pm_head']] = $row['id_pm'];
2630
		$smcFunc['db_free_result']($request);
2631
2632
		$request = $smcFunc['db_query']('', '
2633
			SELECT id_pm, id_pm_head
2634
			FROM {db_prefix}personal_messages
2635
			WHERE id_pm_head IN ({array_int:pm_heads})',
2636
			array(
2637
				'pm_heads' => array_keys($pm_heads),
2638
			)
2639
		);
2640
		// Copy the action from the single to PM to the others.
2641
		while ($row = $smcFunc['db_fetch_assoc']($request))
2642
		{
2643
			if (isset($pm_heads[$row['id_pm_head']]) && isset($_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]]))
2644
				$_REQUEST['pm_actions'][$row['id_pm']] = $_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]];
2645
		}
2646
		$smcFunc['db_free_result']($request);
2647
	}
2648
2649
	$to_delete = array();
2650
	$to_label = array();
2651
	$label_type = array();
2652
	$labels = array();
2653
	foreach ($_REQUEST['pm_actions'] as $pm => $action)
2654
	{
2655
		if ($action === 'delete')
2656
			$to_delete[] = (int) $pm;
2657
		else
2658
		{
2659
			if (substr($action, 0, 4) == 'add_')
2660
			{
2661
				$type = 'add';
2662
				$action = substr($action, 4);
2663
			}
2664
			elseif (substr($action, 0, 4) == 'rem_')
2665
			{
2666
				$type = 'rem';
2667
				$action = substr($action, 4);
2668
			}
2669
			else
2670
				$type = 'unk';
2671
2672
			if ($action == '-1' || (int) $action > 0)
2673
			{
2674
				$to_label[(int) $pm] = (int) $action;
2675
				$label_type[(int) $pm] = $type;
2676
			}
2677
		}
2678
	}
2679
2680
	// Deleting, it looks like?
2681
	if (!empty($to_delete))
2682
		deleteMessages($to_delete, $context['display_mode'] == 2 ? null : $context['folder']);
2683
2684
	// Are we labeling anything?
2685
	if (!empty($to_label) && $context['folder'] == 'inbox')
2686
	{
2687
		// Are we dealing with conversation view? If so, get all the messages in each conversation
2688
		if ($context['display_mode'] == 2)
2689
		{
2690
			$get_pms = $smcFunc['db_query']('', '
2691
				SELECT pm.id_pm_head, pm.id_pm
2692
				FROM {db_prefix}personal_messages AS pm
2693
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2694
				WHERE pm.id_pm_head IN ({array_int:head_pms})
2695
					AND pm.id_pm NOT IN ({array_int:head_pms})
2696
					AND pmr.id_member = {int:current_member}',
2697
				array(
2698
					'head_pms' => array_keys($to_label),
2699
					'current_member' => $user_info['id'],
2700
				)
2701
			);
2702
2703
			while ($other_pms = $smcFunc['db_fetch_assoc']($get_pms))
2704
			{
2705
				$to_label[$other_pms['id_pm']] = $to_label[$other_pms['id_pm_head']];
2706
			}
2707
2708
			$smcFunc['db_free_result']($get_pms);
2709
		}
2710
2711
		// Get information about each message...
2712
		$request = $smcFunc['db_query']('', '
2713
			SELECT id_pm, in_inbox
2714
			FROM {db_prefix}pm_recipients
2715
			WHERE id_member = {int:current_member}
2716
				AND id_pm IN ({array_int:to_label})
2717
			LIMIT ' . count($to_label),
2718
			array(
2719
				'current_member' => $user_info['id'],
2720
				'to_label' => array_keys($to_label),
2721
			)
2722
		);
2723
2724
		while ($row = $smcFunc['db_fetch_assoc']($request))
2725
		{
2726
			// Get the labels as well, but only if we're not dealing with the inbox
2727
			if ($to_label[$row['id_pm']] != '-1')
2728
			{
2729
				// The JOIN here ensures we only get labels that this user has applied to this PM
2730
				$request2 = $smcFunc['db_query']('', '
2731
					SELECT l.id_label, pml.id_pm
2732
					FROM {db_prefix}pm_labels AS l
2733
						INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2734
					WHERE l.id_member = {int:current_member}
2735
						AND pml.id_pm = {int:current_pm}',
2736
					array(
2737
						'current_member' => $user_info['id'],
2738
						'current_pm' => $row['id_pm'],
2739
					)
2740
				);
2741
2742
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
2743
				{
2744
					$labels[$row2['id_label']] = $row2['id_label'];
2745
				}
2746
2747
				$smcFunc['db_free_result']($request2);
2748
			}
2749
			elseif ($type == 'rem')
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type seems to be defined by a foreach iteration on line 2653. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2750
			{
2751
				// If we're removing from the inbox, see if we have at least one other label.
2752
				// This query is faster than the one above
2753
				$request2 = $smcFunc['db_query']('', '
2754
					SELECT COUNT(l.id_label)
2755
					FROM {db_prefix}pm_labels AS l
2756
						INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2757
					WHERE l.id_member = {int:current_member}
2758
						AND pml.id_pm = {int:current_pm}',
2759
					array(
2760
						'current_member' => $user_info['id'],
2761
						'current_pm' => $row['id_pm'],
2762
					)
2763
				);
2764
2765
				// How many labels do you have?
2766
				list ($num_labels) = $smcFunc['db_fetch_assoc']($request2);
2767
2768
				if ($num_labels > 0)
2769
					$context['can_remove_inbox'] = true;
2770
2771
				$smcFunc['db_free_result']($request2);
2772
			}
2773
2774
			// Use this to determine what to do later on...
2775
			$original_labels = $labels;
2776
2777
			// Ignore inbox for now - we'll deal with it later
2778
			if ($to_label[$row['id_pm']] != '-1')
2779
			{
2780
				// If this label is in the list and we're not adding it, remove it
2781
				if (array_key_exists($to_label[$row['id_pm']], $labels) && $type !== 'add')
2782
					unset($labels[$to_label[$row['id_pm']]]);
2783
				elseif ($type !== 'rem')
2784
					$labels[$to_label[$row['id_pm']]] = $to_label[$row['id_pm']];
2785
			}
2786
2787
			// Removing all labels or just removing the inbox label
2788
			if ($type == 'rem' && empty($labels))
2789
				$in_inbox = (empty($context['can_remove_inbox']) ? 1 : 0);
2790
			// Adding new labels, but removing inbox and applying new ones
2791
			elseif ($type == 'add' && !empty($options['pm_remove_inbox_label']) && !empty($labels))
2792
				$in_inbox = 0;
2793
			// Just adding it to the inbox
2794
			else
2795
				$in_inbox = 1;
2796
2797
			// Are we adding it to or removing it from the inbox?
2798
			if ($in_inbox != $row['in_inbox'])
2799
			{
2800
				$smcFunc['db_query']('', '
2801
					UPDATE {db_prefix}pm_recipients
2802
					SET in_inbox = {int:in_inbox}
2803
					WHERE id_pm = {int:id_pm}
2804
						AND id_member = {int:current_member}',
2805
					array(
2806
						'current_member' => $user_info['id'],
2807
						'id_pm' => $row['id_pm'],
2808
						'in_inbox' => $in_inbox,
2809
					)
2810
				);
2811
			}
2812
2813
			// Which labels do we not want now?
2814
			$labels_to_remove = array_diff($original_labels, $labels);
2815
2816
			// Don't apply it if it's already applied
2817
			$labels_to_apply = array_diff($labels, $original_labels);
2818
2819
			// Remove labels
2820
			if (!empty($labels_to_remove))
2821
			{
2822
				$smcFunc['db_query']('', '
2823
					DELETE FROM {db_prefix}pm_labeled_messages
2824
					WHERE id_pm = {int:current_pm}
2825
						AND id_label IN ({array_int:labels_to_remove})',
2826
					array(
2827
						'current_pm' => $row['id_pm'],
2828
						'labels_to_remove' => $labels_to_remove,
2829
					)
2830
				);
2831
			}
2832
2833
			// Add new ones
2834
			if (!empty($labels_to_apply))
2835
			{
2836
				$inserts = array();
2837
				foreach ($labels_to_apply as $label)
2838
					$inserts[] = array($row['id_pm'], $label);
2839
2840
				$smcFunc['db_insert']('',
2841
					'{db_prefix}pm_labeled_messages',
2842
					array('id_pm' => 'int', 'id_label' => 'int'),
2843
					$inserts,
2844
					array()
2845
				);
2846
			}
2847
		}
2848
		$smcFunc['db_free_result']($request);
2849
	}
2850
2851
	// Back to the folder.
2852
	$_SESSION['pm_selected'] = array_keys($to_label);
2853
	redirectexit($context['current_label_redirect'] . (count($to_label) == 1 ? '#msg' . $_SESSION['pm_selected'][0] : ''), count($to_label) == 1 && isBrowser('ie'));
2854
}
2855
2856
/**
2857
 * Delete ALL the messages!
2858
 */
2859
function MessageKillAll()
2860
{
2861
	global $context;
2862
2863
	checkSession();
2864
2865
	deleteMessages(null, null);
2866
2867
	// Done... all gone.
2868
	redirectexit($context['current_label_redirect']);
2869
}
2870
2871
/**
2872
 * This function allows the user to delete all messages older than so many days.
2873
 */
2874
function MessagePrune()
2875
{
2876
	global $txt, $context, $user_info, $scripturl, $smcFunc;
2877
2878
	// Actually delete the messages.
2879
	if (isset($_REQUEST['age']))
2880
	{
2881
		checkSession();
2882
2883
		// Calculate the time to delete before.
2884
		$deleteTime = max(0, time() - (86400 * (int) $_REQUEST['age']));
2885
2886
		// Array to store the IDs in.
2887
		$toDelete = array();
2888
2889
		// Select all the messages they have sent older than $deleteTime.
2890
		$request = $smcFunc['db_query']('', '
2891
			SELECT id_pm
2892
			FROM {db_prefix}personal_messages
2893
			WHERE deleted_by_sender = {int:not_deleted}
2894
				AND id_member_from = {int:current_member}
2895
				AND msgtime < {int:msgtime}',
2896
			array(
2897
				'current_member' => $user_info['id'],
2898
				'not_deleted' => 0,
2899
				'msgtime' => $deleteTime,
2900
			)
2901
		);
2902
		while ($row = $smcFunc['db_fetch_row']($request))
2903
			$toDelete[] = $row[0];
2904
		$smcFunc['db_free_result']($request);
2905
2906
		// Select all messages in their inbox older than $deleteTime.
2907
		$request = $smcFunc['db_query']('', '
2908
			SELECT pmr.id_pm
2909
			FROM {db_prefix}pm_recipients AS pmr
2910
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
2911
			WHERE pmr.deleted = {int:not_deleted}
2912
				AND pmr.id_member = {int:current_member}
2913
				AND pm.msgtime < {int:msgtime}',
2914
			array(
2915
				'current_member' => $user_info['id'],
2916
				'not_deleted' => 0,
2917
				'msgtime' => $deleteTime,
2918
			)
2919
		);
2920
		while ($row = $smcFunc['db_fetch_assoc']($request))
2921
			$toDelete[] = $row['id_pm'];
2922
		$smcFunc['db_free_result']($request);
2923
2924
		// Delete the actual messages.
2925
		deleteMessages($toDelete);
2926
2927
		// Go back to their inbox.
2928
		redirectexit($context['current_label_redirect']);
2929
	}
2930
2931
	// Build the link tree elements.
2932
	$context['linktree'][] = array(
2933
		'url' => $scripturl . '?action=pm;sa=prune',
2934
		'name' => $txt['pm_prune']
2935
	);
2936
2937
	$context['sub_template'] = 'prune';
2938
	$context['page_title'] = $txt['pm_prune'];
2939
}
2940
2941
/**
2942
 * Delete the specified personal messages.
2943
 *
2944
 * @param array|null $personal_messages An array containing the IDs of PMs to delete or null to delete all of them
2945
 * @param string|null $folder Which "folder" to delete PMs from - 'sent' to delete them from the outbox, null or anything else to delete from the inbox
2946
 * @param array|int|null $owner An array of IDs of users whose PMs are being deleted, the ID of a single user or null to use the current user's ID
2947
 */
2948
function deleteMessages($personal_messages, $folder = null, $owner = null)
2949
{
2950
	global $user_info, $smcFunc;
2951
2952
	if ($owner === null)
2953
		$owner = array($user_info['id']);
2954
	elseif (empty($owner))
2955
		return;
2956
	elseif (!is_array($owner))
2957
		$owner = array($owner);
2958
2959
	if ($personal_messages !== null)
2960
	{
2961
		if (empty($personal_messages) || !is_array($personal_messages))
2962
			return;
2963
2964
		foreach ($personal_messages as $index => $delete_id)
2965
			$personal_messages[$index] = (int) $delete_id;
2966
2967
		$where = '
2968
				AND id_pm IN ({array_int:pm_list})';
2969
	}
2970
	else
2971
		$where = '';
2972
2973
	if ($folder == 'sent' || $folder === null)
2974
	{
2975
		$smcFunc['db_query']('', '
2976
			UPDATE {db_prefix}personal_messages
2977
			SET deleted_by_sender = {int:is_deleted}
2978
			WHERE id_member_from IN ({array_int:member_list})
2979
				AND deleted_by_sender = {int:not_deleted}' . $where,
2980
			array(
2981
				'member_list' => $owner,
2982
				'is_deleted' => 1,
2983
				'not_deleted' => 0,
2984
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2985
			)
2986
		);
2987
	}
2988
	if ($folder != 'sent' || $folder === null)
2989
	{
2990
		// Calculate the number of messages each member's gonna lose...
2991
		$request = $smcFunc['db_query']('', '
2992
			SELECT id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read
2993
			FROM {db_prefix}pm_recipients
2994
			WHERE id_member IN ({array_int:member_list})
2995
				AND deleted = {int:not_deleted}' . $where . '
2996
			GROUP BY id_member, is_read',
2997
			array(
2998
				'member_list' => $owner,
2999
				'not_deleted' => 0,
3000
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3001
			)
3002
		);
3003
		// ...And update the statistics accordingly - now including unread messages!.
3004
		while ($row = $smcFunc['db_fetch_assoc']($request))
3005
		{
3006
			if ($row['is_read'])
3007
				updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages']));
3008
			else
3009
				updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages'], 'unread_messages' => $where == '' ? 0 : 'unread_messages - ' . $row['num_deleted_messages']));
3010
3011
			// If this is the current member we need to make their message count correct.
3012
			if ($user_info['id'] == $row['id_member'])
3013
			{
3014
				$user_info['messages'] -= $row['num_deleted_messages'];
3015
				if (!($row['is_read']))
3016
					$user_info['unread_messages'] -= $row['num_deleted_messages'];
3017
			}
3018
		}
3019
		$smcFunc['db_free_result']($request);
3020
3021
		// Do the actual deletion.
3022
		$smcFunc['db_query']('', '
3023
			UPDATE {db_prefix}pm_recipients
3024
			SET deleted = {int:is_deleted}
3025
			WHERE id_member IN ({array_int:member_list})
3026
				AND deleted = {int:not_deleted}' . $where,
3027
			array(
3028
				'member_list' => $owner,
3029
				'is_deleted' => 1,
3030
				'not_deleted' => 0,
3031
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3032
			)
3033
		);
3034
3035
		$labels = array();
3036
3037
		// Get any labels that the owner may have applied to this PM
3038
		// The join is here to ensure we only get labels applied by the specified member(s)
3039
		$get_labels = $smcFunc['db_query']('', '
3040
			SELECT pml.id_label
3041
			FROM {db_prefix}pm_labels AS l
3042
				INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
3043
			WHERE l.id_member IN ({array_int:member_list})' . $where,
3044
			array(
3045
				'member_list' => $owner,
3046
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3047
			)
3048
		);
3049
3050
		while ($row = $smcFunc['db_fetch_assoc']($get_labels))
3051
		{
3052
			$labels[] = $row['id_label'];
3053
		}
3054
3055
		$smcFunc['db_free_result']($get_labels);
3056
3057
		if (!empty($labels))
3058
		{
3059
			$smcFunc['db_query']('', '
3060
				DELETE FROM {db_prefix}pm_labeled_messages
3061
				WHERE id_label IN ({array_int:labels})' . $where,
3062
				array(
3063
					'labels' => $labels,
3064
					'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3065
				)
3066
			);
3067
		}
3068
	}
3069
3070
	// If sender and recipients all have deleted their message, it can be removed.
3071
	$request = $smcFunc['db_query']('', '
3072
		SELECT pm.id_pm AS sender, pmr.id_pm
3073
		FROM {db_prefix}personal_messages AS pm
3074
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted})
3075
		WHERE pm.deleted_by_sender = {int:is_deleted} AND pmr.id_pm is null
3076
			' . str_replace('id_pm', 'pm.id_pm', $where),
3077
		array(
3078
			'not_deleted' => 0,
3079
			'is_deleted' => 1,
3080
			'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3081
		)
3082
	);
3083
	$remove_pms = array();
3084
	while ($row = $smcFunc['db_fetch_assoc']($request))
3085
		$remove_pms[] = $row['sender'];
3086
	$smcFunc['db_free_result']($request);
3087
3088
	if (!empty($remove_pms))
3089
	{
3090
		$smcFunc['db_query']('', '
3091
			DELETE FROM {db_prefix}personal_messages
3092
			WHERE id_pm IN ({array_int:pm_list})',
3093
			array(
3094
				'pm_list' => $remove_pms,
3095
			)
3096
		);
3097
3098
		$smcFunc['db_query']('', '
3099
			DELETE FROM {db_prefix}pm_recipients
3100
			WHERE id_pm IN ({array_int:pm_list})',
3101
			array(
3102
				'pm_list' => $remove_pms,
3103
			)
3104
		);
3105
3106
		$smcFunc['db_query']('', '
3107
			DELETE FROM {db_prefix}pm_labeled_messages
3108
			WHERE id_pm IN ({array_int:pm_list})',
3109
			array(
3110
				'pm_list' => $remove_pms,
3111
			)
3112
		);
3113
	}
3114
3115
	// Any cached numbers may be wrong now.
3116
	cache_put_data('labelCounts:' . $user_info['id'], null, 720);
3117
}
3118
3119
/**
3120
 * Mark the specified personal messages read.
3121
 *
3122
 * @param array|null $personal_messages An array of PM IDs to mark or null to mark all
3123
 * @param int|null $label The ID of a label. If set, only messages with this label will be marked.
3124
 * @param int|null $owner If owner is set, marks messages owned by that member id
3125
 */
3126
function markMessages($personal_messages = null, $label = null, $owner = null)
3127
{
3128
	global $user_info, $context, $smcFunc;
3129
3130
	if ($owner === null)
3131
		$owner = $user_info['id'];
3132
3133
	$in_inbox = '';
3134
3135
	// Marking all messages with a specific label as read?
3136
	// If we know which PMs we're marking read, then we don't need label info
3137
	if ($personal_messages === null && $label !== null && $label != '-1')
3138
	{
3139
		$personal_messages = array();
3140
		$get_messages = $smcFunc['db_query']('', '
3141
			SELECT id_pm
3142
			FROM {db_prefix}pm_labeled_messages
3143
			WHERE id_label = {int:current_label}',
3144
			array(
3145
				'current_label' => $label,
3146
			)
3147
		);
3148
3149
		while ($row = $smcFunc['db_fetch_assoc']($get_messages))
3150
		{
3151
			$personal_messages[] = $row['id_pm'];
3152
		}
3153
3154
		$smcFunc['db_free_result']($get_messages);
3155
	}
3156
	elseif ($label = '-1')
0 ignored issues
show
The assignment to $label is dead and can be removed.
Loading history...
3157
	{
3158
		// Marking all PMs in your inbox read
3159
		$in_inbox = '
3160
			AND in_inbox = {int:in_inbox}';
3161
	}
3162
3163
	$smcFunc['db_query']('', '
3164
		UPDATE {db_prefix}pm_recipients
3165
		SET is_read = is_read | 1
3166
		WHERE id_member = {int:id_member}
3167
			AND NOT (is_read & 1 >= 1)' . ($personal_messages !== null ? '
3168
			AND id_pm IN ({array_int:personal_messages})' : '') . $in_inbox,
3169
		array(
3170
			'personal_messages' => $personal_messages,
3171
			'id_member' => $owner,
3172
			'in_inbox' => 1,
3173
		)
3174
	);
3175
3176
	// If something wasn't marked as read, get the number of unread messages remaining.
3177
	if ($smcFunc['db_affected_rows']() > 0)
3178
	{
3179
		if ($owner == $user_info['id'])
3180
		{
3181
			foreach ($context['labels'] as $label)
3182
				$context['labels'][(int) $label['id']]['unread_messages'] = 0;
3183
		}
3184
3185
		$result = $smcFunc['db_query']('', '
3186
			SELECT id_pm, in_inbox, COUNT(*) AS num
3187
			FROM {db_prefix}pm_recipients
3188
			WHERE id_member = {int:id_member}
3189
				AND NOT (is_read & 1 >= 1)
3190
				AND deleted = {int:is_not_deleted}
3191
			GROUP BY id_pm, in_inbox',
3192
			array(
3193
				'id_member' => $owner,
3194
				'is_not_deleted' => 0,
3195
			)
3196
		);
3197
		$total_unread = 0;
3198
		while ($row = $smcFunc['db_fetch_assoc']($result))
3199
		{
3200
			$total_unread += $row['num'];
3201
3202
			if ($owner != $user_info['id'] || empty($row['id_pm']))
3203
				continue;
3204
3205
			$this_labels = array();
3206
3207
			// Get all the labels
3208
			$result2 = $smcFunc['db_query']('', '
3209
				SELECT pml.id_label
3210
				FROM {db_prefix}pm_labels AS l
3211
					INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
3212
				WHERE l.id_member = {int:id_member}
3213
					AND pml.id_pm = {int:current_pm}',
3214
				array(
3215
					'id_member' => $owner,
3216
					'current_pm' => $row['id_pm'],
3217
				)
3218
			);
3219
3220
			while ($row2 = $smcFunc['db_fetch_assoc']($result2))
3221
			{
3222
				$this_labels[] = $row2['id_label'];
3223
			}
3224
3225
			$smcFunc['db_free_result']($result2);
3226
3227
			foreach ($this_labels as $this_label)
3228
				$context['labels'][$this_label]['unread_messages'] += $row['num'];
3229
3230
			if ($row['in_inbox'] == 1)
3231
				$context['labels'][-1]['unread_messages'] += $row['num'];
3232
		}
3233
		$smcFunc['db_free_result']($result);
3234
3235
		// Need to store all this.
3236
		cache_put_data('labelCounts:' . $owner, $context['labels'], 720);
3237
		updateMemberData($owner, array('unread_messages' => $total_unread));
3238
3239
		// If it was for the current member, reflect this in the $user_info array too.
3240
		if ($owner == $user_info['id'])
3241
			$user_info['unread_messages'] = $total_unread;
3242
	}
3243
}
3244
3245
/**
3246
 * This function handles adding, deleting and editing labels on messages.
3247
 */
3248
function ManageLabels()
3249
{
3250
	global $txt, $context, $user_info, $scripturl, $smcFunc;
3251
3252
	// Build the link tree elements...
3253
	$context['linktree'][] = array(
3254
		'url' => $scripturl . '?action=pm;sa=manlabels',
3255
		'name' => $txt['pm_manage_labels']
3256
	);
3257
3258
	$context['page_title'] = $txt['pm_manage_labels'];
3259
	$context['sub_template'] = 'labels';
3260
3261
	$the_labels = array();
3262
	$labels_to_add = array();
3263
	$labels_to_remove = array();
3264
	$label_updates = array();
3265
3266
	// Add all existing labels to the array to save, slashing them as necessary...
3267
	foreach ($context['labels'] as $label)
3268
	{
3269
		if ($label['id'] != -1)
3270
			$the_labels[$label['id']] = $label['name'];
3271
	}
3272
3273
	if (isset($_POST[$context['session_var']]))
3274
	{
3275
		checkSession();
3276
3277
		// This will be for updating messages.
3278
		$message_changes = array();
3279
		$rule_changes = array();
3280
3281
		// Will most likely need this.
3282
		LoadRules();
3283
3284
		// Adding a new label?
3285
		if (isset($_POST['add']))
3286
		{
3287
			$_POST['label'] = strtr($smcFunc['htmlspecialchars'](trim($_POST['label'])), array(',' => '&#044;'));
3288
3289
			if ($smcFunc['strlen']($_POST['label']) > 30)
3290
				$_POST['label'] = $smcFunc['substr']($_POST['label'], 0, 30);
3291
			if ($_POST['label'] != '')
3292
			{
3293
				$the_labels[] = $_POST['label'];
3294
				$labels_to_add[] = $_POST['label'];
3295
			}
3296
		}
3297
		// Deleting an existing label?
3298
		elseif (isset($_POST['delete'], $_POST['delete_label']))
3299
		{
3300
			foreach ($_POST['delete_label'] AS $label => $dummy)
3301
			{
3302
				unset($the_labels[$label]);
3303
				$labels_to_remove[] = $label;
3304
			}
3305
		}
3306
		// The hardest one to deal with... changes.
3307
		elseif (isset($_POST['save']) && !empty($_POST['label_name']))
3308
		{
3309
			foreach ($the_labels as $id => $name)
3310
			{
3311
				if ($id == -1)
3312
					continue;
3313
				elseif (isset($_POST['label_name'][$id]))
3314
				{
3315
					$_POST['label_name'][$id] = trim(strtr($smcFunc['htmlspecialchars']($_POST['label_name'][$id]), array(',' => '&#044;')));
3316
3317
					if ($smcFunc['strlen']($_POST['label_name'][$id]) > 30)
3318
						$_POST['label_name'][$id] = $smcFunc['substr']($_POST['label_name'][$id], 0, 30);
3319
					if ($_POST['label_name'][$id] != '')
3320
					{
3321
						// Changing the name of this label?
3322
						if ($the_labels[$id] != $_POST['label_name'][$id])
3323
							$label_updates[$id] = $_POST['label_name'][$id];
3324
3325
						$the_labels[(int) $id] = $_POST['label_name'][$id];
3326
					}
3327
					else
3328
					{
3329
						unset($the_labels[(int) $id]);
3330
						$labels_to_remove[] = $id;
3331
						$message_changes[(int) $id] = true;
3332
					}
3333
				}
3334
			}
3335
		}
3336
3337
		// Save any new labels
3338
		if (!empty($labels_to_add))
3339
		{
3340
			$inserts = array();
3341
			foreach ($labels_to_add AS $label)
3342
				$inserts[] = array($user_info['id'], $label);
3343
3344
			$smcFunc['db_insert']('', '{db_prefix}pm_labels', array('id_member' => 'int', 'name' => 'string-30'), $inserts, array());
3345
		}
3346
3347
		// Update existing labels as needed
3348
		if (!empty($label_upates))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $label_upates does not exist. Did you maybe mean $label_updates?
Loading history...
3349
		{
3350
			foreach ($label_updates AS $id => $name)
3351
			{
3352
				$smcFunc['db_query']('', '
3353
					UPDATE {db_prefix}labels
3354
					SET name = {string:name}
3355
					WHERE id_label = {int:id_label}',
3356
					array(
3357
						'name' => $name,
3358
						'id' => $id
3359
					)
3360
				);
3361
			}
3362
		}
3363
3364
		// Now the fun part... Deleting labels.
3365
		if (!empty($labels_to_remove))
3366
		{
3367
			// First delete the labels
3368
			$smcFunc['db_query']('', '
3369
				DELETE FROM {db_prefix}pm_labels
3370
				WHERE id_label IN ({array_int:labels_to_delete})',
3371
				array(
3372
					'labels_to_delete' => $labels_to_remove,
3373
				)
3374
			);
3375
3376
			// Now remove the now-deleted labels from any PMs...
3377
			$smcFunc['db_query']('', '
3378
				DELETE FROM {db_prefix}pm_labeled_messages
3379
				WHERE id_label IN ({array_int:labels_to_delete})',
3380
				array(
3381
					'labels_to_delete' => $labels_to_remove,
3382
				)
3383
			);
3384
3385
			// Get any PMs with no labels which aren't in the inbox
3386
			$get_stranded_pms = $smcFunc['db_query']('', '
3387
				SELECT pmr.id_pm
3388
				FROM {db_prefix}pm_recipients AS pmr
3389
					LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)
3390
				WHERE pml.id_label IS NULL
3391
					AND pmr.in_inbox = {int:not_in_inbox}
3392
					AND pmr.deleted = {int:not_deleted}
3393
					AND pmr.id_member = {int:current_member}',
3394
				array(
3395
					'not_in_inbox' => 0,
3396
					'not_deleted' => 0,
3397
					'current_member' => $user_info['id'],
3398
				)
3399
			);
3400
3401
			$stranded_messages = array();
3402
			while ($row = $smcFunc['db_fetch_assoc']($get_stranded_pms))
3403
			{
3404
				$stranded_messages[] = $row['id_pm'];
3405
			}
3406
3407
			$smcFunc['db_free_result']($get_stranded_pms);
3408
3409
			// Move these back to the inbox if necessary
3410
			if (!empty($stranded_messages))
3411
			{
3412
				// We now have more messages in the inbox
3413
				$context['labels'][-1]['messages'] += count($stranded_messages);
3414
				$smcFunc['db_query']('', '
3415
					UPDATE {db_prefix}pm_recipients
3416
					SET in_inbox = {int:in_inbox}
3417
					WHERE id_pm IN ({array_int:stranded_messages})
3418
						AND id_member = {int:current_member}',
3419
					array(
3420
						'stranded_messages' => $stranded_messages,
3421
						'in_inbox' => 1,
3422
					)
3423
				);
3424
			}
3425
3426
			// Now do the same the rules - check through each rule.
3427
			foreach ($context['rules'] as $k => $rule)
3428
			{
3429
				// Each action...
3430
				foreach ($rule['actions'] as $k2 => $action)
3431
				{
3432
					if ($action['t'] != 'lab' || !in_array($action['v'], $labels_to_remove))
3433
						continue;
3434
3435
					$rule_changes[] = $rule['id'];
3436
3437
					// Can't apply this label anymore if it doesn't exist
3438
					unset($context['rules'][$k]['actions'][$k2]);
3439
				}
3440
			}
3441
		}
3442
3443
		// If we have rules to change do so now.
3444
		if (!empty($rule_changes))
3445
		{
3446
			$rule_changes = array_unique($rule_changes);
3447
			// Update/delete as appropriate.
3448
			foreach ($rule_changes as $k => $id)
3449
				if (!empty($context['rules'][$id]['actions']))
3450
				{
3451
					$smcFunc['db_query']('', '
3452
						UPDATE {db_prefix}pm_rules
3453
						SET actions = {string:actions}
3454
						WHERE id_rule = {int:id_rule}
3455
							AND id_member = {int:current_member}',
3456
						array(
3457
							'current_member' => $user_info['id'],
3458
							'id_rule' => $id,
3459
							'actions' => $smcFunc['json_encode']($context['rules'][$id]['actions']),
3460
						)
3461
					);
3462
					unset($rule_changes[$k]);
3463
				}
3464
3465
			// Anything left here means it's lost all actions...
3466
			if (!empty($rule_changes))
3467
				$smcFunc['db_query']('', '
3468
					DELETE FROM {db_prefix}pm_rules
3469
					WHERE id_rule IN ({array_int:rule_list})
3470
						AND id_member = {int:current_member}',
3471
					array(
3472
						'current_member' => $user_info['id'],
3473
						'rule_list' => $rule_changes,
3474
					)
3475
				);
3476
		}
3477
3478
		// Make sure we're not caching this!
3479
		cache_put_data('labelCounts:' . $user_info['id'], null, 720);
3480
3481
		// To make the changes appear right away, redirect.
3482
		redirectexit('action=pm;sa=manlabels');
3483
	}
3484
}
3485
3486
/**
3487
 * Allows to edit Personal Message Settings.
3488
 *
3489
 * Uses Profile.php
3490
 * Uses Profile-Modify.php
3491
 * Uses Profile template.
3492
 * Uses Profile language file.
3493
 */
3494
function MessageSettings()
3495
{
3496
	global $txt, $user_info, $context, $sourcedir;
3497
	global $scripturl, $profile_vars, $cur_profile, $user_profile;
3498
3499
	// Need this for the display.
3500
	require_once($sourcedir . '/Profile.php');
3501
	require_once($sourcedir . '/Profile-Modify.php');
3502
3503
	// We want them to submit back to here.
3504
	$context['profile_custom_submit_url'] = $scripturl . '?action=pm;sa=settings;save';
3505
3506
	loadMemberData($user_info['id'], false, 'profile');
3507
	$cur_profile = $user_profile[$user_info['id']];
3508
3509
	loadLanguage('Profile');
3510
	loadTemplate('Profile');
3511
3512
	// Since this is internally handled with the profile code because that's how it was done ages ago
3513
	// we have to set everything up for handling this...
3514
	$context['page_title'] = $txt['pm_settings'];
3515
	$context['user']['is_owner'] = true;
3516
	$context['id_member'] = $user_info['id'];
3517
	$context['require_password'] = false;
3518
	$context['menu_item_selected'] = 'settings';
3519
	$context['submit_button_text'] = $txt['pm_settings'];
3520
	$context['profile_header_text'] = $txt['personal_messages'];
3521
	$context['sub_template'] = 'edit_options';
3522
	$context['page_desc'] = $txt['pm_settings_desc'];
3523
3524
	loadThemeOptions($user_info['id']);
3525
	loadCustomFields($user_info['id'], 'pmprefs');
3526
3527
	// Add our position to the linktree.
3528
	$context['linktree'][] = array(
3529
		'url' => $scripturl . '?action=pm;sa=settings',
3530
		'name' => $txt['pm_settings']
3531
	);
3532
3533
	// Are they saving?
3534
	if (isset($_REQUEST['save']))
3535
	{
3536
		checkSession();
3537
3538
		// Mimic what profile would do.
3539
		$_POST = htmltrim__recursive($_POST);
3540
		$_POST = htmlspecialchars__recursive($_POST);
3541
3542
		// Save the fields.
3543
		saveProfileFields();
3544
3545
		if (!empty($profile_vars))
3546
			updateMemberData($user_info['id'], $profile_vars);
3547
	}
3548
3549
	setupProfileContext(
3550
		array(
3551
			'pm_prefs',
3552
		)
3553
	);
3554
}
3555
3556
/**
3557
 * Allows the user to report a personal message to an administrator.
3558
 *
3559
 * - In the first instance requires that the ID of the message to report is passed through $_GET.
3560
 * - It allows the user to report to either a particular administrator - or the whole admin team.
3561
 * - It will forward on a copy of the original message without allowing the reporter to make changes.
3562
 *
3563
 * @uses template_report_message()
3564
 */
3565
function ReportMessage()
3566
{
3567
	global $txt, $context, $scripturl;
3568
	global $user_info, $language, $modSettings, $smcFunc;
3569
3570
	// Check that this feature is even enabled!
3571
	if (empty($modSettings['enableReportPM']) || empty($_REQUEST['pmsg']))
3572
		fatal_lang_error('no_access', false);
3573
3574
	$pmsg = (int) $_REQUEST['pmsg'];
3575
3576
	if (!isAccessiblePM($pmsg, 'inbox'))
3577
		fatal_lang_error('no_access', false);
3578
3579
	$context['pm_id'] = $pmsg;
3580
	$context['page_title'] = $txt['pm_report_title'];
3581
3582
	// If we're here, just send the user to the template, with a few useful context bits.
3583
	if (!isset($_POST['report']))
3584
	{
3585
		$context['sub_template'] = 'report_message';
3586
3587
		// @todo I don't like being able to pick who to send it to.  Favoritism, etc. sucks.
3588
		// Now, get all the administrators.
3589
		$request = $smcFunc['db_query']('', '
3590
			SELECT id_member, real_name
3591
			FROM {db_prefix}members
3592
			WHERE id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0
3593
			ORDER BY real_name',
3594
			array(
3595
				'admin_group' => 1,
3596
			)
3597
		);
3598
		$context['admins'] = array();
3599
		while ($row = $smcFunc['db_fetch_assoc']($request))
3600
			$context['admins'][$row['id_member']] = $row['real_name'];
3601
		$smcFunc['db_free_result']($request);
3602
3603
		// How many admins in total?
3604
		$context['admin_count'] = count($context['admins']);
3605
	}
3606
	// Otherwise, let's get down to the sending stuff.
3607
	else
3608
	{
3609
		// Check the session before proceeding any further!
3610
		checkSession();
3611
3612
		// First, pull out the message contents, and verify it actually went to them!
3613
		$request = $smcFunc['db_query']('', '
3614
			SELECT pm.subject, pm.body, pm.msgtime, pm.id_member_from, COALESCE(m.real_name, pm.from_name) AS sender_name
3615
			FROM {db_prefix}personal_messages AS pm
3616
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
3617
				LEFT JOIN {db_prefix}members AS m ON (m.id_member = pm.id_member_from)
3618
			WHERE pm.id_pm = {int:id_pm}
3619
				AND pmr.id_member = {int:current_member}
3620
				AND pmr.deleted = {int:not_deleted}
3621
			LIMIT 1',
3622
			array(
3623
				'current_member' => $user_info['id'],
3624
				'id_pm' => $context['pm_id'],
3625
				'not_deleted' => 0,
3626
			)
3627
		);
3628
		// Can only be a hacker here!
3629
		if ($smcFunc['db_num_rows']($request) == 0)
3630
			fatal_lang_error('no_access', false);
3631
		list ($subject, $body, $time, $memberFromID, $memberFromName) = $smcFunc['db_fetch_row']($request);
3632
		$smcFunc['db_free_result']($request);
3633
3634
		// Remove the line breaks...
3635
		$body = preg_replace('~<br ?/?' . '>~i', "\n", $body);
3636
3637
		// Get any other recipients of the email.
3638
		$request = $smcFunc['db_query']('', '
3639
			SELECT mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc
3640
			FROM {db_prefix}pm_recipients AS pmr
3641
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
3642
			WHERE pmr.id_pm = {int:id_pm}
3643
				AND pmr.id_member != {int:current_member}',
3644
			array(
3645
				'current_member' => $user_info['id'],
3646
				'id_pm' => $context['pm_id'],
3647
			)
3648
		);
3649
		$recipients = array();
3650
		$hidden_recipients = 0;
3651
		while ($row = $smcFunc['db_fetch_assoc']($request))
3652
		{
3653
			// If it's hidden still don't reveal their names - privacy after all ;)
3654
			if ($row['bcc'])
3655
				$hidden_recipients++;
3656
			else
3657
				$recipients[] = '[url=' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . ']' . $row['to_name'] . '[/url]';
3658
		}
3659
		$smcFunc['db_free_result']($request);
3660
3661
		if ($hidden_recipients)
3662
			$recipients[] = sprintf($txt['pm_report_pm_hidden'], $hidden_recipients);
3663
3664
		// Now let's get out and loop through the admins.
3665
		$request = $smcFunc['db_query']('', '
3666
			SELECT id_member, real_name, lngfile
3667
			FROM {db_prefix}members
3668
			WHERE (id_group = {int:admin_id} OR FIND_IN_SET({int:admin_id}, additional_groups) != 0)
3669
				' . (empty($_POST['id_admin']) ? '' : 'AND id_member = {int:specific_admin}') . '
3670
			ORDER BY lngfile',
3671
			array(
3672
				'admin_id' => 1,
3673
				'specific_admin' => isset($_POST['id_admin']) ? (int) $_POST['id_admin'] : 0,
3674
			)
3675
		);
3676
3677
		// Maybe we shouldn't advertise this?
3678
		if ($smcFunc['db_num_rows']($request) == 0)
3679
			fatal_lang_error('no_access', false);
3680
3681
		$memberFromName = un_htmlspecialchars($memberFromName);
3682
3683
		// Prepare the message storage array.
3684
		$messagesToSend = array();
3685
		// Loop through each admin, and add them to the right language pile...
3686
		while ($row = $smcFunc['db_fetch_assoc']($request))
3687
		{
3688
			// Need to send in the correct language!
3689
			$cur_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'];
3690
3691
			if (!isset($messagesToSend[$cur_language]))
3692
			{
3693
				loadLanguage('PersonalMessage', $cur_language, false);
3694
3695
				// Make the body.
3696
				$report_body = str_replace(array('{REPORTER}', '{SENDER}'), array(un_htmlspecialchars($user_info['name']), $memberFromName), $txt['pm_report_pm_user_sent']);
3697
				$report_body .= "\n" . '[b]' . $_POST['reason'] . '[/b]' . "\n\n";
3698
				if (!empty($recipients))
3699
					$report_body .= $txt['pm_report_pm_other_recipients'] . ' ' . implode(', ', $recipients) . "\n\n";
3700
				$report_body .= $txt['pm_report_pm_unedited_below'] . "\n" . '[quote author=' . (empty($memberFromID) ? '"' . $memberFromName . '"' : $memberFromName . ' link=action=profile;u=' . $memberFromID . ' date=' . $time) . ']' . "\n" . un_htmlspecialchars($body) . '[/quote]';
3701
3702
				// Plonk it in the array ;)
3703
				$messagesToSend[$cur_language] = array(
3704
					'subject' => ($smcFunc['strpos']($subject, $txt['pm_report_pm_subject']) === false ? $txt['pm_report_pm_subject'] : '') . un_htmlspecialchars($subject),
3705
					'body' => $report_body,
3706
					'recipients' => array(
3707
						'to' => array(),
3708
						'bcc' => array()
3709
					),
3710
				);
3711
			}
3712
3713
			// Add them to the list.
3714
			$messagesToSend[$cur_language]['recipients']['to'][$row['id_member']] = $row['id_member'];
3715
		}
3716
		$smcFunc['db_free_result']($request);
3717
3718
		// Send a different email for each language.
3719
		foreach ($messagesToSend as $lang => $message)
3720
			sendpm($message['recipients'], $message['subject'], $message['body']);
3721
3722
		// Give the user their own language back!
3723
		if (!empty($modSettings['userLanguage']))
3724
			loadLanguage('PersonalMessage', '', false);
3725
3726
		// Leave them with a template.
3727
		$context['sub_template'] = 'report_message_complete';
3728
	}
3729
}
3730
3731
/**
3732
 * List all rules, and allow adding/entering etc...
3733
 */
3734
function ManageRules()
3735
{
3736
	global $txt, $context, $user_info, $scripturl, $smcFunc;
3737
3738
	// Limit the Criteria and Actions to this.
3739
	$context['rule_limiters'] = array(
3740
		'criteria' => 10,
3741
		'actions' => 10,
3742
	);
3743
3744
	// The link tree - gotta have this :o
3745
	$context['linktree'][] = array(
3746
		'url' => $scripturl . '?action=pm;sa=manrules',
3747
		'name' => $txt['pm_manage_rules']
3748
	);
3749
3750
	$context['page_title'] = $txt['pm_manage_rules'];
3751
	$context['sub_template'] = 'rules';
3752
3753
	// Load them... load them!!
3754
	LoadRules();
3755
3756
	// Likely to need all the groups!
3757
	$request = $smcFunc['db_query']('', '
3758
		SELECT mg.id_group, mg.group_name, COALESCE(gm.id_member, 0) AS can_moderate, mg.hidden
3759
		FROM {db_prefix}membergroups AS mg
3760
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
3761
		WHERE mg.min_posts = {int:min_posts}
3762
			AND mg.id_group != {int:moderator_group}
3763
			AND mg.hidden = {int:not_hidden}
3764
		ORDER BY mg.group_name',
3765
		array(
3766
			'current_member' => $user_info['id'],
3767
			'min_posts' => -1,
3768
			'moderator_group' => 3,
3769
			'not_hidden' => 0,
3770
		)
3771
	);
3772
	$context['groups'] = array();
3773
	while ($row = $smcFunc['db_fetch_assoc']($request))
3774
	{
3775
		// Hide hidden groups!
3776
		if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups'))
3777
			continue;
3778
3779
		$context['groups'][$row['id_group']] = $row['group_name'];
3780
	}
3781
	$smcFunc['db_free_result']($request);
3782
3783
	// Applying all rules?
3784
	if (isset($_GET['apply']))
3785
	{
3786
		checkSession('get');
3787
		spamProtection('pm');
3788
3789
		ApplyRules(true);
3790
		redirectexit('action=pm;sa=manrules');
3791
	}
3792
	// Editing a specific one?
3793
	if (isset($_GET['add']))
3794
	{
3795
		$context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']]) ? (int) $_GET['rid'] : 0;
3796
		$context['sub_template'] = 'add_rule';
3797
3798
		// Current rule information...
3799
		if ($context['rid'])
3800
		{
3801
			$context['rule'] = $context['rules'][$context['rid']];
3802
			$members = array();
3803
			// Need to get member names!
3804
			foreach ($context['rule']['criteria'] as $k => $criteria)
3805
				if ($criteria['t'] == 'mid' && !empty($criteria['v']))
3806
					$members[(int) $criteria['v']] = $k;
3807
3808
			if (!empty($members))
3809
			{
3810
				$request = $smcFunc['db_query']('', '
3811
					SELECT id_member, member_name
3812
					FROM {db_prefix}members
3813
					WHERE id_member IN ({array_int:member_list})',
3814
					array(
3815
						'member_list' => array_keys($members),
3816
					)
3817
				);
3818
				while ($row = $smcFunc['db_fetch_assoc']($request))
3819
					$context['rule']['criteria'][$members[$row['id_member']]]['v'] = $row['member_name'];
3820
				$smcFunc['db_free_result']($request);
3821
			}
3822
		}
3823
		else
3824
			$context['rule'] = array(
3825
				'id' => '',
3826
				'name' => '',
3827
				'criteria' => array(),
3828
				'actions' => array(),
3829
				'logic' => 'and',
3830
			);
3831
	}
3832
	// Saving?
3833
	elseif (isset($_GET['save']))
3834
	{
3835
		checkSession();
3836
		$context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']]) ? (int) $_GET['rid'] : 0;
3837
3838
		// Name is easy!
3839
		$ruleName = $smcFunc['htmlspecialchars'](trim($_POST['rule_name']));
3840
		if (empty($ruleName))
3841
			fatal_lang_error('pm_rule_no_name', false);
3842
3843
		// Sanity check...
3844
		if (empty($_POST['ruletype']) || empty($_POST['acttype']))
3845
			fatal_lang_error('pm_rule_no_criteria', false);
3846
3847
		// Let's do the criteria first - it's also hardest!
3848
		$criteria = array();
3849
		$criteriaCount = 0;
3850
		foreach ($_POST['ruletype'] as $ind => $type)
3851
		{
3852
			// Check everything is here...
3853
			if ($type == 'gid' && (!isset($_POST['ruledefgroup'][$ind]) || !isset($context['groups'][$_POST['ruledefgroup'][$ind]])))
3854
				continue;
3855
			elseif ($type != 'bud' && !isset($_POST['ruledef'][$ind]))
3856
				continue;
3857
3858
			// Too many rules in this rule.
3859
			if ($criteriaCount++ >= $context['rule_limiters']['criteria'])
3860
				break;
3861
3862
			// Members need to be found.
3863
			if ($type == 'mid')
3864
			{
3865
				$name = trim($_POST['ruledef'][$ind]);
3866
				$request = $smcFunc['db_query']('', '
3867
					SELECT id_member
3868
					FROM {db_prefix}members
3869
					WHERE real_name = {string:member_name}
3870
						OR member_name = {string:member_name}',
3871
					array(
3872
						'member_name' => $name,
3873
					)
3874
				);
3875
				if ($smcFunc['db_num_rows']($request) == 0)
3876
				{
3877
					loadLanguage('Errors');
3878
					fatal_lang_error('invalid_username', false);
3879
				}
3880
				list ($memID) = $smcFunc['db_fetch_row']($request);
3881
				$smcFunc['db_free_result']($request);
3882
3883
				$criteria[] = array('t' => 'mid', 'v' => $memID);
3884
			}
3885
			elseif ($type == 'bud')
3886
				$criteria[] = array('t' => 'bud', 'v' => 1);
3887
			elseif ($type == 'gid')
3888
				$criteria[] = array('t' => 'gid', 'v' => (int) $_POST['ruledefgroup'][$ind]);
3889
			elseif (in_array($type, array('sub', 'msg')) && trim($_POST['ruledef'][$ind]) != '')
3890
				$criteria[] = array('t' => $type, 'v' => $smcFunc['htmlspecialchars'](trim($_POST['ruledef'][$ind])));
3891
		}
3892
3893
		// Also do the actions!
3894
		$actions = array();
3895
		$doDelete = 0;
3896
		$isOr = $_POST['rule_logic'] == 'or' ? 1 : 0;
3897
		$actionCount = 0;
3898
		foreach ($_POST['acttype'] as $ind => $type)
3899
		{
3900
			// Picking a valid label?
3901
			if ($type == 'lab' && (!ctype_digit((string) $ind) || !isset($_POST['labdef'][$ind]) || $_POST['labdef'][$ind] == '' || !isset($context['labels'][$_POST['labdef'][$ind]])))
3902
				continue;
3903
3904
			// Too many actions in this rule.
3905
			if ($actionCount++ >= $context['rule_limiters']['actions'])
3906
				break;
3907
3908
			// Record what we're doing.
3909
			if ($type == 'del')
3910
				$doDelete = 1;
3911
			elseif ($type == 'lab')
3912
				$actions[] = array('t' => 'lab', 'v' => (int) $_POST['labdef'][$ind]);
3913
		}
3914
3915
		if (empty($criteria) || (empty($actions) && !$doDelete))
3916
			fatal_lang_error('pm_rule_no_criteria', false);
3917
3918
		// What are we storing?
3919
		$criteria = $smcFunc['json_encode']($criteria);
3920
		$actions = $smcFunc['json_encode']($actions);
3921
3922
		// Create the rule?
3923
		if (empty($context['rid']))
3924
			$smcFunc['db_insert']('',
3925
				'{db_prefix}pm_rules',
3926
				array(
3927
					'id_member' => 'int', 'rule_name' => 'string', 'criteria' => 'string', 'actions' => 'string',
3928
					'delete_pm' => 'int', 'is_or' => 'int',
3929
				),
3930
				array(
3931
					$user_info['id'], $ruleName, $criteria, $actions, $doDelete, $isOr,
3932
				),
3933
				array('id_rule')
3934
			);
3935
		else
3936
			$smcFunc['db_query']('', '
3937
				UPDATE {db_prefix}pm_rules
3938
				SET rule_name = {string:rule_name}, criteria = {string:criteria}, actions = {string:actions},
3939
					delete_pm = {int:delete_pm}, is_or = {int:is_or}
3940
				WHERE id_rule = {int:id_rule}
3941
					AND id_member = {int:current_member}',
3942
				array(
3943
					'current_member' => $user_info['id'],
3944
					'delete_pm' => $doDelete,
3945
					'is_or' => $isOr,
3946
					'id_rule' => $context['rid'],
3947
					'rule_name' => $ruleName,
3948
					'criteria' => $criteria,
3949
					'actions' => $actions,
3950
				)
3951
			);
3952
3953
		redirectexit('action=pm;sa=manrules');
3954
	}
3955
	// Deleting?
3956
	elseif (isset($_POST['delselected']) && !empty($_POST['delrule']))
3957
	{
3958
		checkSession();
3959
		$toDelete = array();
3960
		foreach ($_POST['delrule'] as $k => $v)
3961
			$toDelete[] = (int) $k;
3962
3963
		if (!empty($toDelete))
3964
			$smcFunc['db_query']('', '
3965
				DELETE FROM {db_prefix}pm_rules
3966
				WHERE id_rule IN ({array_int:delete_list})
3967
					AND id_member = {int:current_member}',
3968
				array(
3969
					'current_member' => $user_info['id'],
3970
					'delete_list' => $toDelete,
3971
				)
3972
			);
3973
3974
		redirectexit('action=pm;sa=manrules');
3975
	}
3976
}
3977
3978
/**
3979
 * This will apply rules to all unread messages. If all_messages is set will, clearly, do it to all!
3980
 *
3981
 * @param bool $all_messages Whether to apply this to all messages or just unread ones
3982
 */
3983
function ApplyRules($all_messages = false)
3984
{
3985
	global $user_info, $smcFunc, $context, $options;
3986
3987
	// Want this - duh!
3988
	loadRules();
3989
3990
	// No rules?
3991
	if (empty($context['rules']))
3992
		return;
3993
3994
	// Just unread ones?
3995
	$ruleQuery = $all_messages ? '' : ' AND pmr.is_new = 1';
3996
3997
	// @todo Apply all should have timeout protection!
3998
	// Get all the messages that match this.
3999
	$request = $smcFunc['db_query']('', '
4000
		SELECT
4001
			pmr.id_pm, pm.id_member_from, pm.subject, pm.body, mem.id_group
4002
		FROM {db_prefix}pm_recipients AS pmr
4003
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
4004
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
4005
		WHERE pmr.id_member = {int:current_member}
4006
			AND pmr.deleted = {int:not_deleted}
4007
			' . $ruleQuery,
4008
		array(
4009
			'current_member' => $user_info['id'],
4010
			'not_deleted' => 0,
4011
		)
4012
	);
4013
	$actions = array();
4014
	while ($row = $smcFunc['db_fetch_assoc']($request))
4015
	{
4016
		foreach ($context['rules'] as $rule)
4017
		{
4018
			$match = false;
4019
			// Loop through all the criteria hoping to make a match.
4020
			foreach ($rule['criteria'] as $criterium)
4021
			{
4022
				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))
4023
					$match = true;
4024
				// If we're adding and one criteria don't match then we stop!
4025
				elseif ($rule['logic'] == 'and')
4026
				{
4027
					$match = false;
4028
					break;
4029
				}
4030
			}
4031
4032
			// If we have a match the rule must be true - act!
4033
			if ($match)
4034
			{
4035
				if ($rule['delete'])
4036
					$actions['deletes'][] = $row['id_pm'];
4037
				else
4038
				{
4039
					foreach ($rule['actions'] as $ruleAction)
4040
					{
4041
						if ($ruleAction['t'] == 'lab')
4042
						{
4043
							// Get a basic pot started!
4044
							if (!isset($actions['labels'][$row['id_pm']]))
4045
								$actions['labels'][$row['id_pm']] = array();
4046
4047
							$actions['labels'][$row['id_pm']][] = $ruleAction['v'];
4048
						}
4049
					}
4050
				}
4051
			}
4052
		}
4053
	}
4054
	$smcFunc['db_free_result']($request);
4055
4056
	// Deletes are easy!
4057
	if (!empty($actions['deletes']))
4058
		deleteMessages($actions['deletes']);
4059
4060
	// Relabel?
4061
	if (!empty($actions['labels']))
4062
	{
4063
		foreach ($actions['labels'] as $pm => $labels)
4064
		{
4065
			// Quickly check each label is valid!
4066
			$realLabels = array();
4067
			foreach ($context['labels'] as $label)
4068
			{
4069
				if (in_array($label['id'], $labels) && $label['id'] != -1 || empty($options['pm_remove_inbox_label']))
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: (in_array($label['id'], ...m_remove_inbox_label']), Probably Intended Meaning: in_array($label['id'], $..._remove_inbox_label']))
Loading history...
4070
				{
4071
					// Make sure this stays in the inbox
4072
					if ($label['id'] == '-1')
4073
					{
4074
						$smcFunc['db_query']('', '
4075
							UPDATE {db_prefix}pm_recipients
4076
							SET in_inbox = {int:in_inbox}
4077
							WHERE id_pm = {int:id_pm}
4078
								AND id_member = {int:current_member}',
4079
							array(
4080
								'in_inbox' => 1,
4081
								'id_pm' => $pm,
4082
								'current_member' => $user_info['id'],
4083
							)
4084
						);
4085
					}
4086
					else
4087
					{
4088
						$realLabels[] = $label['id'];
4089
					}
4090
				}
4091
			}
4092
4093
			$inserts = array();
4094
			// Now we insert the label info
4095
			foreach ($realLabels as $a_label)
4096
				$inserts[] = array($pm, $a_label);
4097
4098
			$smcFunc['db_insert']('ignore',
4099
				'{db_prefix}pm_labeled_messages',
4100
				array('id_pm' => 'int', 'id_label' => 'int'),
4101
				$inserts,
4102
				array('id_pm', 'id_label')
4103
			);
4104
		}
4105
	}
4106
}
4107
4108
/**
4109
 * Load up all the rules for the current user.
4110
 *
4111
 * @param bool $reload Whether or not to reload all the rules from the database if $context['rules'] is set
4112
 */
4113
function LoadRules($reload = false)
4114
{
4115
	global $user_info, $context, $smcFunc;
4116
4117
	if (isset($context['rules']) && !$reload)
4118
		return;
4119
4120
	$request = $smcFunc['db_query']('', '
4121
		SELECT
4122
			id_rule, rule_name, criteria, actions, delete_pm, is_or
4123
		FROM {db_prefix}pm_rules
4124
		WHERE id_member = {int:current_member}',
4125
		array(
4126
			'current_member' => $user_info['id'],
4127
		)
4128
	);
4129
	$context['rules'] = array();
4130
	// Simply fill in the data!
4131
	while ($row = $smcFunc['db_fetch_assoc']($request))
4132
	{
4133
		$context['rules'][$row['id_rule']] = array(
4134
			'id' => $row['id_rule'],
4135
			'name' => $row['rule_name'],
4136
			'criteria' => $smcFunc['json_decode']($row['criteria'], true),
4137
			'actions' => $smcFunc['json_decode']($row['actions'], true),
4138
			'delete' => $row['delete_pm'],
4139
			'logic' => $row['is_or'] ? 'or' : 'and',
4140
		);
4141
4142
		if ($row['delete_pm'])
4143
			$context['rules'][$row['id_rule']]['actions'][] = array('t' => 'del', 'v' => 1);
4144
	}
4145
	$smcFunc['db_free_result']($request);
4146
}
4147
4148
/**
4149
 * Check if the PM is available to the current user.
4150
 *
4151
 * @param int $pmID The ID of the PM
4152
 * @param string $validFor Which folders this is valud for - can be 'inbox', 'outbox' or 'in_or_outbox'
4153
 * @return boolean Whether the PM is accessible in that folder for the current user
4154
 */
4155
function isAccessiblePM($pmID, $validFor = 'in_or_outbox')
4156
{
4157
	global $user_info, $smcFunc;
4158
4159
	$request = $smcFunc['db_query']('', '
4160
		SELECT
4161
			pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted} AS valid_for_outbox,
4162
			pmr.id_pm IS NOT NULL AS valid_for_inbox
4163
		FROM {db_prefix}personal_messages AS pm
4164
			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})
4165
		WHERE pm.id_pm = {int:id_pm}
4166
			AND ((pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted}) OR pmr.id_pm IS NOT NULL)',
4167
		array(
4168
			'id_pm' => $pmID,
4169
			'id_current_member' => $user_info['id'],
4170
			'not_deleted' => 0,
4171
		)
4172
	);
4173
4174
	if ($smcFunc['db_num_rows']($request) === 0)
4175
	{
4176
		$smcFunc['db_free_result']($request);
4177
		return false;
4178
	}
4179
4180
	$validationResult = $smcFunc['db_fetch_assoc']($request);
4181
	$smcFunc['db_free_result']($request);
4182
4183
	switch ($validFor)
4184
	{
4185
		case 'inbox':
4186
			return !empty($validationResult['valid_for_inbox']);
4187
			break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
4188
4189
		case 'outbox':
4190
			return !empty($validationResult['valid_for_outbox']);
4191
			break;
4192
4193
		case 'in_or_outbox':
4194
			return !empty($validationResult['valid_for_inbox']) || !empty($validationResult['valid_for_outbox']);
4195
			break;
4196
4197
		default:
4198
			trigger_error('Undefined validation type given', E_USER_ERROR);
4199
			break;
4200
	}
4201
}
4202
4203
?>