Issues (1065)

Sources/PersonalMessage.php (10 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 2025 Simple Machines and individual contributors
13
 * @license https://www.simplemachines.org/about/smf/license.php BSD
14
 *
15
 * @version 2.1.5
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, $options;
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
	$context['can_moderate_forum'] = allowedTo('moderate_forum');
180
181
	// Are PM drafts enabled?
182
	$context['drafts_pm_save'] = !empty($modSettings['drafts_pm_enabled']) && allowedTo('pm_draft');
183
	$context['drafts_autosave'] = !empty($context['drafts_pm_save']) && !empty($modSettings['drafts_autosave_enabled']) && !empty($options['drafts_autosave_enabled']);
184
185
	// Build the linktree for all the actions...
186
	$context['linktree'][] = array(
187
		'url' => $scripturl . '?action=pm',
188
		'name' => $txt['personal_messages']
189
	);
190
191
	// Preferences...
192
	$context['display_mode'] = $user_settings['pm_prefs'] & 3;
193
194
	$subActions = array(
195
		'popup' => 'MessagePopup',
196
		'manlabels' => 'ManageLabels',
197
		'manrules' => 'ManageRules',
198
		'pmactions' => 'MessageActionsApply',
199
		'prune' => 'MessagePrune',
200
		'removeall2' => 'MessageKillAll',
201
		'report' => 'ReportMessage',
202
		'search' => 'MessageSearch',
203
		'search2' => 'MessageSearch2',
204
		'send' => 'MessagePost',
205
		'send2' => 'MessagePost2',
206
		'settings' => 'MessageSettings',
207
		'showpmdrafts' => 'MessageDrafts',
208
	);
209
210
	if (!isset($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']]))
211
	{
212
		$_REQUEST['sa'] = '';
213
		MessageFolder();
214
	}
215
	else
216
	{
217
		if (!isset($_REQUEST['xml']) && $_REQUEST['sa'] != 'popup')
218
			messageIndexBar($_REQUEST['sa']);
219
220
		call_helper($subActions[$_REQUEST['sa']]);
221
	}
222
}
223
224
/**
225
 * A menu to easily access different areas of the PM section
226
 *
227
 * @param string $area The area we're currently in
228
 */
229
function messageIndexBar($area)
230
{
231
	global $txt, $context, $scripturl, $sourcedir, $modSettings, $user_info;
232
233
	$pm_areas = array(
234
		'folders' => array(
235
			'title' => $txt['pm_messages'],
236
			'areas' => array(
237
				'inbox' => array(
238
					'label' => $txt['inbox'],
239
					'custom_url' => $scripturl . '?action=pm',
240
					'amt' => 0,
241
				),
242
				'send' => array(
243
					'label' => $txt['new_message'],
244
					'custom_url' => $scripturl . '?action=pm;sa=send',
245
					'permission' => 'pm_send',
246
					'amt' => 0,
247
				),
248
				'sent' => array(
249
					'label' => $txt['sent_items'],
250
					'custom_url' => $scripturl . '?action=pm;f=sent',
251
					'amt' => 0,
252
				),
253
				'drafts' => array(
254
					'label' => $txt['drafts_show'],
255
					'custom_url' => $scripturl . '?action=pm;sa=showpmdrafts',
256
					'permission' => 'pm_draft',
257
					'enabled' => !empty($modSettings['drafts_pm_enabled']),
258
					'amt' => 0,
259
				),
260
			),
261
			'amt' => 0,
262
		),
263
		'labels' => array(
264
			'title' => $txt['pm_labels'],
265
			'areas' => array(),
266
			'amt' => 0,
267
		),
268
		'actions' => array(
269
			'title' => $txt['pm_actions'],
270
			'areas' => array(
271
				'search' => array(
272
					'label' => $txt['pm_search_bar_title'],
273
					'custom_url' => $scripturl . '?action=pm;sa=search',
274
				),
275
				'prune' => array(
276
					'label' => $txt['pm_prune'],
277
					'custom_url' => $scripturl . '?action=pm;sa=prune'
278
				),
279
			),
280
		),
281
		'pref' => array(
282
			'title' => $txt['pm_preferences'],
283
			'areas' => array(
284
				'manlabels' => array(
285
					'label' => $txt['pm_manage_labels'],
286
					'custom_url' => $scripturl . '?action=pm;sa=manlabels',
287
				),
288
				'manrules' => array(
289
					'label' => $txt['pm_manage_rules'],
290
					'custom_url' => $scripturl . '?action=pm;sa=manrules',
291
				),
292
				'settings' => array(
293
					'label' => $txt['pm_settings'],
294
					'custom_url' => $scripturl . '?action=pm;sa=settings',
295
				),
296
			),
297
		),
298
	);
299
300
	// Handle labels.
301
	if (empty($context['currently_using_labels']))
302
		unset($pm_areas['labels']);
303
	else
304
	{
305
		// Note we send labels by id as it will have less problems in the querystring.
306
		foreach ($context['labels'] as $label)
307
		{
308
			if ($label['id'] == -1)
309
				continue;
310
311
			// Count the amount of unread items in labels.
312
			$pm_areas['labels']['amt'] += $label['unread_messages'];
313
314
			// Add the label to the menu.
315
			$pm_areas['labels']['areas']['label' . $label['id']] = array(
316
				'label' => $label['name'],
317
				'custom_url' => $scripturl . '?action=pm;l=' . $label['id'],
318
				'amt' => $label['unread_messages'],
319
				'unread_messages' => $label['unread_messages'],
320
				'messages' => $label['messages'],
321
				'icon' => 'folder',
322
			);
323
		}
324
	}
325
326
	$pm_areas['folders']['areas']['inbox']['unread_messages'] = &$context['labels'][-1]['unread_messages'];
327
	$pm_areas['folders']['areas']['inbox']['messages'] = &$context['labels'][-1]['messages'];
328
	if (!empty($context['labels'][-1]['unread_messages']))
329
	{
330
		$pm_areas['folders']['areas']['inbox']['amt'] = $context['labels'][-1]['unread_messages'];
331
		$pm_areas['folders']['amt'] = $context['labels'][-1]['unread_messages'];
332
	}
333
334
	// Do we have a limit on the amount of messages we can keep?
335
	if (!empty($context['message_limit']))
336
	{
337
		$bar = round(($user_info['messages'] * 100) / $context['message_limit'], 1);
338
339
		$context['limit_bar'] = array(
340
			'messages' => $user_info['messages'],
341
			'allowed' => $context['message_limit'],
342
			'percent' => $bar,
343
			'bar' => $bar > 100 ? 100 : (int) $bar,
344
			'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], $bar)
345
		);
346
	}
347
348
	require_once($sourcedir . '/Subs-Menu.php');
349
350
	// Set a few options for the menu.
351
	$menuOptions = array(
352
		'current_area' => $area,
353
		'disable_url_session_check' => true,
354
	);
355
356
	// Actually create the menu!
357
	$pm_include_data = createMenu($pm_areas, $menuOptions);
358
	unset($pm_areas);
359
360
	// No menu means no access.
361
	if (!$pm_include_data && (!$user_info['is_guest'] || validateSession()))
0 ignored issues
show
The condition $pm_include_data is always false.
Loading history...
362
		fatal_lang_error('no_access', false);
363
364
	// Make a note of the Unique ID for this menu.
365
	$context['pm_menu_id'] = $context['max_menu_id'];
366
	$context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id'];
367
368
	// Set the selected item.
369
	$current_area = $pm_include_data['current_area'];
370
	$context['menu_item_selected'] = $current_area;
371
372
	// Set the template for this area and add the profile layer.
373
	if (!isset($_REQUEST['xml']))
374
		$context['template_layers'][] = 'pm';
375
}
376
377
/**
378
 * The popup for when we ask for the popup from the user.
379
 */
380
function MessagePopup()
381
{
382
	global $context, $modSettings, $smcFunc, $memberContext, $scripturl, $user_settings, $db_show_debug;
383
384
	// We do not want to output debug information here.
385
	$db_show_debug = false;
386
387
	// We only want to output our little layer here.
388
	$context['template_layers'] = array();
389
	$context['sub_template'] = 'pm_popup';
390
391
	$context['can_send_pm'] = allowedTo('pm_send');
392
	$context['can_draft'] = allowedTo('pm_draft') && !empty($modSettings['drafts_pm_enabled']);
393
394
	// So are we loading stuff?
395
	$request = $smcFunc['db_query']('', '
396
		SELECT id_pm
397
		FROM {db_prefix}pm_recipients AS pmr
398
		WHERE pmr.id_member = {int:current_member}
399
			AND is_read = {int:not_read}
400
			AND deleted = {int:not_deleted}
401
		ORDER BY id_pm',
402
		array(
403
			'current_member' => $context['user']['id'],
404
			'not_read' => 0,
405
			'not_deleted' => 0,
406
		)
407
	);
408
	$pms = array();
409
	while ($row = $smcFunc['db_fetch_row']($request))
410
		$pms[] = $row[0];
411
	$smcFunc['db_free_result']($request);
412
413
	if (!empty($pms))
414
	{
415
		// Just quickly, it's possible that the number of PMs can get out of sync.
416
		$count_unread = count($pms);
417
		if ($count_unread != $user_settings['unread_messages'])
418
		{
419
			updateMemberData($context['user']['id'], array('unread_messages' => $count_unread));
420
			$context['user']['unread_messages'] = count($pms);
421
		}
422
423
		// Now, actually fetch me some PMs. Make sure we track the senders, got some work to do for them.
424
		$senders = array();
425
426
		$request = $smcFunc['db_query']('', '
427
			SELECT pm.id_pm, pm.id_pm_head, COALESCE(mem.id_member, pm.id_member_from) AS id_member_from,
428
				COALESCE(mem.real_name, pm.from_name) AS member_from, pm.msgtime AS timestamp, pm.subject
429
			FROM {db_prefix}personal_messages AS pm
430
				LEFT JOIN {db_prefix}members AS mem ON (pm.id_member_from = mem.id_member)
431
			WHERE pm.id_pm IN ({array_int:id_pms})',
432
			array(
433
				'id_pms' => $pms,
434
			)
435
		);
436
		while ($row = $smcFunc['db_fetch_assoc']($request))
437
		{
438
			if (!empty($row['id_member_from']))
439
				$senders[] = $row['id_member_from'];
440
441
			$row['replied_to_you'] = $row['id_pm'] != $row['id_pm_head'];
442
			$row['time'] = timeformat($row['timestamp']);
443
			$row['pm_link'] = '<a href="' . $scripturl . '?action=pm;f=inbox;pmsg=' . $row['id_pm'] . '">' . $row['subject'] . '</a>';
444
			$context['unread_pms'][$row['id_pm']] = $row;
445
		}
446
		$smcFunc['db_free_result']($request);
447
448
		$senders = loadMemberData($senders);
449
		foreach ($senders as $member)
450
			loadMemberContext($member);
451
452
		// Having loaded everyone, attach them to the PMs.
453
		foreach ($context['unread_pms'] as $id_pm => $details)
454
			if (!empty($memberContext[$details['id_member_from']]))
455
				$context['unread_pms'][$id_pm]['member'] = &$memberContext[$details['id_member_from']];
456
	}
457
}
458
459
/**
460
 * A folder, ie. inbox/sent etc.
461
 */
462
function MessageFolder()
463
{
464
	global $txt, $scripturl, $modSettings, $context, $subjects_request;
465
	global $messages_request, $user_info, $recipients, $options, $smcFunc, $user_settings;
466
467
	// Changing view?
468
	if (isset($_GET['view']))
469
	{
470
		$context['display_mode'] = $context['display_mode'] > 1 ? 0 : $context['display_mode'] + 1;
471
		updateMemberData($user_info['id'], array('pm_prefs' => ($user_settings['pm_prefs'] & 252) | $context['display_mode']));
472
	}
473
474
	// Make sure the starting location is valid.
475
	if (isset($_GET['start']) && $_GET['start'] != 'new')
476
		$_GET['start'] = (int) $_GET['start'];
477
	elseif (!isset($_GET['start']) && !empty($options['view_newest_pm_first']))
478
		$_GET['start'] = 0;
479
	else
480
		$_GET['start'] = 'new';
481
482
	// Set up some basic theme stuff.
483
	$context['from_or_to'] = $context['folder'] != 'sent' ? 'from' : 'to';
484
	$context['get_pmessage'] = 'prepareMessageContext';
485
	$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
486
	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
487
488
	// Prevent signature images from going outside the box.
489
	if ($context['signature_enabled'])
490
	{
491
		list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
492
		$sig_limits = explode(',', $sig_limits);
493
494
		if (!empty($sig_limits[5]) || !empty($sig_limits[6]))
495
			addInlineCss('
496
	.signature img { ' . (!empty($sig_limits[5]) ? 'max-width: ' . (int) $sig_limits[5] . 'px; ' : '') . (!empty($sig_limits[6]) ? 'max-height: ' . (int) $sig_limits[6] . 'px; ' : '') . '}');
497
	}
498
499
	$labelJoin = '';
500
	$labelQuery = '';
501
	$labelQuery2 = '';
502
503
	// SMF logic: If you're viewing a label, it's still the inbox
504
	if ($context['folder'] == 'inbox' && $context['current_label_id'] == -1)
505
	{
506
		$labelQuery = '
507
			AND pmr.in_inbox = 1';
508
	}
509
	elseif ($context['folder'] != 'sent')
510
	{
511
		$labelJoin = '
512
			INNER JOIN {db_prefix}pm_labeled_messages AS pl ON (pl.id_pm = pmr.id_pm)';
513
514
		$labelQuery2 = '
515
			AND pl.id_label = ' . $context['current_label_id'];
516
	}
517
518
	// Set the index bar correct!
519
	messageIndexBar($context['current_label_id'] == -1 ? $context['folder'] : 'label' . $context['current_label_id']);
520
521
	// Sorting the folder.
522
	$sort_methods = array(
523
		'date' => 'pm.id_pm',
524
		'name' => 'COALESCE(mem.real_name, \'\')',
525
		'subject' => 'pm.subject',
526
	);
527
528
	// They didn't pick one, use the forum default.
529
	if (!isset($_GET['sort']) || !isset($sort_methods[$_GET['sort']]))
530
	{
531
		$context['sort_by'] = 'date';
532
		$_GET['sort'] = 'pm.id_pm';
533
		// An overriding setting?
534
		$descending = !empty($options['view_newest_pm_first']);
535
	}
536
	// Otherwise use the defaults: ascending, by date.
537
	else
538
	{
539
		$context['sort_by'] = $_GET['sort'];
540
		$_GET['sort'] = $sort_methods[$_GET['sort']];
541
		$descending = isset($_GET['desc']);
542
	}
543
544
	$context['sort_direction'] = $descending ? 'down' : 'up';
545
546
	// Set the text to resemble the current folder.
547
	$pmbox = $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'];
548
	$txt['delete_all'] = str_replace('PMBOX', $pmbox, $txt['delete_all']);
549
550
	// Now, build the link tree!
551
	if ($context['current_label_id'] == -1)
552
		$context['linktree'][] = array(
553
			'url' => $scripturl . '?action=pm;f=' . $context['folder'],
554
			'name' => $pmbox
555
		);
556
557
	// Build it further for a label.
558
	if ($context['current_label_id'] != -1)
559
		$context['linktree'][] = array(
560
			'url' => $scripturl . '?action=pm;f=' . $context['folder'] . ';l=' . $context['current_label_id'],
561
			'name' => $txt['pm_current_label'] . ': ' . $context['current_label']
562
		);
563
564
	// Figure out how many messages there are.
565
	if ($context['folder'] == 'sent')
566
		$request = $smcFunc['db_query']('', '
567
			SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
568
			FROM {db_prefix}personal_messages AS pm
569
			WHERE pm.id_member_from = {int:current_member}
570
				AND pm.deleted_by_sender = {int:not_deleted}',
571
			array(
572
				'current_member' => $user_info['id'],
573
				'not_deleted' => 0,
574
			)
575
		);
576
	else
577
		$request = $smcFunc['db_query']('', '
578
			SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
579
			FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
580
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . $labelJoin . '
581
			WHERE pmr.id_member = {int:current_member}
582
				AND pmr.deleted = {int:not_deleted}' . $labelQuery . $labelQuery2,
583
			array(
584
				'current_member' => $user_info['id'],
585
				'not_deleted' => 0,
586
			)
587
		);
588
	list ($max_messages) = $smcFunc['db_fetch_row']($request);
589
	$smcFunc['db_free_result']($request);
590
591
	// Only show the button if there are messages to delete.
592
	$context['show_delete'] = $max_messages > 0;
593
	$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
594
595
	// Start on the last page.
596
	if (!is_numeric($_GET['start']) || $_GET['start'] >= $max_messages)
597
		$_GET['start'] = ($max_messages - 1) - (($max_messages - 1) % $maxPerPage);
598
	elseif ($_GET['start'] < 0)
599
		$_GET['start'] = 0;
600
601
	// ... but wait - what if we want to start from a specific message?
602
	if (isset($_GET['pmid']))
603
	{
604
		$pmID = (int) $_GET['pmid'];
605
606
		// Make sure you have access to this PM.
607
		if (!isAccessiblePM($pmID, $context['folder'] == 'sent' ? 'outbox' : 'inbox'))
608
			fatal_lang_error('no_access', false);
609
610
		$context['current_pm'] = $pmID;
611
612
		// With only one page of PM's we're gonna want page 1.
613
		if ($max_messages <= $maxPerPage)
614
			$_GET['start'] = 0;
615
		// If we pass kstart we assume we're in the right place.
616
		elseif (!isset($_GET['kstart']))
617
		{
618
			if ($context['folder'] == 'sent')
619
				$request = $smcFunc['db_query']('', '
620
					SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
621
					FROM {db_prefix}personal_messages
622
					WHERE id_member_from = {int:current_member}
623
						AND deleted_by_sender = {int:not_deleted}
624
						AND id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}',
625
					array(
626
						'current_member' => $user_info['id'],
627
						'not_deleted' => 0,
628
						'id_pm' => $pmID,
629
					)
630
				);
631
			else
632
				$request = $smcFunc['db_query']('', '
633
					SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
634
					FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
635
						INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . $labelJoin . '
636
					WHERE pmr.id_member = {int:current_member}
637
						AND pmr.deleted = {int:not_deleted}' . $labelQuery . $labelQuery2 . '
638
						AND pmr.id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}',
639
					array(
640
						'current_member' => $user_info['id'],
641
						'not_deleted' => 0,
642
						'id_pm' => $pmID,
643
					)
644
				);
645
646
			list ($_GET['start']) = $smcFunc['db_fetch_row']($request);
647
			$smcFunc['db_free_result']($request);
648
649
			// To stop the page index's being abnormal, start the page on the page the message would normally be located on...
650
			$_GET['start'] = $maxPerPage * (int) ($_GET['start'] / $maxPerPage);
651
		}
652
	}
653
654
	// Sanitize and validate pmsg variable if set.
655
	if (isset($_GET['pmsg']))
656
	{
657
		$pmsg = (int) $_GET['pmsg'];
658
659
		if (!isAccessiblePM($pmsg, $context['folder'] == 'sent' ? 'outbox' : 'inbox'))
660
			fatal_lang_error('no_access', false);
661
	}
662
663
	// Set up the page index.
664
	$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);
665
	$context['start'] = $_GET['start'];
666
667
	// Determine the navigation context.
668
	$context['links'] = array(
669
		'first' => $_GET['start'] >= $maxPerPage ? $scripturl . '?action=pm;start=0' : '',
670
		'prev' => $_GET['start'] >= $maxPerPage ? $scripturl . '?action=pm;start=' . ($_GET['start'] - $maxPerPage) : '',
671
		'next' => $_GET['start'] + $maxPerPage < $max_messages ? $scripturl . '?action=pm;start=' . ($_GET['start'] + $maxPerPage) : '',
672
		'last' => $_GET['start'] + $maxPerPage < $max_messages ? $scripturl . '?action=pm;start=' . (floor(($max_messages - 1) / $maxPerPage) * $maxPerPage) : '',
673
		'up' => $scripturl,
674
	);
675
	$context['page_info'] = array(
676
		'current_page' => $_GET['start'] / $maxPerPage + 1,
677
		'num_pages' => floor(($max_messages - 1) / $maxPerPage) + 1
678
	);
679
680
	// First work out what messages we need to see - if grouped is a little trickier...
681
	if ($context['display_mode'] == 2)
682
	{
683
		if ($context['folder'] != 'sent' && $context['folder'] != 'inbox')
684
		{
685
			$labelJoin = '
686
				INNER JOIN {db_prefix}pm_labeled_messages AS pl ON (pl.id_pm = pm.id_pm)';
687
688
			$labelQuery = '';
689
			$labelQuery2 = '
690
				AND pl.id_label = ' . $context['current_label_id'];
691
		}
692
693
		$request = $smcFunc['db_query']('', '
694
			SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
695
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? '
696
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
697
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
698
					AND pmr.id_member = {int:current_member}
699
					AND pmr.deleted = {int:deleted_by}
700
					' . $labelQuery . ')') . $labelJoin . ($context['sort_by'] == 'name' ? ('
701
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
702
			WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
703
				AND pm.deleted_by_sender = {int:deleted_by}' : '1=1') . (empty($pmsg) ? '' : '
704
				AND pm.id_pm = {int:pmsg}') . $labelQuery2 . '
705
			GROUP BY pm.id_pm_head' . ($_GET['sort'] != 'pm.id_pm' ? ',' . $_GET['sort'] : '') . '
706
			ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' ? 'id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($_GET['pmsg']) ? '
707
			LIMIT ' . $_GET['start'] . ', ' . $maxPerPage : ''),
708
			array(
709
				'current_member' => $user_info['id'],
710
				'deleted_by' => 0,
711
				'sort' => $_GET['sort'],
712
				'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
713
				'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
714
			)
715
		);
716
	}
717
	// This is kinda simple!
718
	else
719
	{
720
		// @todo SLOW This query uses a filesort. (inbox only.)
721
		$request = $smcFunc['db_query']('', '
722
			SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
723
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' . ($context['sort_by'] == 'name' ? '
724
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
725
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
726
					AND pmr.id_member = {int:current_member}
727
					AND pmr.deleted = {int:is_deleted}
728
					' . $labelQuery . ')') . $labelJoin . ($context['sort_by'] == 'name' ? ('
729
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
730
			WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
731
				AND pm.deleted_by_sender = {int:is_deleted}' : '1=1') . (empty($pmsg) ? '' : '
732
				AND pm.id_pm = {int:pmsg}') . $labelQuery2 . '
733
			ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'pmr.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? '
734
			LIMIT ' . $_GET['start'] . ', ' . $maxPerPage : ''),
735
			array(
736
				'current_member' => $user_info['id'],
737
				'is_deleted' => 0,
738
				'sort' => $_GET['sort'],
739
				'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
740
				'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
741
			)
742
		);
743
	}
744
	// Load the id_pms and initialize recipients.
745
	$pms = array();
746
	$lastData = array();
747
	$posters = $context['folder'] == 'sent' ? array($user_info['id']) : array();
748
	$recipients = array();
749
750
	while ($row = $smcFunc['db_fetch_assoc']($request))
751
	{
752
		if (!isset($recipients[$row['id_pm']]))
753
		{
754
			if (isset($row['id_member_from']))
755
				$posters[$row['id_pm']] = $row['id_member_from'];
756
			$pms[$row['id_pm']] = $row['id_pm'];
757
			$recipients[$row['id_pm']] = array(
758
				'to' => array(),
759
				'bcc' => array()
760
			);
761
		}
762
763
		// Keep track of the last message so we know what the head is without another query!
764
		if ((empty($pmID) && (empty($options['view_newest_pm_first']) || !isset($lastData))) || empty($lastData) || (!empty($pmID) && $pmID == $row['id_pm']))
765
			$lastData = array(
766
				'id' => $row['id_pm'],
767
				'head' => $row['id_pm_head'],
768
			);
769
	}
770
	$smcFunc['db_free_result']($request);
771
772
	// Make sure that we have been given a correct head pm id!
773
	if ($context['display_mode'] == 2 && !empty($pmID) && $pmID != $lastData['id'])
774
		fatal_lang_error('no_access', false);
775
776
	if (!empty($pms))
777
	{
778
		// Select the correct current message.
779
		if (empty($pmID))
780
			$context['current_pm'] = $lastData['id'];
781
782
		// This is a list of the pm's that are used for "full" display.
783
		if ($context['display_mode'] == 0)
784
			$display_pms = $pms;
785
		else
786
			$display_pms = array($context['current_pm']);
787
788
		// At this point we know the main id_pm's. But - if we are looking at conversations we need the others!
789
		if ($context['display_mode'] == 2)
790
		{
791
			$request = $smcFunc['db_query']('', '
792
				SELECT pm.id_pm, pm.id_member_from, pm.deleted_by_sender, pmr.id_member, pmr.deleted
793
				FROM {db_prefix}personal_messages AS pm
794
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
795
				WHERE pm.id_pm_head = {int:id_pm_head}
796
					AND ((pm.id_member_from = {int:current_member} AND pm.deleted_by_sender = {int:not_deleted})
797
					OR (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted}))
798
				ORDER BY pm.id_pm',
799
				array(
800
					'current_member' => $user_info['id'],
801
					'id_pm_head' => $lastData['head'],
802
					'not_deleted' => 0,
803
				)
804
			);
805
			while ($row = $smcFunc['db_fetch_assoc']($request))
806
			{
807
				// This is, frankly, a joke. We will put in a workaround for people sending to themselves - yawn!
808
				if ($context['folder'] == 'sent' && $row['id_member_from'] == $user_info['id'] && $row['deleted_by_sender'] == 1)
809
					continue;
810
				elseif ($row['id_member'] == $user_info['id'] & $row['deleted'] == 1)
811
					continue;
812
813
				if (!isset($recipients[$row['id_pm']]))
814
					$recipients[$row['id_pm']] = array(
815
						'to' => array(),
816
						'bcc' => array()
817
					);
818
				$display_pms[] = $row['id_pm'];
819
				$posters[$row['id_pm']] = $row['id_member_from'];
820
			}
821
			$smcFunc['db_free_result']($request);
822
		}
823
824
		// This is pretty much EVERY pm!
825
		$all_pms = array_merge($pms, $display_pms);
826
		$all_pms = array_unique($all_pms);
827
828
		// Get recipients (don't include bcc-recipients for your inbox, you're not supposed to know :P).
829
		$request = $smcFunc['db_query']('', '
830
			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
831
			FROM {db_prefix}pm_recipients AS pmr
832
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
833
			WHERE pmr.id_pm IN ({array_int:pm_list})',
834
			array(
835
				'pm_list' => $all_pms,
836
			)
837
		);
838
		$context['message_labels'] = array();
839
		$context['message_replied'] = array();
840
		$context['message_unread'] = array();
841
		while ($row = $smcFunc['db_fetch_assoc']($request))
842
		{
843
			if ($context['folder'] == 'sent' || empty($row['bcc']))
844
			{
845
				$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>';
846
847
				$context['folder'] == 'sent' && $context['display_mode'] != 2 ? $context['message_replied'][$row['id_pm']] = $row['is_read'] & 2 : '';
848
			}
849
850
			if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
851
			{
852
				$context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
853
				$context['message_unread'][$row['id_pm']] = $row['is_read'] == 0;
854
855
				// Get the labels for this PM
856
				$request2 = $smcFunc['db_query']('', '
857
					SELECT id_label
858
					FROM {db_prefix}pm_labeled_messages
859
					WHERE id_pm = {int:current_pm}',
860
					array(
861
						'current_pm' => $row['id_pm'],
862
					)
863
				);
864
865
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
866
				{
867
					$l_id = $row2['id_label'];
868
					if (isset($context['labels'][$l_id]))
869
						$context['message_labels'][$row['id_pm']][$l_id] = array('id' => $l_id, 'name' => $context['labels'][$l_id]['name']);
870
				}
871
872
				$smcFunc['db_free_result']($request2);
873
874
				// Is this in the inbox as well?
875
				if ($row['in_inbox'] == 1)
876
				{
877
					$context['message_labels'][$row['id_pm']][-1] = array('id' => -1, 'name' => $context['labels'][-1]['name']);
878
				}
879
			}
880
		}
881
		$smcFunc['db_free_result']($request);
882
883
		// Make sure we don't load unnecessary data.
884
		if ($context['display_mode'] == 1)
885
		{
886
			foreach ($posters as $k => $v)
887
				if (!in_array($k, $display_pms))
888
					unset($posters[$k]);
889
		}
890
891
		// Load any users....
892
		loadMemberData($posters);
893
894
		// If we're on grouped/restricted view get a restricted list of messages.
895
		if ($context['display_mode'] != 0)
896
		{
897
			// Get the order right.
898
			$orderBy = array();
899
			foreach (array_reverse($pms) as $pm)
900
				$orderBy[] = 'pm.id_pm = ' . $pm;
901
902
			// Seperate query for these bits!
903
			$subjects_request = $smcFunc['db_query']('', '
904
				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,
905
					mem.id_member
906
				FROM {db_prefix}personal_messages AS pm
907
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
908
				WHERE pm.id_pm IN ({array_int:pm_list})
909
				ORDER BY ' . implode(', ', $orderBy) . '
910
				LIMIT {int:limit}',
911
				array(
912
					'pm_list' => $pms,
913
					'limit' => count($pms),
914
				)
915
			);
916
		}
917
918
		$group_by = $context['folder'] == 'sent' ? '
919
			GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' .
920
				($context['sort_by'] == 'name' ? ', mem.real_name' : '')
921
		: '';
922
923
		// Execute the query!
924
		$messages_request = $smcFunc['db_query']('', '
925
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
926
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '
927
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') . ($context['sort_by'] == 'name' ? '
928
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})' : '') . '
929
			WHERE pm.id_pm IN ({array_int:display_pms})' .
930
			$group_by . '
931
			ORDER BY ' . ($context['display_mode'] == 2 ? 'pm.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . '
932
			LIMIT {int:limit}',
933
			array(
934
				'display_pms' => $display_pms,
935
				'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
936
				'limit' => count($display_pms),
937
				'sort' => $_GET['sort'],
938
			)
939
		);
940
941
		// Build the conversation button array.
942
		if ($context['display_mode'] == 2)
943
		{
944
			$context['conversation_buttons'] = array(
945
				'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'),
946
			);
947
948
			// Allow mods to add additional buttons here
949
			call_integration_hook('integrate_conversation_buttons');
950
		}
951
	}
952
	else
953
		$messages_request = false;
954
955
	$context['can_send_pm'] = allowedTo('pm_send');
956
	$context['can_send_email'] = allowedTo('moderate_forum');
957
	$context['sub_template'] = 'folder';
958
	$context['page_title'] = $txt['pm_inbox'];
959
960
	// Finally mark the relevant messages as read.
961
	if ($context['folder'] != 'sent' && !empty($context['labels'][(int) $context['current_label_id']]['unread_messages']))
962
	{
963
		// If the display mode is "old sk00l" do them all...
964
		if ($context['display_mode'] == 0)
965
			markMessages(null, $context['current_label_id']);
966
		// Otherwise do just the current one!
967
		elseif (!empty($context['current_pm']))
968
			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...
969
	}
970
}
971
972
/**
973
 * Get a personal message for the theme.  (used to save memory.)
974
 *
975
 * @param string $type The type of message
976
 * @param bool $reset Whether to reset the internal pointer
977
 * @return bool|array False on failure, otherwise an array of info
978
 */
979
function prepareMessageContext($type = 'subject', $reset = false)
980
{
981
	global $txt, $scripturl, $modSettings, $context, $messages_request, $memberContext, $recipients, $smcFunc;
982
	global $user_info, $subjects_request;
983
984
	// Count the current message number....
985
	static $counter = null;
986
	if ($counter === null || $reset)
987
		$counter = $context['start'];
988
989
	static $temp_pm_selected = null;
990
	if ($temp_pm_selected === null)
991
	{
992
		$temp_pm_selected = isset($_SESSION['pm_selected']) ? $_SESSION['pm_selected'] : array();
993
		$_SESSION['pm_selected'] = array();
994
	}
995
996
	// If we're in non-boring view do something exciting!
997
	if ($context['display_mode'] != 0 && $subjects_request && $type == 'subject')
998
	{
999
		$subject = $smcFunc['db_fetch_assoc']($subjects_request);
1000
		if (!$subject)
1001
		{
1002
			$smcFunc['db_free_result']($subjects_request);
1003
			return false;
1004
		}
1005
1006
		$subject['subject'] = $subject['subject'] == '' ? $txt['no_subject'] : $subject['subject'];
1007
		censorText($subject['subject']);
1008
1009
		$output = array(
1010
			'id' => $subject['id_pm'],
1011
			'member' => array(
1012
				'id' => $subject['id_member_from'],
1013
				'name' => $subject['from_name'],
1014
				'link' => ($subject['id_member_from'] != 0) ? '<a href="' . $scripturl . '?action=profile;u=' . $subject['id_member_from'] . '">' . $subject['from_name'] . '</a>' : $subject['from_name'],
1015
			),
1016
			'recipients' => &$recipients[$subject['id_pm']],
1017
			'subject' => $subject['subject'],
1018
			'time' => timeformat($subject['msgtime']),
1019
			'timestamp' => $subject['msgtime'],
1020
			'number_recipients' => count($recipients[$subject['id_pm']]['to']),
1021
			'labels' => &$context['message_labels'][$subject['id_pm']],
1022
			'fully_labeled' => count(isset($context['message_labels'][$subject['id_pm']]) ? $context['message_labels'][$subject['id_pm']] : array()) == count($context['labels']),
1023
			'is_replied_to' => &$context['message_replied'][$subject['id_pm']],
1024
			'is_unread' => &$context['message_unread'][$subject['id_pm']],
1025
			'is_selected' => !empty($temp_pm_selected) && in_array($subject['id_pm'], $temp_pm_selected),
1026
		);
1027
1028
		return $output;
1029
	}
1030
1031
	// Bail if it's false, ie. no messages.
1032
	if ($messages_request == false)
1033
		return false;
1034
1035
	// Reset the data?
1036
	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...
1037
		return @$smcFunc['db_data_seek']($messages_request, 0);
1038
1039
	// Get the next one... bail if anything goes wrong.
1040
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1041
	if (!$message)
1042
	{
1043
		if ($type != 'subject')
1044
			$smcFunc['db_free_result']($messages_request);
1045
1046
		return false;
1047
	}
1048
1049
	// Use '(no subject)' if none was specified.
1050
	$message['subject'] = $message['subject'] == '' ? $txt['no_subject'] : $message['subject'];
1051
1052
	// Load the message's information - if it's not there, load the guest information.
1053
	if (!loadMemberContext($message['id_member_from'], true))
1054
	{
1055
		$memberContext[$message['id_member_from']]['name'] = $message['from_name'];
1056
		$memberContext[$message['id_member_from']]['id'] = 0;
1057
1058
		// Sometimes the forum sends messages itself (Warnings are an example) - in this case don't label it from a guest.
1059
		$memberContext[$message['id_member_from']]['group'] = $message['from_name'] == $context['forum_name_html_safe'] ? '' : $txt['guest_title'];
1060
		$memberContext[$message['id_member_from']]['link'] = $message['from_name'];
1061
		$memberContext[$message['id_member_from']]['email'] = '';
1062
		$memberContext[$message['id_member_from']]['show_email'] = false;
1063
		$memberContext[$message['id_member_from']]['is_guest'] = true;
1064
	}
1065
	else
1066
	{
1067
		$memberContext[$message['id_member_from']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member_from'] == $user_info['id'] && !$user_info['is_guest']);
1068
		$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'])));
1069
		// Show the email if it's your own PM
1070
		$memberContext[$message['id_member_from']]['show_email'] |= $message['id_member_from'] == $user_info['id'];
1071
	}
1072
1073
	$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']);
1074
1075
	// Censor all the important text...
1076
	censorText($message['body']);
1077
	censorText($message['subject']);
1078
1079
	// Run UBBC interpreter on the message.
1080
	$message['body'] = parse_bbc($message['body'], true, 'pm' . $message['id_pm']);
1081
1082
	// Send the array.
1083
	$output = array(
1084
		'id' => $message['id_pm'],
1085
		'member' => &$memberContext[$message['id_member_from']],
1086
		'subject' => $message['subject'],
1087
		'time' => timeformat($message['msgtime']),
1088
		'timestamp' => $message['msgtime'],
1089
		'counter' => $counter,
1090
		'body' => $message['body'],
1091
		'recipients' => &$recipients[$message['id_pm']],
1092
		'number_recipients' => count($recipients[$message['id_pm']]['to']),
1093
		'labels' => &$context['message_labels'][$message['id_pm']],
1094
		'fully_labeled' => count(isset($context['message_labels'][$message['id_pm']]) ? $context['message_labels'][$message['id_pm']] : array()) == count($context['labels']),
1095
		'is_replied_to' => &$context['message_replied'][$message['id_pm']],
1096
		'is_unread' => &$context['message_unread'][$message['id_pm']],
1097
		'is_selected' => !empty($temp_pm_selected) && in_array($message['id_pm'], $temp_pm_selected),
1098
		'is_message_author' => $message['id_member_from'] == $user_info['id'],
1099
		'can_report' => !empty($modSettings['enableReportPM']),
1100
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
1101
	);
1102
1103
	$counter++;
1104
1105
	// Any custom profile fields?
1106
	if (!empty($memberContext[$message['id_member_from']]['custom_fields']))
1107
		foreach ($memberContext[$message['id_member_from']]['custom_fields'] as $custom)
1108
			$output['custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
1109
1110
	$output['quickbuttons'] = array(
1111
		'reply_to_all' => array(
1112
			'label' => $txt['reply_to_all'],
1113
			'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',
1114
			'icon' => 'reply_all_button',
1115
			'show' => $context['can_send_pm'] && !$output['member']['is_guest'] && ($output['number_recipients'] > 1 || $output['member']['id'] == $user_info['id']),
1116
		),
1117
		'reply' => array(
1118
			'label' => $txt['reply'],
1119
			'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'],
1120
			'icon' => 'reply_button',
1121
			'show' => $context['can_send_pm'] && !$output['member']['is_guest'] && $output['member']['id'] != $user_info['id'],
1122
		),
1123
		'quote' => array(
1124
			'label' => $txt['quote_action'],
1125
			'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'] : '')),
1126
			'icon' => 'quote',
1127
			'show' => $context['can_send_pm'],
1128
		),
1129
		'delete' => array(
1130
			'label' => $txt['delete'],
1131
			'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'],
1132
			'javascript' => 'data-confirm="' . JavaScriptEscape($txt['remove_message_question']) . '"',
1133
			'class' => 'you_sure',
1134
			'icon' => 'remove_button',
1135
		),
1136
		'more' => array(
1137
			'report' => array(
1138
				'label' => $txt['pm_report_to_admin'],
1139
				'href' => $scripturl . '?action=pm;sa=report;l=' . $context['current_label_id'] . ';pmsg=' . $output['id'],
1140
				'icon' => 'error',
1141
				'show' => $output['can_report']
1142
			),
1143
		),
1144
		'quickmod' => array(
1145
			'class' => 'inline_mod_check',
1146
			'content' => '<input type="checkbox" name="pms[]" id="deletedisplay' . $output['id'] . '" value="' . $output['id'] . '" onclick="document.getElementById(\'deletelisting' . $output['id'] . '\').checked = this.checked;">',
1147
			'show' => empty($context['display_mode'])
1148
		)
1149
	);
1150
1151
	call_integration_hook('integrate_prepare_pm_context', array(&$output, &$message, $counter));
1152
1153
	return $output;
1154
}
1155
1156
/**
1157
 * Allows searching through personal messages.
1158
 */
1159
function MessageSearch()
1160
{
1161
	global $context, $txt, $scripturl, $smcFunc;
1162
1163
	if (isset($_REQUEST['params']))
1164
	{
1165
		$temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
1166
		$context['search_params'] = array();
1167
		foreach ($temp_params as $i => $data)
1168
		{
1169
			@list ($k, $v) = explode('|\'|', $data);
1170
			$context['search_params'][$k] = $v;
1171
		}
1172
	}
1173
	if (isset($_REQUEST['search']))
1174
		$context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']);
1175
1176
	if (isset($context['search_params']['search']))
1177
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1178
	if (isset($context['search_params']['userspec']))
1179
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1180
1181
	if (!empty($context['search_params']['searchtype']))
1182
		$context['search_params']['searchtype'] = 2;
1183
1184
	if (!empty($context['search_params']['minage']))
1185
		$context['search_params']['minage'] = (int) $context['search_params']['minage'];
1186
1187
	if (!empty($context['search_params']['maxage']))
1188
		$context['search_params']['maxage'] = (int) $context['search_params']['maxage'];
1189
1190
	$context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']);
1191
	$context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']);
1192
1193
	// Create the array of labels to be searched.
1194
	$context['search_labels'] = array();
1195
	$searchedLabels = isset($context['search_params']['labels']) && $context['search_params']['labels'] != '' ? explode(',', $context['search_params']['labels']) : array();
1196
	foreach ($context['labels'] as $label)
1197
	{
1198
		$context['search_labels'][] = array(
1199
			'id' => $label['id'],
1200
			'name' => $label['name'],
1201
			'checked' => !empty($searchedLabels) ? in_array($label['id'], $searchedLabels) : true,
1202
		);
1203
	}
1204
1205
	// Are all the labels checked?
1206
	$context['check_all'] = empty($searchedLabels) || count($context['search_labels']) == count($searchedLabels);
1207
1208
	// Load the error text strings if there were errors in the search.
1209
	if (!empty($context['search_errors']))
1210
	{
1211
		loadLanguage('Errors');
1212
		$context['search_errors']['messages'] = array();
1213
		foreach ($context['search_errors'] as $search_error => $dummy)
1214
		{
1215
			if ($search_error == 'messages')
1216
				continue;
1217
1218
			$context['search_errors']['messages'][] = $txt['error_' . $search_error];
1219
		}
1220
	}
1221
1222
	$context['page_title'] = $txt['pm_search_title'];
1223
	$context['sub_template'] = 'search';
1224
	$context['linktree'][] = array(
1225
		'url' => $scripturl . '?action=pm;sa=search',
1226
		'name' => $txt['pm_search_bar_title'],
1227
	);
1228
}
1229
1230
/**
1231
 * Actually do the search of personal messages.
1232
 */
1233
function MessageSearch2()
1234
{
1235
	global $scripturl, $modSettings, $user_info, $context, $txt;
1236
	global $memberContext, $smcFunc;
1237
1238
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search'])
1239
		fatal_lang_error('loadavg_search_disabled', false);
1240
1241
	/**
1242
	 * @todo For the moment force the folder to the inbox.
1243
	 * @todo Maybe set the inbox based on a cookie or theme setting?
1244
	 */
1245
	$context['folder'] = 'inbox';
1246
1247
	// Some useful general permissions.
1248
	$context['can_send_pm'] = allowedTo('pm_send');
1249
1250
	// Some hardcoded veriables that can be tweaked if required.
1251
	$maxMembersToSearch = 500;
1252
1253
	// Extract all the search parameters.
1254
	$search_params = array();
1255
	if (isset($_REQUEST['params']))
1256
	{
1257
		$temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
1258
		foreach ($temp_params as $i => $data)
1259
		{
1260
			@list ($k, $v) = explode('|\'|', $data);
1261
			$search_params[$k] = $v;
1262
		}
1263
	}
1264
1265
	$context['start'] = isset($_GET['start']) ? (int) $_GET['start'] : 0;
1266
1267
	// Store whether simple search was used (needed if the user wants to do another query).
1268
	if (!isset($search_params['advanced']))
1269
		$search_params['advanced'] = empty($_REQUEST['advanced']) ? 0 : 1;
1270
1271
	// 1 => 'allwords' (default, don't set as param) / 2 => 'anywords'.
1272
	if (!empty($search_params['searchtype']) || (!empty($_REQUEST['searchtype']) && $_REQUEST['searchtype'] == 2))
1273
		$search_params['searchtype'] = 2;
1274
1275
	// Minimum age of messages. Default to zero (don't set param in that case).
1276
	if (!empty($search_params['minage']) || (!empty($_REQUEST['minage']) && $_REQUEST['minage'] > 0))
1277
		$search_params['minage'] = !empty($search_params['minage']) ? (int) $search_params['minage'] : (int) $_REQUEST['minage'];
1278
1279
	// Maximum age of messages. Default to infinite (9999 days: param not set).
1280
	if (!empty($search_params['maxage']) || (!empty($_REQUEST['maxage']) && $_REQUEST['maxage'] != 9999))
1281
		$search_params['maxage'] = !empty($search_params['maxage']) ? (int) $search_params['maxage'] : (int) $_REQUEST['maxage'];
1282
1283
	$search_params['subject_only'] = !empty($search_params['subject_only']) || !empty($_REQUEST['subject_only']);
1284
	$search_params['show_complete'] = !empty($search_params['show_complete']) || !empty($_REQUEST['show_complete']);
1285
1286
	// Default the user name to a wildcard matching every user (*).
1287
	if (!empty($search_params['user_spec']) || (!empty($_REQUEST['userspec']) && $_REQUEST['userspec'] != '*'))
1288
		$search_params['userspec'] = isset($search_params['userspec']) ? $search_params['userspec'] : $_REQUEST['userspec'];
1289
1290
	// This will be full of all kinds of parameters!
1291
	$searchq_parameters = array();
1292
1293
	// If there's no specific user, then don't mention it in the main query.
1294
	if (empty($search_params['userspec']))
1295
		$userQuery = '';
1296
	else
1297
	{
1298
		$userString = strtr($smcFunc['htmlspecialchars']($search_params['userspec'], ENT_QUOTES), array('&quot;' => '"'));
1299
		$userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_'));
1300
1301
		preg_match_all('~"([^"]+)"~', $userString, $matches);
1302
		$possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString)));
1303
1304
		for ($k = 0, $n = count($possible_users); $k < $n; $k++)
1305
		{
1306
			$possible_users[$k] = trim($possible_users[$k]);
1307
1308
			if (strlen($possible_users[$k]) == 0)
1309
				unset($possible_users[$k]);
1310
		}
1311
1312
		if (!empty($possible_users))
1313
		{
1314
			// We need to bring this into the query and do it nice and cleanly.
1315
			$where_params = array();
1316
			$where_clause = array();
1317
			foreach ($possible_users as $k => $v)
1318
			{
1319
				$where_params['name_' . $k] = $v;
1320
				$where_clause[] = '{raw:real_name} LIKE {string:name_' . $k . '}';
1321
				if (!isset($where_params['real_name']))
1322
					$where_params['real_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name';
1323
			}
1324
1325
			// Who matches those criteria?
1326
			// @todo This doesn't support sent item searching.
1327
			$request = $smcFunc['db_query']('', '
1328
				SELECT id_member
1329
				FROM {db_prefix}members
1330
				WHERE ' . implode(' OR ', $where_clause),
1331
				$where_params
1332
			);
1333
1334
			// Simply do nothing if there're too many members matching the criteria.
1335
			if ($smcFunc['db_num_rows']($request) > $maxMembersToSearch)
1336
				$userQuery = '';
1337
			elseif ($smcFunc['db_num_rows']($request) == 0)
1338
			{
1339
				$userQuery = 'AND pm.id_member_from = 0 AND ({raw:pm_from_name} LIKE {raw:guest_user_name_implode})';
1340
				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
1341
				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
1342
			}
1343
			else
1344
			{
1345
				$memberlist = array();
1346
				while ($row = $smcFunc['db_fetch_assoc']($request))
1347
					$memberlist[] = $row['id_member'];
1348
				$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})))';
1349
				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
1350
				$searchq_parameters['member_list'] = $memberlist;
1351
				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
1352
			}
1353
			$smcFunc['db_free_result']($request);
1354
		}
1355
		else
1356
			$userQuery = '';
1357
	}
1358
1359
	// Setup the sorting variables...
1360
	// @todo Add more in here!
1361
	$sort_columns = array(
1362
		'pm.id_pm',
1363
	);
1364
	if (empty($search_params['sort']) && !empty($_REQUEST['sort']))
1365
		list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, '');
1366
	$search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'pm.id_pm';
1367
	$search_params['sort_dir'] = !empty($search_params['sort_dir']) && $search_params['sort_dir'] == 'asc' ? 'asc' : 'desc';
1368
1369
	// Sort out any labels we may be searching by.
1370
	$context['search_in'] = array();
1371
	$labelQuery = '';
1372
	$labelJoin = '';
1373
	if ($context['folder'] == 'inbox' && !empty($search_params['advanced']) && $context['currently_using_labels'])
1374
	{
1375
		// Came here from pagination?  Put them back into $_REQUEST for sanitization.
1376
		if (isset($search_params['labels']))
1377
			$_REQUEST['searchlabel'] = explode(',', $search_params['labels']);
1378
1379
		// Assuming we have some labels - make them all integers.
1380
		if (!empty($_REQUEST['searchlabel']) && is_array($_REQUEST['searchlabel']))
1381
		{
1382
			foreach ($_REQUEST['searchlabel'] as $key => $id)
1383
				$_REQUEST['searchlabel'][$key] = (int) $id;
1384
		}
1385
		else
1386
			$_REQUEST['searchlabel'] = array();
1387
1388
		// Now that everything is cleaned up a bit, make the labels a param.
1389
		$search_params['labels'] = implode(',', $_REQUEST['searchlabel']);
1390
1391
		// No labels selected? That must be an error!
1392
		if (empty($_REQUEST['searchlabel']))
1393
			$context['search_errors']['no_labels_selected'] = true;
1394
		// Otherwise prepare the query!
1395
		elseif (count($_REQUEST['searchlabel']) != count($context['labels']))
1396
		{
1397
			// Special case here... "inbox" isn't a real label anymore...
1398
			if (in_array(-1, $_REQUEST['searchlabel']))
1399
			{
1400
				$context['search_in'][] = $context['labels'][-1]['name'];
1401
1402
				$labelQuery = '	AND pmr.in_inbox = {int:in_inbox}';
1403
				$searchq_parameters['in_inbox'] = 1;
1404
1405
				// Now we get rid of that...
1406
				$temp = array_diff($_REQUEST['searchlabel'], array(-1));
1407
				$_REQUEST['searchlabel'] = $temp;
1408
			}
1409
1410
			// Still have something?
1411
			if (!empty($_REQUEST['searchlabel']))
1412
			{
1413
				if ($labelQuery == '')
1414
				{
1415
					// Not searching the inbox - PM must be labeled
1416
					$labelQuery = ' AND pml.id_label IN ({array_int:labels})';
1417
					$labelJoin = ' INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1418
				}
1419
				else
1420
				{
1421
					// Searching the inbox - PM doesn't have to be labeled
1422
					$labelQuery = ' AND (' . substr($labelQuery, 5) . ' OR pml.id_label IN ({array_int:labels}))';
1423
					$labelJoin = ' LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1424
				}
1425
1426
				$searchq_parameters['labels'] = $_REQUEST['searchlabel'];
1427
1428
				foreach ($_REQUEST['searchlabel'] as $label_key)
1429
					$context['search_in'][] = $context['labels'][$label_key]['name'];
1430
			}
1431
		}
1432
	}
1433
1434
	if (empty($context['search_in']))
1435
		$context['search_in'][] = $context['folder'];
1436
1437
	// What are we actually searching for?
1438
	$search_params['search'] = !empty($search_params['search']) ? $search_params['search'] : (isset($_REQUEST['search']) ? $_REQUEST['search'] : '');
1439
	// If we ain't got nothing - we should error!
1440
	if (!isset($search_params['search']) || $search_params['search'] == '')
1441
		$context['search_errors']['invalid_search_string'] = true;
1442
1443
	// Extract phrase parts first (e.g. some words "this is a phrase" some more words.)
1444
	preg_match_all('~(?:^|\s)([-]?)"([^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), $search_params['search'], $matches, PREG_PATTERN_ORDER);
1445
	$searchArray = $matches[2];
1446
1447
	// Remove the phrase parts and extract the words.
1448
	$tempSearch = explode(' ', preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']));
1449
1450
	// A minus sign in front of a word excludes the word.... so...
1451
	$excludedWords = array();
1452
1453
	// .. first, we check for things like -"some words", but not "-some words".
1454
	foreach ($matches[1] as $index => $word)
1455
		if ($word == '-')
1456
		{
1457
			$word = $smcFunc['strtolower'](trim($searchArray[$index]));
1458
			if (strlen($word) > 0)
1459
				$excludedWords[] = $word;
1460
			unset($searchArray[$index]);
1461
		}
1462
1463
	// Now we look for -test, etc.... normaller.
1464
	foreach ($tempSearch as $index => $word)
1465
	{
1466
		if (strpos(trim($word), '-') === 0)
1467
		{
1468
			$word = substr($smcFunc['strtolower']($word), 1);
1469
			if (strlen($word) > 0)
1470
				$excludedWords[] = $word;
1471
			unset($tempSearch[$index]);
1472
		}
1473
	}
1474
1475
	$searchArray = array_merge($searchArray, $tempSearch);
1476
1477
	// Trim everything and make sure there are no words that are the same.
1478
	foreach ($searchArray as $index => $value)
1479
	{
1480
		$searchArray[$index] = $smcFunc['strtolower'](trim($value));
1481
		if ($searchArray[$index] == '')
1482
			unset($searchArray[$index]);
1483
		else
1484
		{
1485
			// Sort out entities first.
1486
			$searchArray[$index] = $smcFunc['htmlspecialchars']($searchArray[$index]);
1487
		}
1488
	}
1489
	$searchArray = array_unique($searchArray);
1490
1491
	// Create an array of replacements for highlighting.
1492
	$context['mark'] = array();
1493
	foreach ($searchArray as $word)
1494
		$context['mark'][$word] = '<strong class="highlight">' . $word . '</strong>';
1495
1496
	// This contains *everything*
1497
	$searchWords = array_merge($searchArray, $excludedWords);
1498
1499
	// Make sure at least one word is being searched for.
1500
	if (empty($searchArray))
1501
		$context['search_errors']['invalid_search_string'] = true;
1502
1503
	// Sort out the search query so the user can edit it - if they want.
1504
	$context['search_params'] = $search_params;
1505
	if (isset($context['search_params']['search']))
1506
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1507
	if (isset($context['search_params']['userspec']))
1508
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1509
1510
	// Now we have all the parameters, combine them together for pagination and the like...
1511
	$context['params'] = array();
1512
	foreach ($search_params as $k => $v)
1513
		$context['params'][] = $k . '|\'|' . $v;
1514
	$context['params'] = base64_encode(implode('|"|', $context['params']));
1515
1516
	// Compile the subject query part.
1517
	$andQueryParts = array();
1518
1519
	foreach ($searchWords as $index => $word)
1520
	{
1521
		if ($word == '')
1522
			continue;
1523
1524
		if ($search_params['subject_only'])
1525
			$andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}';
1526
		else
1527
			$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 . '})';
1528
		$searchq_parameters['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%';
1529
	}
1530
1531
	$searchQuery = ' 1=1';
1532
	if (!empty($andQueryParts))
1533
		$searchQuery = implode(!empty($search_params['searchtype']) && $search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts);
1534
1535
	// Age limits?
1536
	$timeQuery = '';
1537
	if (!empty($search_params['minage']))
1538
		$timeQuery .= ' AND pm.msgtime < ' . (time() - $search_params['minage'] * 86400);
1539
	if (!empty($search_params['maxage']))
1540
		$timeQuery .= ' AND pm.msgtime > ' . (time() - $search_params['maxage'] * 86400);
1541
1542
	// If we have errors - return back to the first screen...
1543
	if (!empty($context['search_errors']))
1544
	{
1545
		$_REQUEST['params'] = $context['params'];
1546
		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...
1547
	}
1548
1549
	// Get the amount of results.
1550
	$request = $smcFunc['db_query']('', '
1551
		SELECT COUNT(*)
1552
		FROM {db_prefix}pm_recipients AS pmr
1553
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1554
			' . $labelJoin . '
1555
		WHERE ' . ($context['folder'] == 'inbox' ? '
1556
			pmr.id_member = {int:current_member}
1557
			AND pmr.deleted = {int:not_deleted}' : '
1558
			pm.id_member_from = {int:current_member}
1559
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1560
			' . $userQuery . $labelQuery . $timeQuery . '
1561
			AND (' . $searchQuery . ')',
1562
		array_merge($searchq_parameters, array(
1563
			'current_member' => $user_info['id'],
1564
			'not_deleted' => 0,
1565
		))
1566
	);
1567
	list ($numResults) = $smcFunc['db_fetch_row']($request);
1568
	$smcFunc['db_free_result']($request);
1569
1570
	// Get all the matching messages... using standard search only (No caching and the like!)
1571
	// @todo This doesn't support sent item searching yet.
1572
	$request = $smcFunc['db_query']('', '
1573
		SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
1574
		FROM {db_prefix}pm_recipients AS pmr
1575
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1576
			' . $labelJoin . '
1577
		WHERE ' . ($context['folder'] == 'inbox' ? '
1578
			pmr.id_member = {int:current_member}
1579
			AND pmr.deleted = {int:not_deleted}' : '
1580
			pm.id_member_from = {int:current_member}
1581
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1582
			' . $userQuery . $labelQuery . $timeQuery . '
1583
			AND (' . $searchQuery . ')
1584
		ORDER BY {raw:sort} {raw:sort_dir}
1585
		LIMIT {int:start}, {int:max}',
1586
		array_merge($searchq_parameters, array(
1587
			'current_member' => $user_info['id'],
1588
			'not_deleted' => 0,
1589
			'sort' => $search_params['sort'],
1590
			'sort_dir' => $search_params['sort_dir'],
1591
			'start' => $context['start'],
1592
			'max' => $modSettings['search_results_per_page'],
1593
		))
1594
	);
1595
	$foundMessages = array();
1596
	$posters = array();
1597
	$head_pms = array();
1598
	while ($row = $smcFunc['db_fetch_assoc']($request))
1599
	{
1600
		$foundMessages[] = $row['id_pm'];
1601
		$posters[] = $row['id_member_from'];
1602
		$head_pms[$row['id_pm']] = $row['id_pm_head'];
1603
	}
1604
	$smcFunc['db_free_result']($request);
1605
1606
	// Find the real head pms!
1607
	if ($context['display_mode'] == 2 && !empty($head_pms))
1608
	{
1609
		$request = $smcFunc['db_query']('', '
1610
			SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
1611
			FROM {db_prefix}personal_messages AS pm
1612
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
1613
			WHERE pm.id_pm_head IN ({array_int:head_pms})
1614
				AND pmr.id_member = {int:current_member}
1615
				AND pmr.deleted = {int:not_deleted}
1616
			GROUP BY pm.id_pm_head
1617
			LIMIT {int:limit}',
1618
			array(
1619
				'head_pms' => array_unique($head_pms),
1620
				'current_member' => $user_info['id'],
1621
				'not_deleted' => 0,
1622
				'limit' => count($head_pms),
1623
			)
1624
		);
1625
		$real_pm_ids = array();
1626
		while ($row = $smcFunc['db_fetch_assoc']($request))
1627
			$real_pm_ids[$row['id_pm_head']] = $row['id_pm'];
1628
		$smcFunc['db_free_result']($request);
1629
	}
1630
1631
	// Load the users...
1632
	loadMemberData($posters);
1633
1634
	// Sort out the page index.
1635
	$context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=search2;params=' . $context['params'], $_GET['start'], $numResults, $modSettings['search_results_per_page'], false);
1636
1637
	$context['num_results'] = $numResults;
1638
1639
	$context['message_labels'] = array();
1640
	$context['message_replied'] = array();
1641
	$context['personal_messages'] = array();
1642
1643
	if (!empty($foundMessages))
1644
	{
1645
		// Now get recipients (but don't include bcc-recipients for your inbox, you're not supposed to know :P!)
1646
		$request = $smcFunc['db_query']('', '
1647
			SELECT
1648
				pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name,
1649
				pmr.bcc, pmr.in_inbox, pmr.is_read
1650
			FROM {db_prefix}pm_recipients AS pmr
1651
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
1652
			WHERE pmr.id_pm IN ({array_int:message_list})',
1653
			array(
1654
				'message_list' => $foundMessages,
1655
			)
1656
		);
1657
		while ($row = $smcFunc['db_fetch_assoc']($request))
1658
		{
1659
			if ($context['folder'] == 'sent' || empty($row['bcc']))
1660
				$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>';
1661
1662
			if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
1663
			{
1664
				$context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
1665
1666
				$row['labels'] = '';
1667
1668
				// Get the labels for this PM
1669
				$request2 = $smcFunc['db_query']('', '
1670
					SELECT id_label
1671
					FROM {db_prefix}pm_labeled_messages
1672
					WHERE id_pm = {int:current_pm}',
1673
					array(
1674
						'current_pm' => $row['id_pm'],
1675
					)
1676
				);
1677
1678
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
1679
				{
1680
					$l_id = $row2['id_label'];
1681
					if (isset($context['labels'][$l_id]))
1682
						$context['message_labels'][$row['id_pm']][$l_id] = array('id' => $l_id, 'name' => $context['labels'][$l_id]['name']);
1683
1684
					// Here we find the first label on a message - for linking to posts in results
1685
					if (!isset($context['first_label'][$row['id_pm']]) && $row['in_inbox'] != 1)
1686
						$context['first_label'][$row['id_pm']] = $l_id;
1687
				}
1688
1689
				$smcFunc['db_free_result']($request2);
1690
1691
				// Is this in the inbox as well?
1692
				if ($row['in_inbox'] == 1)
1693
				{
1694
					$context['message_labels'][$row['id_pm']][-1] = array('id' => -1, 'name' => $context['labels'][-1]['name']);
1695
				}
1696
1697
				$row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']);
1698
			}
1699
		}
1700
1701
		// Prepare the query for the callback!
1702
		$request = $smcFunc['db_query']('', '
1703
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
1704
			FROM {db_prefix}personal_messages AS pm
1705
			WHERE pm.id_pm IN ({array_int:message_list})
1706
			ORDER BY {raw:sort} {raw:sort_dir}
1707
			LIMIT {int:limit}',
1708
			array(
1709
				'message_list' => $foundMessages,
1710
				'limit' => count($foundMessages),
1711
				'sort' => $search_params['sort'],
1712
				'sort_dir' => $search_params['sort_dir'],
1713
			)
1714
		);
1715
		$counter = 0;
1716
		while ($row = $smcFunc['db_fetch_assoc']($request))
1717
		{
1718
			// If there's no message subject, use the default.
1719
			$row['subject'] = $row['subject'] == '' ? $txt['no_subject'] : $row['subject'];
1720
1721
			// Load this posters context info, if it ain't there then fill in the essentials...
1722
			if (!loadMemberContext($row['id_member_from'], true))
1723
			{
1724
				$memberContext[$row['id_member_from']]['name'] = $row['from_name'];
1725
				$memberContext[$row['id_member_from']]['id'] = 0;
1726
				$memberContext[$row['id_member_from']]['group'] = $txt['guest_title'];
1727
				$memberContext[$row['id_member_from']]['link'] = $row['from_name'];
1728
				$memberContext[$row['id_member_from']]['email'] = '';
1729
				$memberContext[$row['id_member_from']]['is_guest'] = true;
1730
			}
1731
			else
1732
			{
1733
				$memberContext[$row['id_member_from']]['can_view_profile'] = allowedTo('profile_view') || ($row['id_member_from'] == $user_info['id'] && !$user_info['is_guest']);
1734
				$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'])));
1735
				// Show the email if it's your own PM
1736
				$memberContext[$row['id_member_from']]['show_email'] |= $row['id_member_from'] == $user_info['id'];
1737
			}
1738
1739
			$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']);
1740
1741
			// Censor anything we don't want to see...
1742
			censorText($row['body']);
1743
			censorText($row['subject']);
1744
1745
			// Parse out any BBC...
1746
			$row['body'] = parse_bbc($row['body'], true, 'pm' . $row['id_pm']);
1747
1748
			$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'];
1749
			$context['personal_messages'][] = array(
1750
				'id' => $row['id_pm'],
1751
				'member' => &$memberContext[$row['id_member_from']],
1752
				'subject' => $row['subject'],
1753
				'body' => $row['body'],
1754
				'time' => timeformat($row['msgtime']),
1755
				'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...
1756
				'labels' => &$context['message_labels'][$row['id_pm']],
1757
				'fully_labeled' => count($context['message_labels'][$row['id_pm']]) == count($context['labels']),
1758
				'is_replied_to' => &$context['message_replied'][$row['id_pm']],
1759
				'href' => $href,
1760
				'link' => '<a href="' . $href . '">' . $row['subject'] . '</a>',
1761
				'counter' => ++$counter,
1762
				'can_see_ip' => allowedTo('moderate_forum') || ($row['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
1763
				'quickbuttons' => array(
1764
					'reply_to_all' => array(
1765
						'label' => $txt['reply_to_all'],
1766
						'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',
1767
						'icon' => 'reply_all_button',
1768
						'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']),
1769
					),
1770
					'reply' => array(
1771
						'label' => $txt['reply'],
1772
						'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'],
1773
						'icon' => 'reply_button',
1774
						'show' => $context['can_send_pm'] && !$memberContext[$row['id_member_from']]['is_guest'] && $row['id_member_from'] != $user_info['id'],
1775
					),
1776
					'quote' => array(
1777
						'label' => $txt['quote_action'],
1778
						'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'] : '')),
1779
						'icon' => 'quote',
1780
						'show' => $context['can_send_pm'],
1781
					),
1782
					'delete' => array(
1783
						'label' => $txt['delete'],
1784
						'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'],
1785
						'javascript' => 'data-confirm="' . JavaScriptEscape($txt['remove_message_question']) . '"',
1786
						'class' => 'you_sure',
1787
						'icon' => 'remove_button',
1788
					),
1789
					'more' => array(
1790
						'report' => array(
1791
							'label' => $txt['pm_report_to_admin'],
1792
							'href' => $scripturl . '?action=pm;sa=report;l=' . $context['current_label_id'] . ';pmsg=' . $row['id_pm'],
1793
							'icon' => 'error',
1794
							'show' => !empty($modSettings['enableReportPM']),
1795
						),
1796
					),
1797
				)
1798
			);
1799
		}
1800
		$smcFunc['db_free_result']($request);
1801
	}
1802
1803
	call_integration_hook('integrate_search_pm_context');
1804
1805
	// Finish off the context.
1806
	$context['page_title'] = $txt['pm_search_title'];
1807
	$context['sub_template'] = 'search_results';
1808
	$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search';
1809
	$context['linktree'][] = array(
1810
		'url' => $scripturl . '?action=pm;sa=search',
1811
		'name' => $txt['pm_search_bar_title'],
1812
	);
1813
}
1814
1815
/**
1816
 * Send a new message?
1817
 */
1818
function MessagePost()
1819
{
1820
	global $txt, $sourcedir, $scripturl, $modSettings;
1821
	global $context, $smcFunc, $language, $user_info;
1822
1823
	isAllowedTo('pm_send');
1824
1825
	loadLanguage('PersonalMessage');
1826
	// Just in case it was loaded from somewhere else.
1827
	loadTemplate('PersonalMessage');
1828
	loadJavaScriptFile('PersonalMessage.js', array('defer' => false, 'minimize' => true), 'smf_pms');
1829
	loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
1830
	if ($context['drafts_autosave'])
1831
		loadJavaScriptFile('drafts.js', array('defer' => false, 'minimize' => true), 'smf_drafts');
1832
	$context['sub_template'] = 'send';
1833
1834
	// Extract out the spam settings - cause it's neat.
1835
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
1836
1837
	// Set the title...
1838
	$context['page_title'] = $txt['send_message'];
1839
1840
	$context['reply'] = isset($_REQUEST['pmsg']) || isset($_REQUEST['quote']);
1841
1842
	// Check whether we've gone over the limit of messages we can send per hour.
1843
	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')
1844
	{
1845
		// How many messages have they sent this last hour?
1846
		$request = $smcFunc['db_query']('', '
1847
			SELECT COUNT(pr.id_pm) AS post_count
1848
			FROM {db_prefix}personal_messages AS pm
1849
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
1850
			WHERE pm.id_member_from = {int:current_member}
1851
				AND pm.msgtime > {int:msgtime}',
1852
			array(
1853
				'current_member' => $user_info['id'],
1854
				'msgtime' => time() - 3600,
1855
			)
1856
		);
1857
		list ($postCount) = $smcFunc['db_fetch_row']($request);
1858
		$smcFunc['db_free_result']($request);
1859
1860
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
1861
			fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
1862
	}
1863
1864
	// Quoting/Replying to a message?
1865
	if (!empty($_REQUEST['pmsg']))
1866
	{
1867
		$pmsg = (int) $_REQUEST['pmsg'];
1868
1869
		// Make sure this is yours.
1870
		if (!isAccessiblePM($pmsg))
1871
			fatal_lang_error('no_access', false);
1872
1873
		// Work out whether this is one you've received?
1874
		$request = $smcFunc['db_query']('', '
1875
			SELECT
1876
				id_pm
1877
			FROM {db_prefix}pm_recipients
1878
			WHERE id_pm = {int:id_pm}
1879
				AND id_member = {int:current_member}
1880
			LIMIT 1',
1881
			array(
1882
				'current_member' => $user_info['id'],
1883
				'id_pm' => $pmsg,
1884
			)
1885
		);
1886
		$isReceived = $smcFunc['db_num_rows']($request) != 0;
1887
		$smcFunc['db_free_result']($request);
1888
1889
		// Get the quoted message (and make sure you're allowed to see this quote!).
1890
		$request = $smcFunc['db_query']('', '
1891
			SELECT
1892
				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,
1893
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
1894
				COALESCE(mem.real_name, pm.from_name) AS real_name
1895
			FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : '
1896
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . '
1897
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1898
			WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? '
1899
				AND pm.id_member_from = {int:current_member}' : '
1900
				AND pmr.id_member = {int:current_member}') . '
1901
			LIMIT 1',
1902
			array(
1903
				'current_member' => $user_info['id'],
1904
				'id_pm_head_empty' => 0,
1905
				'id_pm' => $pmsg,
1906
			)
1907
		);
1908
		if ($smcFunc['db_num_rows']($request) == 0)
1909
			fatal_lang_error('pm_not_yours', false);
1910
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
1911
		$smcFunc['db_free_result']($request);
1912
1913
		// Censor the message.
1914
		censorText($row_quoted['subject']);
1915
		censorText($row_quoted['body']);
1916
1917
		// Add 'Re: ' to it....
1918
		if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix')))
1919
		{
1920
			if ($language === $user_info['language'])
1921
				$context['response_prefix'] = $txt['response_prefix'];
1922
			else
1923
			{
1924
				loadLanguage('index', $language, false);
1925
				$context['response_prefix'] = $txt['response_prefix'];
1926
				loadLanguage('index');
1927
			}
1928
			cache_put_data('response_prefix', $context['response_prefix'], 600);
1929
		}
1930
		$form_subject = $row_quoted['subject'];
1931
		if ($context['reply'] && trim($context['response_prefix']) != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0)
1932
			$form_subject = $context['response_prefix'] . $form_subject;
1933
1934
		if (isset($_REQUEST['quote']))
1935
		{
1936
			// Remove any nested quotes and <br>...
1937
			$form_message = preg_replace('~<br ?/?' . '>~i', "\n", $row_quoted['body']);
1938
			if (!empty($modSettings['removeNestedQuotes']))
1939
				$form_message = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $form_message);
1940
			if (empty($row_quoted['id_member']))
1941
				$form_message = '[quote author=&quot;' . $row_quoted['real_name'] . '&quot;]' . "\n" . $form_message . "\n" . '[/quote]';
1942
			else
1943
				$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]';
1944
		}
1945
		else
1946
			$form_message = '';
1947
1948
		// Do the BBC thang on the message.
1949
		$row_quoted['body'] = parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']);
1950
1951
		// Set up the quoted message array.
1952
		$context['quoted_message'] = array(
1953
			'id' => $row_quoted['id_pm'],
1954
			'pm_head' => $row_quoted['pm_head'],
1955
			'member' => array(
1956
				'name' => $row_quoted['real_name'],
1957
				'username' => $row_quoted['member_name'],
1958
				'id' => $row_quoted['id_member'],
1959
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
1960
				'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'],
1961
			),
1962
			'subject' => $row_quoted['subject'],
1963
			'time' => timeformat($row_quoted['msgtime']),
1964
			'timestamp' => $row_quoted['msgtime'],
1965
			'body' => $row_quoted['body']
1966
		);
1967
	}
1968
	else
1969
	{
1970
		$context['quoted_message'] = false;
1971
		$form_subject = '';
1972
		$form_message = '';
1973
	}
1974
1975
	$context['recipients'] = array(
1976
		'to' => array(),
1977
		'bcc' => array(),
1978
	);
1979
1980
	// Sending by ID?  Replying to all?  Fetch the real_name(s).
1981
	if (isset($_REQUEST['u']))
1982
	{
1983
		// If the user is replying to all, get all the other members this was sent to..
1984
		if ($_REQUEST['u'] == 'all' && isset($row_quoted))
1985
		{
1986
			// Firstly, to reply to all we clearly already have $row_quoted - so have the original member from.
1987
			if ($row_quoted['id_member'] != $user_info['id'])
1988
				$context['recipients']['to'][] = array(
1989
					'id' => $row_quoted['id_member'],
1990
					'name' => $smcFunc['htmlspecialchars']($row_quoted['real_name']),
1991
				);
1992
1993
			// Now to get the others.
1994
			$request = $smcFunc['db_query']('', '
1995
				SELECT mem.id_member, mem.real_name
1996
				FROM {db_prefix}pm_recipients AS pmr
1997
					INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
1998
				WHERE pmr.id_pm = {int:id_pm}
1999
					AND pmr.id_member != {int:current_member}
2000
					AND pmr.bcc = {int:not_bcc}',
2001
				array(
2002
					'current_member' => $user_info['id'],
2003
					'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...
2004
					'not_bcc' => 0,
2005
				)
2006
			);
2007
			while ($row = $smcFunc['db_fetch_assoc']($request))
2008
				$context['recipients']['to'][] = array(
2009
					'id' => $row['id_member'],
2010
					'name' => $row['real_name'],
2011
				);
2012
			$smcFunc['db_free_result']($request);
2013
		}
2014
		else
2015
		{
2016
			$_REQUEST['u'] = explode(',', $_REQUEST['u']);
2017
			foreach ($_REQUEST['u'] as $key => $uID)
2018
				$_REQUEST['u'][$key] = (int) $uID;
2019
2020
			$_REQUEST['u'] = array_unique($_REQUEST['u']);
2021
2022
			$request = $smcFunc['db_query']('', '
2023
				SELECT id_member, real_name
2024
				FROM {db_prefix}members
2025
				WHERE id_member IN ({array_int:member_list})
2026
				LIMIT {int:limit}',
2027
				array(
2028
					'member_list' => $_REQUEST['u'],
2029
					'limit' => count($_REQUEST['u']),
2030
				)
2031
			);
2032
			while ($row = $smcFunc['db_fetch_assoc']($request))
2033
				$context['recipients']['to'][] = array(
2034
					'id' => $row['id_member'],
2035
					'name' => $row['real_name'],
2036
				);
2037
			$smcFunc['db_free_result']($request);
2038
		}
2039
2040
		// Get a literal name list in case the user has JavaScript disabled.
2041
		$names = array();
2042
		foreach ($context['recipients']['to'] as $to)
2043
			$names[] = $to['name'];
2044
		$context['to_value'] = empty($names) ? '' : '&quot;' . implode('&quot;, &quot;', $names) . '&quot;';
2045
	}
2046
	else
2047
		$context['to_value'] = '';
2048
2049
	// Set the defaults...
2050
	$context['subject'] = $form_subject;
2051
	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
2052
	$context['post_error'] = array();
2053
2054
	// And build the link tree.
2055
	$context['linktree'][] = array(
2056
		'url' => $scripturl . '?action=pm;sa=send',
2057
		'name' => $txt['new_message']
2058
	);
2059
2060
	// Generate a list of drafts that they can load in to the editor
2061
	if (!empty($context['drafts_pm_save']))
2062
	{
2063
		require_once($sourcedir . '/Drafts.php');
2064
		$pm_seed = isset($_REQUEST['pmsg']) ? $_REQUEST['pmsg'] : (isset($_REQUEST['quote']) ? $_REQUEST['quote'] : 0);
2065
		ShowDrafts($user_info['id'], $pm_seed, 1);
2066
	}
2067
2068
	// Needed for the WYSIWYG editor.
2069
	require_once($sourcedir . '/Subs-Editor.php');
2070
2071
	// Now create the editor.
2072
	$editorOptions = array(
2073
		'id' => 'message',
2074
		'value' => $context['message'],
2075
		'height' => '175px',
2076
		'width' => '100%',
2077
		'labels' => array(
2078
			'post_button' => $txt['send_message'],
2079
		),
2080
		'preview_type' => 2,
2081
		'required' => true,
2082
	);
2083
	create_control_richedit($editorOptions);
2084
2085
	// Store the ID for old compatibility.
2086
	$context['post_box_name'] = $editorOptions['id'];
2087
2088
	$context['bcc_value'] = '';
2089
2090
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2091
	if ($context['require_verification'])
2092
	{
2093
		$verificationOptions = array(
2094
			'id' => 'pm',
2095
		);
2096
		$context['require_verification'] = create_control_verification($verificationOptions);
2097
		$context['visual_verification_id'] = $verificationOptions['id'];
2098
	}
2099
2100
	call_integration_hook('integrate_pm_post');
2101
2102
	// Register this form and get a sequence number in $context.
2103
	checkSubmitOnce('register');
2104
}
2105
2106
/**
2107
 * This function allows the user to view their PM drafts
2108
 */
2109
function MessageDrafts()
2110
{
2111
	global $sourcedir, $user_info;
2112
2113
	// validate with loadMemberData()
2114
	$memberResult = loadMemberData($user_info['id'], false);
2115
	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...
2116
		fatal_lang_error('not_a_user', false);
2117
	list ($memID) = $memberResult;
2118
2119
	// drafts is where the functions reside
2120
	require_once($sourcedir . '/Drafts.php');
2121
	showPMDrafts($memID);
2122
}
2123
2124
/**
2125
 * An error in the message...
2126
 *
2127
 * @param array $error_types An array of strings indicating which type of errors occurred
2128
 * @param array $named_recipients
2129
 * @param $recipient_ids
2130
 */
2131
function messagePostError($error_types, $named_recipients, $recipient_ids = array())
2132
{
2133
	global $txt, $context, $scripturl, $modSettings;
2134
	global $smcFunc, $user_info, $sourcedir;
2135
2136
	if (!isset($_REQUEST['xml']))
2137
	{
2138
		$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send';
2139
		$context['sub_template'] = 'send';
2140
		loadJavaScriptFile('PersonalMessage.js', array('defer' => false, 'minimize' => true), 'smf_pms');
2141
		loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
2142
	}
2143
	else
2144
		$context['sub_template'] = 'pm';
2145
2146
	$context['page_title'] = $txt['send_message'];
2147
2148
	// Got some known members?
2149
	$context['recipients'] = array(
2150
		'to' => array(),
2151
		'bcc' => array(),
2152
	);
2153
	if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
2154
	{
2155
		$allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
2156
2157
		$request = $smcFunc['db_query']('', '
2158
			SELECT id_member, real_name
2159
			FROM {db_prefix}members
2160
			WHERE id_member IN ({array_int:member_list})',
2161
			array(
2162
				'member_list' => $allRecipients,
2163
			)
2164
		);
2165
		while ($row = $smcFunc['db_fetch_assoc']($request))
2166
		{
2167
			$recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
2168
			$context['recipients'][$recipientType][] = array(
2169
				'id' => $row['id_member'],
2170
				'name' => $row['real_name'],
2171
			);
2172
		}
2173
		$smcFunc['db_free_result']($request);
2174
	}
2175
2176
	// Set everything up like before....
2177
	$context['subject'] = isset($_REQUEST['subject']) ? $smcFunc['htmlspecialchars']($_REQUEST['subject']) : '';
2178
	$context['message'] = isset($_REQUEST['message']) ? str_replace(array('  '), array('&nbsp; '), $smcFunc['htmlspecialchars']($_REQUEST['message'])) : '';
2179
	$context['reply'] = !empty($_REQUEST['replied_to']);
2180
2181
	if ($context['reply'])
2182
	{
2183
		$_REQUEST['replied_to'] = (int) $_REQUEST['replied_to'];
2184
2185
		$request = $smcFunc['db_query']('', '
2186
			SELECT
2187
				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,
2188
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
2189
				COALESCE(mem.real_name, pm.from_name) AS real_name
2190
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' : '
2191
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:replied_to})') . '
2192
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
2193
			WHERE pm.id_pm = {int:replied_to}' . ($context['folder'] == 'sent' ? '
2194
				AND pm.id_member_from = {int:current_member}' : '
2195
				AND pmr.id_member = {int:current_member}') . '
2196
			LIMIT 1',
2197
			array(
2198
				'current_member' => $user_info['id'],
2199
				'no_id_pm_head' => 0,
2200
				'replied_to' => $_REQUEST['replied_to'],
2201
			)
2202
		);
2203
		if ($smcFunc['db_num_rows']($request) == 0)
2204
		{
2205
			if (!isset($_REQUEST['xml']))
2206
				fatal_lang_error('pm_not_yours', false);
2207
			else
2208
				$error_types[] = 'pm_not_yours';
2209
		}
2210
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
2211
		$smcFunc['db_free_result']($request);
2212
2213
		censorText($row_quoted['subject']);
2214
		censorText($row_quoted['body']);
2215
2216
		$context['quoted_message'] = array(
2217
			'id' => $row_quoted['id_pm'],
2218
			'pm_head' => $row_quoted['pm_head'],
2219
			'member' => array(
2220
				'name' => $row_quoted['real_name'],
2221
				'username' => $row_quoted['member_name'],
2222
				'id' => $row_quoted['id_member'],
2223
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
2224
				'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'],
2225
			),
2226
			'subject' => $row_quoted['subject'],
2227
			'time' => timeformat($row_quoted['msgtime']),
2228
			'timestamp' => $row_quoted['msgtime'],
2229
			'body' => parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']),
2230
		);
2231
	}
2232
2233
	// Build the link tree....
2234
	$context['linktree'][] = array(
2235
		'url' => $scripturl . '?action=pm;sa=send',
2236
		'name' => $txt['new_message']
2237
	);
2238
2239
	// Set each of the errors for the template.
2240
	loadLanguage('Errors');
2241
2242
	$context['error_type'] = 'minor';
2243
2244
	$context['post_error'] = array(
2245
		'messages' => array(),
2246
		// @todo error handling: maybe fatal errors can be error_type => serious
2247
		'error_type' => '',
2248
	);
2249
2250
	foreach ($error_types as $error_type)
2251
	{
2252
		$context['post_error'][$error_type] = true;
2253
		if (isset($txt['error_' . $error_type]))
2254
		{
2255
			if ($error_type == 'long_message')
2256
				$txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']);
2257
2258
			$context['post_error']['messages'][] = $txt['error_' . $error_type];
2259
		}
2260
2261
		// If it's not a minor error flag it as such.
2262
		if (!in_array($error_type, array('new_reply', 'not_approved', 'new_replies', 'old_topic', 'need_qr_verification', 'no_subject')))
2263
			$context['error_type'] = 'serious';
2264
	}
2265
2266
	// We need to load the editor once more.
2267
	require_once($sourcedir . '/Subs-Editor.php');
2268
2269
	// Create it...
2270
	$editorOptions = array(
2271
		'id' => 'message',
2272
		'value' => $context['message'],
2273
		'width' => '90%',
2274
		'height' => '175px',
2275
		'labels' => array(
2276
			'post_button' => $txt['send_message'],
2277
		),
2278
		'preview_type' => 2,
2279
	);
2280
	create_control_richedit($editorOptions);
2281
2282
	// ... and store the ID again...
2283
	$context['post_box_name'] = $editorOptions['id'];
2284
2285
	// Check whether we need to show the code again.
2286
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2287
	if ($context['require_verification'] && !isset($_REQUEST['xml']))
2288
	{
2289
		require_once($sourcedir . '/Subs-Editor.php');
2290
		$verificationOptions = array(
2291
			'id' => 'pm',
2292
		);
2293
		$context['require_verification'] = create_control_verification($verificationOptions);
2294
		$context['visual_verification_id'] = $verificationOptions['id'];
2295
	}
2296
2297
	$context['to_value'] = empty($named_recipients['to']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['to']) . '&quot;';
2298
	$context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['bcc']) . '&quot;';
2299
2300
	call_integration_hook('integrate_pm_error');
2301
2302
	// No check for the previous submission is needed.
2303
	checkSubmitOnce('free');
2304
2305
	// Acquire a new form sequence number.
2306
	checkSubmitOnce('register');
2307
}
2308
2309
/**
2310
 * Send it!
2311
 */
2312
function MessagePost2()
2313
{
2314
	global $txt, $context, $sourcedir;
2315
	global $user_info, $modSettings, $smcFunc;
2316
2317
	isAllowedTo('pm_send');
2318
	require_once($sourcedir . '/Subs-Auth.php');
2319
2320
	// PM Drafts enabled and needed?
2321
	if ($context['drafts_pm_save'] && (isset($_POST['save_draft']) || isset($_POST['id_pm_draft'])))
2322
	{
2323
		$context['id_pm_draft'] = !empty($_POST['id_pm_draft']) ? (int) $_POST['id_pm_draft'] : 0;
2324
		require_once($sourcedir . '/Drafts.php');
2325
	}
2326
2327
	loadLanguage('PersonalMessage', '', false);
2328
2329
	// Extract out the spam settings - it saves database space!
2330
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
2331
2332
	// Initialize the errors we're about to make.
2333
	$post_errors = array();
2334
2335
	// Check whether we've gone over the limit of messages we can send per hour - fatal error if fails!
2336
	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')
2337
	{
2338
		// How many have they sent this last hour?
2339
		$request = $smcFunc['db_query']('', '
2340
			SELECT COUNT(pr.id_pm) AS post_count
2341
			FROM {db_prefix}personal_messages AS pm
2342
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
2343
			WHERE pm.id_member_from = {int:current_member}
2344
				AND pm.msgtime > {int:msgtime}',
2345
			array(
2346
				'current_member' => $user_info['id'],
2347
				'msgtime' => time() - 3600,
2348
			)
2349
		);
2350
		list ($postCount) = $smcFunc['db_fetch_row']($request);
2351
		$smcFunc['db_free_result']($request);
2352
2353
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
2354
		{
2355
			if (!isset($_REQUEST['xml']))
2356
				fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
2357
			else
2358
				$post_errors[] = 'pm_too_many_per_hour';
2359
		}
2360
	}
2361
2362
	// If your session timed out, show an error, but do allow to re-submit.
2363
	if (!isset($_REQUEST['xml']) && checkSession('post', '', false) != '')
2364
		$post_errors[] = 'session_timeout';
2365
2366
	$_REQUEST['subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : '';
2367
	$_REQUEST['to'] = empty($_POST['to']) ? (empty($_GET['to']) ? '' : $_GET['to']) : $_POST['to'];
2368
	$_REQUEST['bcc'] = empty($_POST['bcc']) ? (empty($_GET['bcc']) ? '' : $_GET['bcc']) : $_POST['bcc'];
2369
2370
	// Route the input from the 'u' parameter to the 'to'-list.
2371
	if (!empty($_POST['u']))
2372
		$_POST['recipient_to'] = explode(',', $_POST['u']);
2373
2374
	// Construct the list of recipients.
2375
	$recipientList = array();
2376
	$namedRecipientList = array();
2377
	$namesNotFound = array();
2378
	foreach (array('to', 'bcc') as $recipientType)
2379
	{
2380
		// First, let's see if there's user ID's given.
2381
		$recipientList[$recipientType] = array();
2382
		if (!empty($_POST['recipient_' . $recipientType]) && is_array($_POST['recipient_' . $recipientType]))
2383
		{
2384
			foreach ($_POST['recipient_' . $recipientType] as $recipient)
2385
				$recipientList[$recipientType][] = (int) $recipient;
2386
		}
2387
2388
		// Are there also literal names set?
2389
		if (!empty($_REQUEST[$recipientType]))
2390
		{
2391
			// We're going to take out the "s anyway ;).
2392
			$recipientString = strtr($_REQUEST[$recipientType], array('\\"' => '"'));
2393
2394
			preg_match_all('~"([^"]+)"~', $recipientString, $matches);
2395
			$namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))));
2396
2397
			foreach ($namedRecipientList[$recipientType] as $index => $recipient)
2398
			{
2399
				if (strlen(trim($recipient)) > 0)
2400
					$namedRecipientList[$recipientType][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($recipient)));
2401
				else
2402
					unset($namedRecipientList[$recipientType][$index]);
2403
			}
2404
2405
			if (!empty($namedRecipientList[$recipientType]))
2406
			{
2407
				$foundMembers = findMembers($namedRecipientList[$recipientType]);
2408
2409
				// Assume all are not found, until proven otherwise.
2410
				$namesNotFound[$recipientType] = $namedRecipientList[$recipientType];
2411
2412
				foreach ($foundMembers as $member)
2413
				{
2414
					$testNames = array(
2415
						$smcFunc['strtolower']($member['username']),
2416
						$smcFunc['strtolower']($member['name']),
2417
						$smcFunc['strtolower']($member['email']),
2418
					);
2419
2420
					if (count(array_intersect($testNames, $namedRecipientList[$recipientType])) !== 0)
2421
					{
2422
						$recipientList[$recipientType][] = $member['id'];
2423
2424
						// Get rid of this username, since we found it.
2425
						$namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames);
2426
					}
2427
				}
2428
			}
2429
		}
2430
2431
		// Selected a recipient to be deleted? Remove them now.
2432
		if (!empty($_POST['delete_recipient']))
2433
			$recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $_POST['delete_recipient']));
2434
2435
		// Make sure we don't include the same name twice
2436
		$recipientList[$recipientType] = array_unique($recipientList[$recipientType]);
2437
	}
2438
2439
	// Are we changing the recipients some how?
2440
	$is_recipient_change = !empty($_POST['delete_recipient']) || !empty($_POST['to_submit']) || !empty($_POST['bcc_submit']);
2441
2442
	// Check if there's at least one recipient.
2443
	if (empty($recipientList['to']) && empty($recipientList['bcc']))
2444
		$post_errors[] = 'no_to';
2445
2446
	// Make sure that we remove the members who did get it from the screen.
2447
	if (!$is_recipient_change)
2448
	{
2449
		foreach ($recipientList as $recipientType => $dummy)
2450
		{
2451
			if (!empty($namesNotFound[$recipientType]))
2452
			{
2453
				$post_errors[] = 'bad_' . $recipientType;
2454
2455
				// Since we already have a post error, remove the previous one.
2456
				$post_errors = array_diff($post_errors, array('no_to'));
2457
2458
				foreach ($namesNotFound[$recipientType] as $name)
2459
					$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2460
			}
2461
		}
2462
	}
2463
2464
	// Did they make any mistakes?
2465
	if ($_REQUEST['subject'] == '')
2466
		$post_errors[] = 'no_subject';
2467
	if (!isset($_REQUEST['message']) || $_REQUEST['message'] == '')
2468
		$post_errors[] = 'no_message';
2469
	elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_REQUEST['message']) > $modSettings['max_messageLength'])
2470
		$post_errors[] = 'long_message';
2471
	else
2472
	{
2473
		// Preparse the message.
2474
		$message = $_REQUEST['message'];
2475
		preparsecode($message);
2476
2477
		// Make sure there's still some content left without the tags.
2478
		if ($smcFunc['htmltrim'](strip_tags(parse_bbc($smcFunc['htmlspecialchars']($message, ENT_QUOTES), false), '<img>')) === '' && (!allowedTo('bbc_html') || strpos($message, '[html]') === false))
2479
			$post_errors[] = 'no_message';
2480
	}
2481
2482
	// Wrong verification code?
2483
	if (!$user_info['is_admin'] && !isset($_REQUEST['xml']) && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'])
2484
	{
2485
		require_once($sourcedir . '/Subs-Editor.php');
2486
		$verificationOptions = array(
2487
			'id' => 'pm',
2488
		);
2489
		$context['require_verification'] = create_control_verification($verificationOptions, true);
2490
2491
		if (is_array($context['require_verification']))
2492
			$post_errors = array_merge($post_errors, $context['require_verification']);
2493
	}
2494
2495
	// If they did, give a chance to make ammends.
2496
	if (!empty($post_errors) && !$is_recipient_change && !isset($_REQUEST['preview']) && !isset($_REQUEST['xml']))
2497
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
2498
2499
	// Want to take a second glance before you send?
2500
	if (isset($_REQUEST['preview']))
2501
	{
2502
		// Set everything up to be displayed.
2503
		$context['preview_subject'] = $smcFunc['htmlspecialchars']($_REQUEST['subject']);
2504
		$context['preview_message'] = $smcFunc['htmlspecialchars']($_REQUEST['message'], ENT_QUOTES);
2505
		preparsecode($context['preview_message'], true);
2506
2507
		// Parse out the BBC if it is enabled.
2508
		$context['preview_message'] = parse_bbc($context['preview_message']);
2509
2510
		// Censor, as always.
2511
		censorText($context['preview_subject']);
2512
		censorText($context['preview_message']);
2513
2514
		// Set a descriptive title.
2515
		$context['page_title'] = $txt['preview'] . ' - ' . $context['preview_subject'];
2516
2517
		// Pretend they messed up but don't ignore if they really did :P.
2518
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
2519
	}
2520
2521
	// Adding a recipient cause javascript ain't working?
2522
	elseif ($is_recipient_change)
2523
	{
2524
		// Maybe we couldn't find one?
2525
		foreach ($namesNotFound as $recipientType => $names)
2526
		{
2527
			$post_errors[] = 'bad_' . $recipientType;
2528
			foreach ($names as $name)
2529
				$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2530
		}
2531
2532
		return messagePostError(array(), $namedRecipientList, $recipientList);
2533
	}
2534
2535
	// Want to save this as a draft and think about it some more?
2536
	if ($context['drafts_pm_save'] && isset($_POST['save_draft']))
2537
	{
2538
		SavePMDraft($post_errors, $recipientList);
2539
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
2540
	}
2541
2542
	// Before we send the PM, let's make sure we don't have an abuse of numbers.
2543
	elseif (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum')))
2544
	{
2545
		$context['send_log'] = array(
2546
			'sent' => array(),
2547
			'failed' => array(sprintf($txt['pm_too_many_recipients'], $modSettings['max_pm_recipients'])),
2548
		);
2549
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
2550
	}
2551
2552
	// Protect from message spamming.
2553
	spamProtection('pm');
2554
2555
	// Prevent double submission of this form.
2556
	checkSubmitOnce('check');
2557
2558
	// Do the actual sending of the PM.
2559
	if (!empty($recipientList['to']) || !empty($recipientList['bcc']))
2560
		$context['send_log'] = sendpm($recipientList, $_REQUEST['subject'], $_REQUEST['message'], true, null, !empty($_REQUEST['pm_head']) ? (int) $_REQUEST['pm_head'] : 0);
2561
	else
2562
		$context['send_log'] = array(
2563
			'sent' => array(),
2564
			'failed' => array()
2565
		);
2566
2567
	// Mark the message as "replied to".
2568
	if (!empty($context['send_log']['sent']) && !empty($_REQUEST['replied_to']) && isset($_REQUEST['f']) && $_REQUEST['f'] == 'inbox')
2569
	{
2570
		$smcFunc['db_query']('', '
2571
			UPDATE {db_prefix}pm_recipients
2572
			SET is_read = is_read | 2
2573
			WHERE id_pm = {int:replied_to}
2574
				AND id_member = {int:current_member}',
2575
			array(
2576
				'current_member' => $user_info['id'],
2577
				'replied_to' => (int) $_REQUEST['replied_to'],
2578
			)
2579
		);
2580
	}
2581
2582
	// If one or more of the recipient were invalid, go back to the post screen with the failed usernames.
2583
	if (!empty($context['send_log']['failed']))
2584
		return messagePostError($post_errors, $namesNotFound, array(
2585
			'to' => array_intersect($recipientList['to'], $context['send_log']['failed']),
2586
			'bcc' => array_intersect($recipientList['bcc'], $context['send_log']['failed'])
2587
		));
2588
2589
	// Message sent successfully?
2590
	if (!empty($context['send_log']) && empty($context['send_log']['failed']))
2591
	{
2592
		$context['current_label_redirect'] = $context['current_label_redirect'] . ';done=sent';
2593
2594
		// If we had a PM draft for this one, then its time to remove it since it was just sent
2595
		if ($context['drafts_pm_save'] && !empty($_POST['id_pm_draft']))
2596
			DeleteDraft($_POST['id_pm_draft']);
2597
	}
2598
2599
	// Go back to the where they sent from, if possible...
2600
	redirectexit($context['current_label_redirect']);
2601
}
2602
2603
/**
2604
 * This function performs all additional stuff...
2605
 */
2606
function MessageActionsApply()
2607
{
2608
	global $context, $user_info, $options, $smcFunc;
2609
2610
	checkSession('request');
2611
2612
	if (isset($_REQUEST['del_selected']))
2613
		$_REQUEST['pm_action'] = 'delete';
2614
2615
	if (isset($_REQUEST['pm_action']) && $_REQUEST['pm_action'] != '' && !empty($_REQUEST['pms']) && is_array($_REQUEST['pms']))
2616
	{
2617
		foreach ($_REQUEST['pms'] as $pm)
2618
			$_REQUEST['pm_actions'][(int) $pm] = $_REQUEST['pm_action'];
2619
	}
2620
2621
	if (empty($_REQUEST['pm_actions']))
2622
		redirectexit($context['current_label_redirect']);
2623
2624
	// If we are in conversation, we may need to apply this to every message in the conversation.
2625
	if ($context['display_mode'] == 2 && isset($_REQUEST['conversation']))
2626
	{
2627
		$id_pms = array();
2628
		foreach ($_REQUEST['pm_actions'] as $pm => $dummy)
2629
			$id_pms[] = (int) $pm;
2630
2631
		$request = $smcFunc['db_query']('', '
2632
			SELECT id_pm_head, id_pm
2633
			FROM {db_prefix}personal_messages
2634
			WHERE id_pm IN ({array_int:id_pms})',
2635
			array(
2636
				'id_pms' => $id_pms,
2637
			)
2638
		);
2639
		$pm_heads = array();
2640
		while ($row = $smcFunc['db_fetch_assoc']($request))
2641
			$pm_heads[$row['id_pm_head']] = $row['id_pm'];
2642
		$smcFunc['db_free_result']($request);
2643
2644
		$request = $smcFunc['db_query']('', '
2645
			SELECT id_pm, id_pm_head
2646
			FROM {db_prefix}personal_messages
2647
			WHERE id_pm_head IN ({array_int:pm_heads})',
2648
			array(
2649
				'pm_heads' => array_keys($pm_heads),
2650
			)
2651
		);
2652
		// Copy the action from the single to PM to the others.
2653
		while ($row = $smcFunc['db_fetch_assoc']($request))
2654
		{
2655
			if (isset($pm_heads[$row['id_pm_head']]) && isset($_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]]))
2656
				$_REQUEST['pm_actions'][$row['id_pm']] = $_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]];
2657
		}
2658
		$smcFunc['db_free_result']($request);
2659
	}
2660
2661
	$to_delete = array();
2662
	$to_label = array();
2663
	$label_type = array();
2664
	$labels = array();
2665
	foreach ($_REQUEST['pm_actions'] as $pm => $action)
2666
	{
2667
		if ($action === 'delete')
2668
			$to_delete[] = (int) $pm;
2669
		else
2670
		{
2671
			if (substr($action, 0, 4) == 'add_')
2672
			{
2673
				$type = 'add';
2674
				$action = substr($action, 4);
2675
			}
2676
			elseif (substr($action, 0, 4) == 'rem_')
2677
			{
2678
				$type = 'rem';
2679
				$action = substr($action, 4);
2680
			}
2681
			else
2682
				$type = 'unk';
2683
2684
			if ($action == '-1' || (int) $action > 0)
2685
			{
2686
				$to_label[(int) $pm] = (int) $action;
2687
				$label_type[(int) $pm] = $type;
2688
			}
2689
		}
2690
	}
2691
2692
	// Deleting, it looks like?
2693
	if (!empty($to_delete))
2694
		deleteMessages($to_delete, $context['display_mode'] == 2 ? null : $context['folder']);
2695
2696
	// Are we labeling anything?
2697
	if (!empty($to_label) && $context['folder'] == 'inbox')
2698
	{
2699
		// Are we dealing with conversation view? If so, get all the messages in each conversation
2700
		if ($context['display_mode'] == 2)
2701
		{
2702
			$get_pms = $smcFunc['db_query']('', '
2703
				SELECT pm.id_pm_head, pm.id_pm
2704
				FROM {db_prefix}personal_messages AS pm
2705
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2706
				WHERE pm.id_pm_head IN ({array_int:head_pms})
2707
					AND pm.id_pm NOT IN ({array_int:head_pms})
2708
					AND pmr.id_member = {int:current_member}',
2709
				array(
2710
					'head_pms' => array_keys($to_label),
2711
					'current_member' => $user_info['id'],
2712
				)
2713
			);
2714
2715
			while ($other_pms = $smcFunc['db_fetch_assoc']($get_pms))
2716
			{
2717
				$to_label[$other_pms['id_pm']] = $to_label[$other_pms['id_pm_head']];
2718
			}
2719
2720
			$smcFunc['db_free_result']($get_pms);
2721
		}
2722
2723
		// Get information about each message...
2724
		$request = $smcFunc['db_query']('', '
2725
			SELECT id_pm, in_inbox
2726
			FROM {db_prefix}pm_recipients
2727
			WHERE id_member = {int:current_member}
2728
				AND id_pm IN ({array_int:to_label})
2729
			LIMIT ' . count($to_label),
2730
			array(
2731
				'current_member' => $user_info['id'],
2732
				'to_label' => array_keys($to_label),
2733
			)
2734
		);
2735
2736
		while ($row = $smcFunc['db_fetch_assoc']($request))
2737
		{
2738
			// Get the labels as well, but only if we're not dealing with the inbox
2739
			if ($to_label[$row['id_pm']] != '-1')
2740
			{
2741
				// The JOIN here ensures we only get labels that this user has applied to this PM
2742
				$request2 = $smcFunc['db_query']('', '
2743
					SELECT l.id_label, pml.id_pm
2744
					FROM {db_prefix}pm_labels AS l
2745
						INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2746
					WHERE l.id_member = {int:current_member}
2747
						AND pml.id_pm = {int:current_pm}',
2748
					array(
2749
						'current_member' => $user_info['id'],
2750
						'current_pm' => $row['id_pm'],
2751
					)
2752
				);
2753
2754
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
2755
				{
2756
					$labels[$row2['id_label']] = $row2['id_label'];
2757
				}
2758
2759
				$smcFunc['db_free_result']($request2);
2760
			}
2761
			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 2665. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2762
			{
2763
				// If we're removing from the inbox, see if we have at least one other label.
2764
				// This query is faster than the one above
2765
				$request2 = $smcFunc['db_query']('', '
2766
					SELECT COUNT(*)
2767
					FROM {db_prefix}pm_labels AS l
2768
						INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2769
					WHERE l.id_member = {int:current_member}
2770
						AND pml.id_pm = {int:current_pm}',
2771
					array(
2772
						'current_member' => $user_info['id'],
2773
						'current_pm' => $row['id_pm'],
2774
					)
2775
				);
2776
2777
				// How many labels do you have?
2778
				list ($num_labels) = $smcFunc['db_fetch_assoc']($request2);
2779
2780
				if ($num_labels > 0)
2781
					$context['can_remove_inbox'] = true;
2782
2783
				$smcFunc['db_free_result']($request2);
2784
			}
2785
2786
			// Use this to determine what to do later on...
2787
			$original_labels = $labels;
2788
2789
			// Ignore inbox for now - we'll deal with it later
2790
			if ($to_label[$row['id_pm']] != '-1')
2791
			{
2792
				// If this label is in the list and we're not adding it, remove it
2793
				if (array_key_exists($to_label[$row['id_pm']], $labels) && $type !== 'add')
2794
					unset($labels[$to_label[$row['id_pm']]]);
2795
				elseif ($type !== 'rem')
2796
					$labels[$to_label[$row['id_pm']]] = $to_label[$row['id_pm']];
2797
			}
2798
2799
			// Removing all labels or just removing the inbox label
2800
			if ($type == 'rem' && empty($labels))
2801
				$in_inbox = (empty($context['can_remove_inbox']) ? 1 : 0);
2802
			// Adding new labels, but removing inbox and applying new ones
2803
			elseif ($type == 'add' && !empty($options['pm_remove_inbox_label']) && !empty($labels))
2804
				$in_inbox = 0;
2805
			// Just adding it to the inbox
2806
			else
2807
				$in_inbox = 1;
2808
2809
			// Are we adding it to or removing it from the inbox?
2810
			if ($in_inbox != $row['in_inbox'])
2811
			{
2812
				$smcFunc['db_query']('', '
2813
					UPDATE {db_prefix}pm_recipients
2814
					SET in_inbox = {int:in_inbox}
2815
					WHERE id_pm = {int:id_pm}
2816
						AND id_member = {int:current_member}',
2817
					array(
2818
						'current_member' => $user_info['id'],
2819
						'id_pm' => $row['id_pm'],
2820
						'in_inbox' => $in_inbox,
2821
					)
2822
				);
2823
			}
2824
2825
			// Which labels do we not want now?
2826
			$labels_to_remove = array_diff($original_labels, $labels);
2827
2828
			// Don't apply it if it's already applied
2829
			$labels_to_apply = array_diff($labels, $original_labels);
2830
2831
			// Remove labels
2832
			if (!empty($labels_to_remove))
2833
			{
2834
				$smcFunc['db_query']('', '
2835
					DELETE FROM {db_prefix}pm_labeled_messages
2836
					WHERE id_pm = {int:current_pm}
2837
						AND id_label IN ({array_int:labels_to_remove})',
2838
					array(
2839
						'current_pm' => $row['id_pm'],
2840
						'labels_to_remove' => $labels_to_remove,
2841
					)
2842
				);
2843
			}
2844
2845
			// Add new ones
2846
			if (!empty($labels_to_apply))
2847
			{
2848
				$inserts = array();
2849
				foreach ($labels_to_apply as $label)
2850
					$inserts[] = array($row['id_pm'], $label);
2851
2852
				$smcFunc['db_insert']('',
2853
					'{db_prefix}pm_labeled_messages',
2854
					array('id_pm' => 'int', 'id_label' => 'int'),
2855
					$inserts,
2856
					array()
2857
				);
2858
			}
2859
		}
2860
		$smcFunc['db_free_result']($request);
2861
	}
2862
2863
	// Back to the folder.
2864
	$_SESSION['pm_selected'] = array_keys($to_label);
2865
	redirectexit($context['current_label_redirect'] . (count($to_label) == 1 ? '#msg' . $_SESSION['pm_selected'][0] : ''), count($to_label) == 1 && isBrowser('ie'));
2866
}
2867
2868
/**
2869
 * Delete ALL the messages!
2870
 */
2871
function MessageKillAll()
2872
{
2873
	global $context;
2874
2875
	checkSession();
2876
2877
	deleteMessages(null, null);
2878
2879
	// Done... all gone.
2880
	redirectexit($context['current_label_redirect']);
2881
}
2882
2883
/**
2884
 * This function allows the user to delete all messages older than so many days.
2885
 */
2886
function MessagePrune()
2887
{
2888
	global $txt, $context, $user_info, $scripturl, $smcFunc;
2889
2890
	// Actually delete the messages.
2891
	if (isset($_REQUEST['age']))
2892
	{
2893
		checkSession();
2894
2895
		// Calculate the time to delete before.
2896
		$deleteTime = max(0, time() - (86400 * (int) $_REQUEST['age']));
2897
2898
		// Array to store the IDs in.
2899
		$toDelete = array();
2900
2901
		// Select all the messages they have sent older than $deleteTime.
2902
		$request = $smcFunc['db_query']('', '
2903
			SELECT id_pm
2904
			FROM {db_prefix}personal_messages
2905
			WHERE deleted_by_sender = {int:not_deleted}
2906
				AND id_member_from = {int:current_member}
2907
				AND msgtime < {int:msgtime}',
2908
			array(
2909
				'current_member' => $user_info['id'],
2910
				'not_deleted' => 0,
2911
				'msgtime' => $deleteTime,
2912
			)
2913
		);
2914
		while ($row = $smcFunc['db_fetch_row']($request))
2915
			$toDelete[] = $row[0];
2916
		$smcFunc['db_free_result']($request);
2917
2918
		// Select all messages in their inbox older than $deleteTime.
2919
		$request = $smcFunc['db_query']('', '
2920
			SELECT pmr.id_pm
2921
			FROM {db_prefix}pm_recipients AS pmr
2922
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
2923
			WHERE pmr.deleted = {int:not_deleted}
2924
				AND pmr.id_member = {int:current_member}
2925
				AND pm.msgtime < {int:msgtime}',
2926
			array(
2927
				'current_member' => $user_info['id'],
2928
				'not_deleted' => 0,
2929
				'msgtime' => $deleteTime,
2930
			)
2931
		);
2932
		while ($row = $smcFunc['db_fetch_assoc']($request))
2933
			$toDelete[] = $row['id_pm'];
2934
		$smcFunc['db_free_result']($request);
2935
2936
		// Delete the actual messages.
2937
		deleteMessages($toDelete);
2938
2939
		// Go back to their inbox.
2940
		redirectexit($context['current_label_redirect']);
2941
	}
2942
2943
	// Build the link tree elements.
2944
	$context['linktree'][] = array(
2945
		'url' => $scripturl . '?action=pm;sa=prune',
2946
		'name' => $txt['pm_prune']
2947
	);
2948
2949
	$context['sub_template'] = 'prune';
2950
	$context['page_title'] = $txt['pm_prune'];
2951
}
2952
2953
/**
2954
 * Delete the specified personal messages.
2955
 *
2956
 * @param array|null $personal_messages An array containing the IDs of PMs to delete or null to delete all of them
2957
 * @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
2958
 * @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
2959
 */
2960
function deleteMessages($personal_messages, $folder = null, $owner = null)
2961
{
2962
	global $user_info, $smcFunc;
2963
2964
	if ($owner === null)
2965
		$owner = array($user_info['id']);
2966
	elseif (empty($owner))
2967
		return;
2968
	elseif (!is_array($owner))
2969
		$owner = array($owner);
2970
2971
	if ($personal_messages !== null)
2972
	{
2973
		if (empty($personal_messages) || !is_array($personal_messages))
2974
			return;
2975
2976
		foreach ($personal_messages as $index => $delete_id)
2977
			$personal_messages[$index] = (int) $delete_id;
2978
2979
		$where = '
2980
				AND id_pm IN ({array_int:pm_list})';
2981
	}
2982
	else
2983
		$where = '';
2984
2985
	if ($folder == 'sent' || $folder === null)
2986
	{
2987
		$smcFunc['db_query']('', '
2988
			UPDATE {db_prefix}personal_messages
2989
			SET deleted_by_sender = {int:is_deleted}
2990
			WHERE id_member_from IN ({array_int:member_list})
2991
				AND deleted_by_sender = {int:not_deleted}' . $where,
2992
			array(
2993
				'member_list' => $owner,
2994
				'is_deleted' => 1,
2995
				'not_deleted' => 0,
2996
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2997
			)
2998
		);
2999
	}
3000
	if ($folder != 'sent' || $folder === null)
3001
	{
3002
		// Calculate the number of messages each member's gonna lose...
3003
		$request = $smcFunc['db_query']('', '
3004
			SELECT id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read
3005
			FROM {db_prefix}pm_recipients
3006
			WHERE id_member IN ({array_int:member_list})
3007
				AND deleted = {int:not_deleted}' . $where . '
3008
			GROUP BY id_member, is_read',
3009
			array(
3010
				'member_list' => $owner,
3011
				'not_deleted' => 0,
3012
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3013
			)
3014
		);
3015
		// ...And update the statistics accordingly - now including unread messages!.
3016
		while ($row = $smcFunc['db_fetch_assoc']($request))
3017
		{
3018
			if ($row['is_read'])
3019
				updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages']));
3020
			else
3021
				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']));
3022
3023
			// If this is the current member we need to make their message count correct.
3024
			if ($user_info['id'] == $row['id_member'])
3025
			{
3026
				$user_info['messages'] -= $row['num_deleted_messages'];
3027
				if (!($row['is_read']))
3028
					$user_info['unread_messages'] -= $row['num_deleted_messages'];
3029
			}
3030
		}
3031
		$smcFunc['db_free_result']($request);
3032
3033
		// Do the actual deletion.
3034
		$smcFunc['db_query']('', '
3035
			UPDATE {db_prefix}pm_recipients
3036
			SET deleted = {int:is_deleted}
3037
			WHERE id_member IN ({array_int:member_list})
3038
				AND deleted = {int:not_deleted}' . $where,
3039
			array(
3040
				'member_list' => $owner,
3041
				'is_deleted' => 1,
3042
				'not_deleted' => 0,
3043
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3044
			)
3045
		);
3046
3047
		$labels = array();
3048
3049
		// Get any labels that the owner may have applied to this PM
3050
		// The join is here to ensure we only get labels applied by the specified member(s)
3051
		$get_labels = $smcFunc['db_query']('', '
3052
			SELECT pml.id_label
3053
			FROM {db_prefix}pm_labels AS l
3054
				INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
3055
			WHERE l.id_member IN ({array_int:member_list})' . $where,
3056
			array(
3057
				'member_list' => $owner,
3058
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3059
			)
3060
		);
3061
3062
		while ($row = $smcFunc['db_fetch_assoc']($get_labels))
3063
		{
3064
			$labels[] = $row['id_label'];
3065
		}
3066
3067
		$smcFunc['db_free_result']($get_labels);
3068
3069
		if (!empty($labels))
3070
		{
3071
			$smcFunc['db_query']('', '
3072
				DELETE FROM {db_prefix}pm_labeled_messages
3073
				WHERE id_label IN ({array_int:labels})' . $where,
3074
				array(
3075
					'labels' => $labels,
3076
					'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3077
				)
3078
			);
3079
		}
3080
	}
3081
3082
	// If sender and recipients all have deleted their message, it can be removed.
3083
	$request = $smcFunc['db_query']('', '
3084
		SELECT pm.id_pm AS sender, pmr.id_pm
3085
		FROM {db_prefix}personal_messages AS pm
3086
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted})
3087
		WHERE pm.deleted_by_sender = {int:is_deleted} AND pmr.id_pm is null
3088
			' . str_replace('id_pm', 'pm.id_pm', $where),
3089
		array(
3090
			'not_deleted' => 0,
3091
			'is_deleted' => 1,
3092
			'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3093
		)
3094
	);
3095
	$remove_pms = array();
3096
	while ($row = $smcFunc['db_fetch_assoc']($request))
3097
		$remove_pms[] = $row['sender'];
3098
	$smcFunc['db_free_result']($request);
3099
3100
	if (!empty($remove_pms))
3101
	{
3102
		$smcFunc['db_query']('', '
3103
			DELETE FROM {db_prefix}personal_messages
3104
			WHERE id_pm IN ({array_int:pm_list})',
3105
			array(
3106
				'pm_list' => $remove_pms,
3107
			)
3108
		);
3109
3110
		$smcFunc['db_query']('', '
3111
			DELETE FROM {db_prefix}pm_recipients
3112
			WHERE id_pm IN ({array_int:pm_list})',
3113
			array(
3114
				'pm_list' => $remove_pms,
3115
			)
3116
		);
3117
3118
		$smcFunc['db_query']('', '
3119
			DELETE FROM {db_prefix}pm_labeled_messages
3120
			WHERE id_pm IN ({array_int:pm_list})',
3121
			array(
3122
				'pm_list' => $remove_pms,
3123
			)
3124
		);
3125
	}
3126
3127
	// Any cached numbers may be wrong now.
3128
	cache_put_data('labelCounts:' . $user_info['id'], null, 720);
3129
}
3130
3131
/**
3132
 * Mark the specified personal messages read.
3133
 *
3134
 * @param array|null $personal_messages An array of PM IDs to mark or null to mark all
3135
 * @param int|null $label The ID of a label. If set, only messages with this label will be marked.
3136
 * @param int|null $owner If owner is set, marks messages owned by that member id
3137
 */
3138
function markMessages($personal_messages = null, $label = null, $owner = null)
3139
{
3140
	global $user_info, $context, $smcFunc;
3141
3142
	if ($owner === null)
3143
		$owner = $user_info['id'];
3144
3145
	$in_inbox = '';
3146
3147
	// Marking all messages with a specific label as read?
3148
	// If we know which PMs we're marking read, then we don't need label info
3149
	if ($personal_messages === null && $label !== null && $label != '-1')
3150
	{
3151
		$personal_messages = array();
3152
		$get_messages = $smcFunc['db_query']('', '
3153
			SELECT id_pm
3154
			FROM {db_prefix}pm_labeled_messages
3155
			WHERE id_label = {int:current_label}',
3156
			array(
3157
				'current_label' => $label,
3158
			)
3159
		);
3160
3161
		while ($row = $smcFunc['db_fetch_assoc']($get_messages))
3162
		{
3163
			$personal_messages[] = $row['id_pm'];
3164
		}
3165
3166
		$smcFunc['db_free_result']($get_messages);
3167
	}
3168
	elseif ($label = '-1')
0 ignored issues
show
The assignment to $label is dead and can be removed.
Loading history...
3169
	{
3170
		// Marking all PMs in your inbox read
3171
		$in_inbox = '
3172
			AND in_inbox = {int:in_inbox}';
3173
	}
3174
3175
	$smcFunc['db_query']('', '
3176
		UPDATE {db_prefix}pm_recipients
3177
		SET is_read = is_read | 1
3178
		WHERE id_member = {int:id_member}
3179
			AND NOT (is_read & 1 >= 1)' . ($personal_messages !== null ? '
3180
			AND id_pm IN ({array_int:personal_messages})' : '') . $in_inbox,
3181
		array(
3182
			'personal_messages' => $personal_messages,
3183
			'id_member' => $owner,
3184
			'in_inbox' => 1,
3185
		)
3186
	);
3187
3188
	// If something wasn't marked as read, get the number of unread messages remaining.
3189
	if ($smcFunc['db_affected_rows']() > 0)
3190
	{
3191
		if ($owner == $user_info['id'])
3192
		{
3193
			foreach ($context['labels'] as $label)
3194
				$context['labels'][(int) $label['id']]['unread_messages'] = 0;
3195
		}
3196
3197
		$result = $smcFunc['db_query']('', '
3198
			SELECT id_pm, in_inbox, COUNT(*) AS num
3199
			FROM {db_prefix}pm_recipients
3200
			WHERE id_member = {int:id_member}
3201
				AND NOT (is_read & 1 >= 1)
3202
				AND deleted = {int:is_not_deleted}
3203
			GROUP BY id_pm, in_inbox',
3204
			array(
3205
				'id_member' => $owner,
3206
				'is_not_deleted' => 0,
3207
			)
3208
		);
3209
		$total_unread = 0;
3210
		while ($row = $smcFunc['db_fetch_assoc']($result))
3211
		{
3212
			$total_unread += $row['num'];
3213
3214
			if ($owner != $user_info['id'] || empty($row['id_pm']))
3215
				continue;
3216
3217
			$this_labels = array();
3218
3219
			// Get all the labels
3220
			$result2 = $smcFunc['db_query']('', '
3221
				SELECT pml.id_label
3222
				FROM {db_prefix}pm_labels AS l
3223
					INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
3224
				WHERE l.id_member = {int:id_member}
3225
					AND pml.id_pm = {int:current_pm}',
3226
				array(
3227
					'id_member' => $owner,
3228
					'current_pm' => $row['id_pm'],
3229
				)
3230
			);
3231
3232
			while ($row2 = $smcFunc['db_fetch_assoc']($result2))
3233
			{
3234
				$this_labels[] = $row2['id_label'];
3235
			}
3236
3237
			$smcFunc['db_free_result']($result2);
3238
3239
			foreach ($this_labels as $this_label)
3240
				$context['labels'][$this_label]['unread_messages'] += $row['num'];
3241
3242
			if ($row['in_inbox'] == 1)
3243
				$context['labels'][-1]['unread_messages'] += $row['num'];
3244
		}
3245
		$smcFunc['db_free_result']($result);
3246
3247
		// Need to store all this.
3248
		cache_put_data('labelCounts:' . $owner, $context['labels'], 720);
3249
		updateMemberData($owner, array('unread_messages' => $total_unread));
3250
3251
		// If it was for the current member, reflect this in the $user_info array too.
3252
		if ($owner == $user_info['id'])
3253
			$user_info['unread_messages'] = $total_unread;
3254
	}
3255
}
3256
3257
/**
3258
 * This function handles adding, deleting and editing labels on messages.
3259
 */
3260
function ManageLabels()
3261
{
3262
	global $txt, $context, $user_info, $scripturl, $smcFunc;
3263
3264
	// Build the link tree elements...
3265
	$context['linktree'][] = array(
3266
		'url' => $scripturl . '?action=pm;sa=manlabels',
3267
		'name' => $txt['pm_manage_labels']
3268
	);
3269
3270
	$context['page_title'] = $txt['pm_manage_labels'];
3271
	$context['sub_template'] = 'labels';
3272
3273
	$the_labels = array();
3274
	$labels_to_add = array();
3275
	$labels_to_remove = array();
3276
	$label_updates = array();
3277
3278
	// Add all of the current user's existing labels to the array to save, slashing them as necessary...
3279
	foreach ($context['labels'] as $label)
3280
	{
3281
		if ($label['id'] != -1)
3282
			$the_labels[$label['id']] = $label['name'];
3283
	}
3284
3285
	if (isset($_POST[$context['session_var']]))
3286
	{
3287
		checkSession();
3288
3289
		// This will be for updating messages.
3290
		$message_changes = array();
3291
		$rule_changes = array();
3292
3293
		// Will most likely need this.
3294
		LoadRules();
3295
3296
		// Adding a new label?
3297
		if (isset($_POST['add']))
3298
		{
3299
			$_POST['label'] = strtr($smcFunc['htmlspecialchars'](trim($_POST['label'])), array(',' => '&#044;'));
3300
3301
			if ($smcFunc['strlen']($_POST['label']) > 30)
3302
				$_POST['label'] = $smcFunc['substr']($_POST['label'], 0, 30);
3303
			if ($_POST['label'] != '')
3304
			{
3305
				$the_labels[] = $_POST['label'];
3306
				$labels_to_add[] = $_POST['label'];
3307
			}
3308
		}
3309
		// Deleting an existing label?
3310
		elseif (isset($_POST['delete'], $_POST['delete_label']))
3311
		{
3312
			foreach ($_POST['delete_label'] AS $label => $dummy)
3313
			{
3314
				if (array_key_exists($label, $the_labels))
3315
				{
3316
					unset($the_labels[$label]);
3317
					$labels_to_remove[] = $label;
3318
				}
3319
			}
3320
		}
3321
		// The hardest one to deal with... changes.
3322
		elseif (isset($_POST['save']) && !empty($_POST['label_name']))
3323
		{
3324
			foreach ($the_labels as $id => $name)
3325
			{
3326
				if ($id == -1)
3327
					continue;
3328
				elseif (isset($_POST['label_name'][$id]))
3329
				{
3330
					$_POST['label_name'][$id] = trim(strtr($smcFunc['htmlspecialchars']($_POST['label_name'][$id]), array(',' => '&#044;')));
3331
3332
					if ($smcFunc['strlen']($_POST['label_name'][$id]) > 30)
3333
						$_POST['label_name'][$id] = $smcFunc['substr']($_POST['label_name'][$id], 0, 30);
3334
					if ($_POST['label_name'][$id] != '')
3335
					{
3336
						// Changing the name of this label?
3337
						if ($the_labels[$id] != $_POST['label_name'][$id])
3338
							$label_updates[$id] = $_POST['label_name'][$id];
3339
3340
						$the_labels[(int) $id] = $_POST['label_name'][$id];
3341
					}
3342
					else
3343
					{
3344
						unset($the_labels[(int) $id]);
3345
						$labels_to_remove[] = $id;
3346
						$message_changes[(int) $id] = true;
3347
					}
3348
				}
3349
			}
3350
		}
3351
3352
		// Save any new labels
3353
		if (!empty($labels_to_add))
3354
		{
3355
			$inserts = array();
3356
			foreach ($labels_to_add AS $label)
3357
				$inserts[] = array($user_info['id'], $label);
3358
3359
			$smcFunc['db_insert']('', '{db_prefix}pm_labels', array('id_member' => 'int', 'name' => 'string-30'), $inserts, array());
3360
		}
3361
3362
		// Update existing labels as needed
3363
		if (!empty($label_updates))
3364
		{
3365
			foreach ($label_updates AS $id => $name)
3366
			{
3367
				$smcFunc['db_query']('', '
3368
					UPDATE {db_prefix}pm_labels
3369
					SET name = {string:name}
3370
					WHERE id_label = {int:id_label}
3371
					AND id_member = {int:current_member}',
3372
					array(
3373
						'name' => $name,
3374
						'id_label' => $id,
3375
						'current_member' => $user_info['id'],
3376
					)
3377
				);
3378
			}
3379
		}
3380
3381
		// Now the fun part... Deleting labels.
3382
		if (!empty($labels_to_remove))
3383
		{
3384
			// First delete the labels
3385
			$smcFunc['db_query']('', '
3386
				DELETE FROM {db_prefix}pm_labels
3387
				WHERE id_label IN ({array_int:labels_to_delete})
3388
				AND id_member = {int:current_member}',
3389
				array(
3390
					'labels_to_delete' => $labels_to_remove,
3391
					'current_member' => $user_info['id'],
3392
				)
3393
			);
3394
3395
			// Now remove the now-deleted labels from any PMs...
3396
			$smcFunc['db_query']('', '
3397
				DELETE FROM {db_prefix}pm_labeled_messages
3398
				WHERE id_label IN ({array_int:labels_to_delete})',
3399
				array(
3400
					'labels_to_delete' => $labels_to_remove,
3401
				)
3402
			);
3403
3404
			// Get any PMs with no labels which aren't in the inbox
3405
			$get_stranded_pms = $smcFunc['db_query']('', '
3406
				SELECT pmr.id_pm
3407
				FROM {db_prefix}pm_recipients AS pmr
3408
					LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)
3409
				WHERE pml.id_label IS NULL
3410
					AND pmr.in_inbox = {int:not_in_inbox}
3411
					AND pmr.deleted = {int:not_deleted}
3412
					AND pmr.id_member = {int:current_member}',
3413
				array(
3414
					'not_in_inbox' => 0,
3415
					'not_deleted' => 0,
3416
					'current_member' => $user_info['id'],
3417
				)
3418
			);
3419
3420
			$stranded_messages = array();
3421
			while ($row = $smcFunc['db_fetch_assoc']($get_stranded_pms))
3422
			{
3423
				$stranded_messages[] = $row['id_pm'];
3424
			}
3425
3426
			$smcFunc['db_free_result']($get_stranded_pms);
3427
3428
			// Move these back to the inbox if necessary
3429
			if (!empty($stranded_messages))
3430
			{
3431
				// We now have more messages in the inbox
3432
				$context['labels'][-1]['messages'] += count($stranded_messages);
3433
				$smcFunc['db_query']('', '
3434
					UPDATE {db_prefix}pm_recipients
3435
					SET in_inbox = {int:in_inbox}
3436
					WHERE id_pm IN ({array_int:stranded_messages})
3437
						AND id_member = {int:current_member}',
3438
					array(
3439
						'stranded_messages' => $stranded_messages,
3440
						'in_inbox' => 1,
3441
						'current_member' => $user_info['id'],
3442
					)
3443
				);
3444
			}
3445
3446
			// Now do the same the rules - check through each rule.
3447
			foreach ($context['rules'] as $k => $rule)
3448
			{
3449
				// Each action...
3450
				foreach ($rule['actions'] as $k2 => $action)
3451
				{
3452
					if ($action['t'] != 'lab' || !in_array($action['v'], $labels_to_remove))
3453
						continue;
3454
3455
					$rule_changes[] = $rule['id'];
3456
3457
					// Can't apply this label anymore if it doesn't exist
3458
					unset($context['rules'][$k]['actions'][$k2]);
3459
				}
3460
			}
3461
		}
3462
3463
		// If we have rules to change do so now.
3464
		if (!empty($rule_changes))
3465
		{
3466
			$rule_changes = array_unique($rule_changes);
3467
			// Update/delete as appropriate.
3468
			foreach ($rule_changes as $k => $id)
3469
				if (!empty($context['rules'][$id]['actions']))
3470
				{
3471
					$smcFunc['db_query']('', '
3472
						UPDATE {db_prefix}pm_rules
3473
						SET actions = {string:actions}
3474
						WHERE id_rule = {int:id_rule}
3475
							AND id_member = {int:current_member}',
3476
						array(
3477
							'current_member' => $user_info['id'],
3478
							'id_rule' => $id,
3479
							'actions' => $smcFunc['json_encode']($context['rules'][$id]['actions']),
3480
						)
3481
					);
3482
					unset($rule_changes[$k]);
3483
				}
3484
3485
			// Anything left here means it's lost all actions...
3486
			if (!empty($rule_changes))
3487
				$smcFunc['db_query']('', '
3488
					DELETE FROM {db_prefix}pm_rules
3489
					WHERE id_rule IN ({array_int:rule_list})
3490
						AND id_member = {int:current_member}',
3491
					array(
3492
						'current_member' => $user_info['id'],
3493
						'rule_list' => $rule_changes,
3494
					)
3495
				);
3496
		}
3497
3498
		// Make sure we're not caching this!
3499
		cache_put_data('labelCounts:' . $user_info['id'], null, 720);
3500
3501
		// To make the changes appear right away, redirect.
3502
		redirectexit('action=pm;sa=manlabels');
3503
	}
3504
}
3505
3506
/**
3507
 * Allows to edit Personal Message Settings.
3508
 *
3509
 * Uses Profile.php
3510
 * Uses Profile-Modify.php
3511
 * Uses Profile template.
3512
 * Uses Profile language file.
3513
 */
3514
function MessageSettings()
3515
{
3516
	global $txt, $user_info, $context, $sourcedir;
3517
	global $scripturl, $profile_vars, $cur_profile, $user_profile;
3518
3519
	// Need this for the display.
3520
	require_once($sourcedir . '/Profile.php');
3521
	require_once($sourcedir . '/Profile-Modify.php');
3522
3523
	// We want them to submit back to here.
3524
	$context['profile_custom_submit_url'] = $scripturl . '?action=pm;sa=settings;save';
3525
3526
	loadMemberData($user_info['id'], false, 'profile');
3527
	$cur_profile = $user_profile[$user_info['id']];
3528
3529
	loadLanguage('Profile');
3530
	loadTemplate('Profile');
3531
3532
	// Since this is internally handled with the profile code because that's how it was done ages ago
3533
	// we have to set everything up for handling this...
3534
	$context['page_title'] = $txt['pm_settings'];
3535
	$context['user']['is_owner'] = true;
3536
	$context['id_member'] = $user_info['id'];
3537
	$context['require_password'] = false;
3538
	$context['menu_item_selected'] = 'settings';
3539
	$context['submit_button_text'] = $txt['pm_settings'];
3540
	$context['profile_header_text'] = $txt['personal_messages'];
3541
	$context['sub_template'] = 'edit_options';
3542
	$context['page_desc'] = $txt['pm_settings_desc'];
3543
3544
	loadThemeOptions($user_info['id']);
3545
	loadCustomFields($user_info['id'], 'pmprefs');
3546
3547
	// Add our position to the linktree.
3548
	$context['linktree'][] = array(
3549
		'url' => $scripturl . '?action=pm;sa=settings',
3550
		'name' => $txt['pm_settings']
3551
	);
3552
3553
	// Are they saving?
3554
	if (isset($_REQUEST['save']))
3555
	{
3556
		checkSession();
3557
3558
		// Mimic what profile would do.
3559
		$_POST = htmltrim__recursive($_POST);
3560
		$_POST = htmlspecialchars__recursive($_POST);
3561
3562
		// Save the fields.
3563
		saveProfileFields();
3564
3565
		if (!empty($profile_vars))
3566
			updateMemberData($user_info['id'], $profile_vars);
3567
	}
3568
3569
	setupProfileContext(
3570
		array(
3571
			'pm_prefs',
3572
		)
3573
	);
3574
}
3575
3576
/**
3577
 * Allows the user to report a personal message to an administrator.
3578
 *
3579
 * - In the first instance requires that the ID of the message to report is passed through $_GET.
3580
 * - It allows the user to report to either a particular administrator - or the whole admin team.
3581
 * - It will forward on a copy of the original message without allowing the reporter to make changes.
3582
 *
3583
 * @uses template_report_message()
3584
 */
3585
function ReportMessage()
3586
{
3587
	global $txt, $context, $scripturl;
3588
	global $user_info, $language, $modSettings, $smcFunc;
3589
3590
	// Check that this feature is even enabled!
3591
	if (empty($modSettings['enableReportPM']) || empty($_REQUEST['pmsg']))
3592
		fatal_lang_error('no_access', false);
3593
3594
	$pmsg = (int) $_REQUEST['pmsg'];
3595
3596
	if (!isAccessiblePM($pmsg, 'inbox'))
3597
		fatal_lang_error('no_access', false);
3598
3599
	$context['pm_id'] = $pmsg;
3600
	$context['page_title'] = $txt['pm_report_title'];
3601
3602
	// If we're here, just send the user to the template, with a few useful context bits.
3603
	if (!isset($_POST['report']))
3604
	{
3605
		$context['sub_template'] = 'report_message';
3606
3607
		// @todo I don't like being able to pick who to send it to.  Favoritism, etc. sucks.
3608
		// Now, get all the administrators.
3609
		$request = $smcFunc['db_query']('', '
3610
			SELECT id_member, real_name
3611
			FROM {db_prefix}members
3612
			WHERE id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0
3613
			ORDER BY real_name',
3614
			array(
3615
				'admin_group' => 1,
3616
			)
3617
		);
3618
		$context['admins'] = array();
3619
		while ($row = $smcFunc['db_fetch_assoc']($request))
3620
			$context['admins'][$row['id_member']] = $row['real_name'];
3621
		$smcFunc['db_free_result']($request);
3622
3623
		// How many admins in total?
3624
		$context['admin_count'] = count($context['admins']);
3625
	}
3626
	// Otherwise, let's get down to the sending stuff.
3627
	else
3628
	{
3629
		// Check the session before proceeding any further!
3630
		checkSession();
3631
3632
		// First, pull out the message contents, and verify it actually went to them!
3633
		$request = $smcFunc['db_query']('', '
3634
			SELECT pm.subject, pm.body, pm.msgtime, pm.id_member_from, COALESCE(m.real_name, pm.from_name) AS sender_name
3635
			FROM {db_prefix}personal_messages AS pm
3636
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
3637
				LEFT JOIN {db_prefix}members AS m ON (m.id_member = pm.id_member_from)
3638
			WHERE pm.id_pm = {int:id_pm}
3639
				AND pmr.id_member = {int:current_member}
3640
				AND pmr.deleted = {int:not_deleted}
3641
			LIMIT 1',
3642
			array(
3643
				'current_member' => $user_info['id'],
3644
				'id_pm' => $context['pm_id'],
3645
				'not_deleted' => 0,
3646
			)
3647
		);
3648
		// Can only be a hacker here!
3649
		if ($smcFunc['db_num_rows']($request) == 0)
3650
			fatal_lang_error('no_access', false);
3651
		list ($subject, $body, $time, $memberFromID, $memberFromName) = $smcFunc['db_fetch_row']($request);
3652
		$smcFunc['db_free_result']($request);
3653
3654
		// Remove the line breaks...
3655
		$body = preg_replace('~<br ?/?' . '>~i', "\n", $body);
3656
3657
		// Get any other recipients of the email.
3658
		$request = $smcFunc['db_query']('', '
3659
			SELECT mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc
3660
			FROM {db_prefix}pm_recipients AS pmr
3661
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
3662
			WHERE pmr.id_pm = {int:id_pm}
3663
				AND pmr.id_member != {int:current_member}',
3664
			array(
3665
				'current_member' => $user_info['id'],
3666
				'id_pm' => $context['pm_id'],
3667
			)
3668
		);
3669
		$recipients = array();
3670
		$hidden_recipients = 0;
3671
		while ($row = $smcFunc['db_fetch_assoc']($request))
3672
		{
3673
			// If it's hidden still don't reveal their names - privacy after all ;)
3674
			if ($row['bcc'])
3675
				$hidden_recipients++;
3676
			else
3677
				$recipients[] = '[url=' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . ']' . $row['to_name'] . '[/url]';
3678
		}
3679
		$smcFunc['db_free_result']($request);
3680
3681
		if ($hidden_recipients)
3682
			$recipients[] = sprintf($txt['pm_report_pm_hidden'], $hidden_recipients);
3683
3684
		// Now let's get out and loop through the admins.
3685
		$request = $smcFunc['db_query']('', '
3686
			SELECT id_member, real_name, lngfile
3687
			FROM {db_prefix}members
3688
			WHERE (id_group = {int:admin_id} OR FIND_IN_SET({int:admin_id}, additional_groups) != 0)
3689
				' . (empty($_POST['id_admin']) ? '' : 'AND id_member = {int:specific_admin}') . '
3690
			ORDER BY lngfile',
3691
			array(
3692
				'admin_id' => 1,
3693
				'specific_admin' => isset($_POST['id_admin']) ? (int) $_POST['id_admin'] : 0,
3694
			)
3695
		);
3696
3697
		// Maybe we shouldn't advertise this?
3698
		if ($smcFunc['db_num_rows']($request) == 0)
3699
			fatal_lang_error('no_access', false);
3700
3701
		$memberFromName = un_htmlspecialchars($memberFromName);
3702
3703
		// Prepare the message storage array.
3704
		$messagesToSend = array();
3705
		// Loop through each admin, and add them to the right language pile...
3706
		while ($row = $smcFunc['db_fetch_assoc']($request))
3707
		{
3708
			// Need to send in the correct language!
3709
			$cur_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'];
3710
3711
			if (!isset($messagesToSend[$cur_language]))
3712
			{
3713
				loadLanguage('PersonalMessage', $cur_language, false);
3714
3715
				// Make the body.
3716
				$report_body = str_replace(array('{REPORTER}', '{SENDER}'), array(un_htmlspecialchars($user_info['name']), $memberFromName), $txt['pm_report_pm_user_sent']);
3717
				$report_body .= "\n" . '[b]' . $_POST['reason'] . '[/b]' . "\n\n";
3718
				if (!empty($recipients))
3719
					$report_body .= $txt['pm_report_pm_other_recipients'] . ' ' . implode(', ', $recipients) . "\n\n";
3720
				$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]';
3721
3722
				// Plonk it in the array ;)
3723
				$messagesToSend[$cur_language] = array(
3724
					'subject' => ($smcFunc['strpos']($subject, $txt['pm_report_pm_subject']) === false ? $txt['pm_report_pm_subject'] : '') . un_htmlspecialchars($subject),
3725
					'body' => $report_body,
3726
					'recipients' => array(
3727
						'to' => array(),
3728
						'bcc' => array()
3729
					),
3730
				);
3731
			}
3732
3733
			// Add them to the list.
3734
			$messagesToSend[$cur_language]['recipients']['to'][$row['id_member']] = $row['id_member'];
3735
		}
3736
		$smcFunc['db_free_result']($request);
3737
3738
		// Send a different email for each language.
3739
		foreach ($messagesToSend as $lang => $message)
3740
			sendpm($message['recipients'], $message['subject'], $message['body']);
3741
3742
		// Give the user their own language back!
3743
		if (!empty($modSettings['userLanguage']))
3744
			loadLanguage('PersonalMessage', '', false);
3745
3746
		// Leave them with a template.
3747
		$context['sub_template'] = 'report_message_complete';
3748
	}
3749
}
3750
3751
/**
3752
 * List all rules, and allow adding/entering etc...
3753
 */
3754
function ManageRules()
3755
{
3756
	global $txt, $context, $user_info, $scripturl, $smcFunc;
3757
3758
	// Limit the Criteria and Actions to this.
3759
	$context['rule_limiters'] = array(
3760
		'criteria' => 10,
3761
		'actions' => 10,
3762
	);
3763
3764
	// The link tree - gotta have this :o
3765
	$context['linktree'][] = array(
3766
		'url' => $scripturl . '?action=pm;sa=manrules',
3767
		'name' => $txt['pm_manage_rules']
3768
	);
3769
3770
	$context['page_title'] = $txt['pm_manage_rules'];
3771
	$context['sub_template'] = 'rules';
3772
3773
	// Load them... load them!!
3774
	LoadRules();
3775
3776
	// Likely to need all the groups!
3777
	$request = $smcFunc['db_query']('', '
3778
		SELECT mg.id_group, mg.group_name, COALESCE(gm.id_member, 0) AS can_moderate, mg.hidden
3779
		FROM {db_prefix}membergroups AS mg
3780
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
3781
		WHERE mg.min_posts = {int:min_posts}
3782
			AND mg.id_group != {int:moderator_group}
3783
			AND mg.hidden = {int:not_hidden}
3784
		ORDER BY mg.group_name',
3785
		array(
3786
			'current_member' => $user_info['id'],
3787
			'min_posts' => -1,
3788
			'moderator_group' => 3,
3789
			'not_hidden' => 0,
3790
		)
3791
	);
3792
	$context['groups'] = array();
3793
	while ($row = $smcFunc['db_fetch_assoc']($request))
3794
	{
3795
		// Hide hidden groups!
3796
		if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups'))
3797
			continue;
3798
3799
		$context['groups'][$row['id_group']] = $row['group_name'];
3800
	}
3801
	$smcFunc['db_free_result']($request);
3802
3803
	// Applying all rules?
3804
	if (isset($_GET['apply']))
3805
	{
3806
		checkSession('get');
3807
		spamProtection('pm');
3808
3809
		ApplyRules(true);
3810
		redirectexit('action=pm;sa=manrules');
3811
	}
3812
	// Editing a specific one?
3813
	if (isset($_GET['add']))
3814
	{
3815
		$context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']]) ? (int) $_GET['rid'] : 0;
3816
		$context['sub_template'] = 'add_rule';
3817
3818
		// Current rule information...
3819
		if ($context['rid'])
3820
		{
3821
			$context['rule'] = $context['rules'][$context['rid']];
3822
			$members = array();
3823
			// Need to get member names!
3824
			foreach ($context['rule']['criteria'] as $k => $criteria)
3825
				if ($criteria['t'] == 'mid' && !empty($criteria['v']))
3826
					$members[(int) $criteria['v']] = $k;
3827
3828
			if (!empty($members))
3829
			{
3830
				$request = $smcFunc['db_query']('', '
3831
					SELECT id_member, member_name
3832
					FROM {db_prefix}members
3833
					WHERE id_member IN ({array_int:member_list})',
3834
					array(
3835
						'member_list' => array_keys($members),
3836
					)
3837
				);
3838
				while ($row = $smcFunc['db_fetch_assoc']($request))
3839
					$context['rule']['criteria'][$members[$row['id_member']]]['v'] = $row['member_name'];
3840
				$smcFunc['db_free_result']($request);
3841
			}
3842
		}
3843
		else
3844
			$context['rule'] = array(
3845
				'id' => '',
3846
				'name' => '',
3847
				'criteria' => array(),
3848
				'actions' => array(),
3849
				'logic' => 'and',
3850
			);
3851
	}
3852
	// Saving?
3853
	elseif (isset($_GET['save']))
3854
	{
3855
		checkSession();
3856
		$context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']]) ? (int) $_GET['rid'] : 0;
3857
3858
		// Name is easy!
3859
		$ruleName = $smcFunc['htmlspecialchars'](trim($_POST['rule_name']));
3860
		if (empty($ruleName))
3861
			fatal_lang_error('pm_rule_no_name', false);
3862
3863
		// Sanity check...
3864
		if (empty($_POST['ruletype']) || empty($_POST['acttype']))
3865
			fatal_lang_error('pm_rule_no_criteria', false);
3866
3867
		// Let's do the criteria first - it's also hardest!
3868
		$criteria = array();
3869
		$criteriaCount = 0;
3870
		foreach ($_POST['ruletype'] as $ind => $type)
3871
		{
3872
			// Check everything is here...
3873
			if ($type == 'gid' && (!isset($_POST['ruledefgroup'][$ind]) || !isset($context['groups'][$_POST['ruledefgroup'][$ind]])))
3874
				continue;
3875
			elseif ($type != 'bud' && !isset($_POST['ruledef'][$ind]))
3876
				continue;
3877
3878
			// Too many rules in this rule.
3879
			if ($criteriaCount++ >= $context['rule_limiters']['criteria'])
3880
				break;
3881
3882
			// Members need to be found.
3883
			if ($type == 'mid')
3884
			{
3885
				$name = trim($_POST['ruledef'][$ind]);
3886
				$request = $smcFunc['db_query']('', '
3887
					SELECT id_member
3888
					FROM {db_prefix}members
3889
					WHERE real_name = {string:member_name}
3890
						OR member_name = {string:member_name}',
3891
					array(
3892
						'member_name' => $name,
3893
					)
3894
				);
3895
				if ($smcFunc['db_num_rows']($request) == 0)
3896
				{
3897
					loadLanguage('Errors');
3898
					fatal_lang_error('invalid_username', false);
3899
				}
3900
				list ($memID) = $smcFunc['db_fetch_row']($request);
3901
				$smcFunc['db_free_result']($request);
3902
3903
				$criteria[] = array('t' => 'mid', 'v' => $memID);
3904
			}
3905
			elseif ($type == 'bud')
3906
				$criteria[] = array('t' => 'bud', 'v' => 1);
3907
			elseif ($type == 'gid')
3908
				$criteria[] = array('t' => 'gid', 'v' => (int) $_POST['ruledefgroup'][$ind]);
3909
			elseif (in_array($type, array('sub', 'msg')) && trim($_POST['ruledef'][$ind]) != '')
3910
				$criteria[] = array('t' => $type, 'v' => $smcFunc['htmlspecialchars'](trim($_POST['ruledef'][$ind])));
3911
		}
3912
3913
		// Also do the actions!
3914
		$actions = array();
3915
		$doDelete = 0;
3916
		$isOr = $_POST['rule_logic'] == 'or' ? 1 : 0;
3917
		$actionCount = 0;
3918
		foreach ($_POST['acttype'] as $ind => $type)
3919
		{
3920
			// Picking a valid label?
3921
			if ($type == 'lab' && (!ctype_digit((string) $ind) || !isset($_POST['labdef'][$ind]) || $_POST['labdef'][$ind] == '' || !isset($context['labels'][$_POST['labdef'][$ind]])))
3922
				continue;
3923
3924
			// Too many actions in this rule.
3925
			if ($actionCount++ >= $context['rule_limiters']['actions'])
3926
				break;
3927
3928
			// Record what we're doing.
3929
			if ($type == 'del')
3930
				$doDelete = 1;
3931
			elseif ($type == 'lab')
3932
				$actions[] = array('t' => 'lab', 'v' => (int) $_POST['labdef'][$ind]);
3933
		}
3934
3935
		if (empty($criteria) || (empty($actions) && !$doDelete))
3936
			fatal_lang_error('pm_rule_no_criteria', false);
3937
3938
		// What are we storing?
3939
		$criteria = $smcFunc['json_encode']($criteria);
3940
		$actions = $smcFunc['json_encode']($actions);
3941
3942
		// Create the rule?
3943
		if (empty($context['rid']))
3944
			$smcFunc['db_insert']('',
3945
				'{db_prefix}pm_rules',
3946
				array(
3947
					'id_member' => 'int', 'rule_name' => 'string', 'criteria' => 'string', 'actions' => 'string',
3948
					'delete_pm' => 'int', 'is_or' => 'int',
3949
				),
3950
				array(
3951
					$user_info['id'], $ruleName, $criteria, $actions, $doDelete, $isOr,
3952
				),
3953
				array('id_rule')
3954
			);
3955
		else
3956
			$smcFunc['db_query']('', '
3957
				UPDATE {db_prefix}pm_rules
3958
				SET rule_name = {string:rule_name}, criteria = {string:criteria}, actions = {string:actions},
3959
					delete_pm = {int:delete_pm}, is_or = {int:is_or}
3960
				WHERE id_rule = {int:id_rule}
3961
					AND id_member = {int:current_member}',
3962
				array(
3963
					'current_member' => $user_info['id'],
3964
					'delete_pm' => $doDelete,
3965
					'is_or' => $isOr,
3966
					'id_rule' => $context['rid'],
3967
					'rule_name' => $ruleName,
3968
					'criteria' => $criteria,
3969
					'actions' => $actions,
3970
				)
3971
			);
3972
3973
		redirectexit('action=pm;sa=manrules');
3974
	}
3975
	// Deleting?
3976
	elseif (isset($_POST['delselected']) && !empty($_POST['delrule']))
3977
	{
3978
		checkSession();
3979
		$toDelete = array();
3980
		foreach ($_POST['delrule'] as $k => $v)
3981
			$toDelete[] = (int) $k;
3982
3983
		if (!empty($toDelete))
3984
			$smcFunc['db_query']('', '
3985
				DELETE FROM {db_prefix}pm_rules
3986
				WHERE id_rule IN ({array_int:delete_list})
3987
					AND id_member = {int:current_member}',
3988
				array(
3989
					'current_member' => $user_info['id'],
3990
					'delete_list' => $toDelete,
3991
				)
3992
			);
3993
3994
		redirectexit('action=pm;sa=manrules');
3995
	}
3996
}
3997
3998
/**
3999
 * This will apply rules to all unread messages. If all_messages is set will, clearly, do it to all!
4000
 *
4001
 * @param bool $all_messages Whether to apply this to all messages or just unread ones
4002
 */
4003
function ApplyRules($all_messages = false)
4004
{
4005
	global $user_info, $smcFunc, $context, $options;
4006
4007
	// Want this - duh!
4008
	loadRules();
4009
4010
	// No rules?
4011
	if (empty($context['rules']))
4012
		return;
4013
4014
	// Just unread ones?
4015
	$ruleQuery = $all_messages ? '' : ' AND pmr.is_new = 1';
4016
4017
	// @todo Apply all should have timeout protection!
4018
	// Get all the messages that match this.
4019
	$request = $smcFunc['db_query']('', '
4020
		SELECT
4021
			pmr.id_pm, pm.id_member_from, pm.subject, pm.body, mem.id_group
4022
		FROM {db_prefix}pm_recipients AS pmr
4023
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
4024
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
4025
		WHERE pmr.id_member = {int:current_member}
4026
			AND pmr.deleted = {int:not_deleted}
4027
			' . $ruleQuery,
4028
		array(
4029
			'current_member' => $user_info['id'],
4030
			'not_deleted' => 0,
4031
		)
4032
	);
4033
	$actions = array();
4034
	while ($row = $smcFunc['db_fetch_assoc']($request))
4035
	{
4036
		foreach ($context['rules'] as $rule)
4037
		{
4038
			$match = false;
4039
			// Loop through all the criteria hoping to make a match.
4040
			foreach ($rule['criteria'] as $criterium)
4041
			{
4042
				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))
4043
					$match = true;
4044
				// If we're adding and one criteria don't match then we stop!
4045
				elseif ($rule['logic'] == 'and')
4046
				{
4047
					$match = false;
4048
					break;
4049
				}
4050
			}
4051
4052
			// If we have a match the rule must be true - act!
4053
			if ($match)
4054
			{
4055
				if ($rule['delete'])
4056
					$actions['deletes'][] = $row['id_pm'];
4057
				else
4058
				{
4059
					foreach ($rule['actions'] as $ruleAction)
4060
					{
4061
						if ($ruleAction['t'] == 'lab')
4062
						{
4063
							// Get a basic pot started!
4064
							if (!isset($actions['labels'][$row['id_pm']]))
4065
								$actions['labels'][$row['id_pm']] = array();
4066
4067
							$actions['labels'][$row['id_pm']][] = $ruleAction['v'];
4068
						}
4069
					}
4070
				}
4071
			}
4072
		}
4073
	}
4074
	$smcFunc['db_free_result']($request);
4075
4076
	// Deletes are easy!
4077
	if (!empty($actions['deletes']))
4078
		deleteMessages($actions['deletes']);
4079
4080
	// Relabel?
4081
	if (!empty($actions['labels']))
4082
	{
4083
		foreach ($actions['labels'] as $pm => $labels)
4084
		{
4085
			// Quickly check each label is valid!
4086
			$realLabels = array();
4087
			foreach ($context['labels'] as $label)
4088
				if (in_array($label['id'], $labels))
4089
					$realLabels[] = $label['id'];
4090
4091
			if (!empty($options['pm_remove_inbox_label']))
4092
				$smcFunc['db_query']('', '
4093
					UPDATE {db_prefix}pm_recipients
4094
					SET in_inbox = {int:in_inbox}
4095
					WHERE id_pm = {int:id_pm}
4096
						AND id_member = {int:current_member}',
4097
					array(
4098
						'in_inbox' => 0,
4099
						'id_pm' => $pm,
4100
						'current_member' => $user_info['id'],
4101
					)
4102
				);
4103
4104
			$inserts = array();
4105
			// Now we insert the label info
4106
			foreach ($realLabels as $a_label)
4107
				$inserts[] = array($pm, $a_label);
4108
4109
			$smcFunc['db_insert']('ignore',
4110
				'{db_prefix}pm_labeled_messages',
4111
				array('id_pm' => 'int', 'id_label' => 'int'),
4112
				$inserts,
4113
				array('id_pm', 'id_label')
4114
			);
4115
		}
4116
	}
4117
}
4118
4119
/**
4120
 * Load up all the rules for the current user.
4121
 *
4122
 * @param bool $reload Whether or not to reload all the rules from the database if $context['rules'] is set
4123
 */
4124
function LoadRules($reload = false)
4125
{
4126
	global $user_info, $context, $smcFunc;
4127
4128
	if (isset($context['rules']) && !$reload)
4129
		return;
4130
4131
	$request = $smcFunc['db_query']('', '
4132
		SELECT
4133
			id_rule, rule_name, criteria, actions, delete_pm, is_or
4134
		FROM {db_prefix}pm_rules
4135
		WHERE id_member = {int:current_member}',
4136
		array(
4137
			'current_member' => $user_info['id'],
4138
		)
4139
	);
4140
	$context['rules'] = array();
4141
	// Simply fill in the data!
4142
	while ($row = $smcFunc['db_fetch_assoc']($request))
4143
	{
4144
		$context['rules'][$row['id_rule']] = array(
4145
			'id' => $row['id_rule'],
4146
			'name' => $row['rule_name'],
4147
			'criteria' => $smcFunc['json_decode']($row['criteria'], true),
4148
			'actions' => $smcFunc['json_decode']($row['actions'], true),
4149
			'delete' => $row['delete_pm'],
4150
			'logic' => $row['is_or'] ? 'or' : 'and',
4151
		);
4152
4153
		if ($row['delete_pm'])
4154
			$context['rules'][$row['id_rule']]['actions'][] = array('t' => 'del', 'v' => 1);
4155
	}
4156
	$smcFunc['db_free_result']($request);
4157
}
4158
4159
/**
4160
 * Check if the PM is available to the current user.
4161
 *
4162
 * @param int $pmID The ID of the PM
4163
 * @param string $validFor Which folders this is valud for - can be 'inbox', 'outbox' or 'in_or_outbox'
4164
 * @return boolean Whether the PM is accessible in that folder for the current user
4165
 */
4166
function isAccessiblePM($pmID, $validFor = 'in_or_outbox')
4167
{
4168
	global $user_info, $smcFunc, $txt;
4169
4170
	$request = $smcFunc['db_query']('', '
4171
		SELECT
4172
			pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted} AS valid_for_outbox,
4173
			pmr.id_pm IS NOT NULL AS valid_for_inbox
4174
		FROM {db_prefix}personal_messages AS pm
4175
			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})
4176
		WHERE pm.id_pm = {int:id_pm}
4177
			AND ((pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted}) OR pmr.id_pm IS NOT NULL)',
4178
		array(
4179
			'id_pm' => $pmID,
4180
			'id_current_member' => $user_info['id'],
4181
			'not_deleted' => 0,
4182
		)
4183
	);
4184
4185
	if ($smcFunc['db_num_rows']($request) === 0)
4186
	{
4187
		$smcFunc['db_free_result']($request);
4188
		return false;
4189
	}
4190
4191
	$validationResult = $smcFunc['db_fetch_assoc']($request);
4192
	$smcFunc['db_free_result']($request);
4193
4194
	switch ($validFor)
4195
	{
4196
		case 'inbox':
4197
			return !empty($validationResult['valid_for_inbox']);
4198
			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...
4199
4200
		case 'outbox':
4201
			return !empty($validationResult['valid_for_outbox']);
4202
			break;
4203
4204
		case 'in_or_outbox':
4205
			return !empty($validationResult['valid_for_inbox']) || !empty($validationResult['valid_for_outbox']);
4206
			break;
4207
4208
		default:
4209
			loadLanguage('Errors');
4210
			throw new \Exception('pm_invalid_validation_type');
4211
			break;
4212
	}
4213
}
4214
4215
?>